atris 3.34.0 → 3.36.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 +35 -0
- package/FOR_AGENTS.md +5 -3
- package/README.md +5 -3
- package/atris/GETTING_STARTED.md +1 -1
- package/atris/atris.md +3 -0
- package/atris/policies/day-loop-voice.md +102 -0
- package/atris/policies/outbound-artifact-gate.md +2 -0
- package/atris/skills/design/SKILL.md +56 -32
- package/atris/skills/endgame/SKILL.md +12 -6
- package/atris/skills/engines/SKILL.md +35 -8
- package/atris/skills/fable-method/SKILL.md +66 -0
- package/atris/skills/improve/SKILL.md +65 -45
- package/atris/skills/render-cli/SKILL.md +88 -0
- package/atris/skills/youtube/SKILL.md +10 -1
- package/atris.md +6 -2
- package/ax +617 -31
- package/bin/atris.js +719 -266
- package/commands/activate.js +194 -88
- package/commands/aeo.js +52 -0
- package/commands/agents.js +166 -0
- package/commands/autoland.js +718 -72
- package/commands/autopilot-front.js +20 -2
- package/commands/autopilot.js +118 -2
- package/commands/avail.js +407 -0
- package/commands/bench.js +188 -0
- package/commands/brain.js +3 -0
- package/commands/brief.js +651 -0
- package/commands/business-sync.js +192 -6
- package/commands/business.js +91 -8
- package/commands/clean.js +50 -24
- package/commands/close.js +1083 -0
- package/commands/cloud.js +245 -0
- package/commands/codex-goal.js +26 -2
- package/commands/compile.js +292 -1
- package/commands/computer.js +150 -3
- package/commands/dream.js +365 -0
- package/commands/drill.js +371 -0
- package/commands/drive.js +187 -0
- package/commands/engine.js +1061 -29
- package/commands/experiments.js +28 -0
- package/commands/feed.js +202 -0
- package/commands/feedback.js +34 -12
- package/commands/fleet-report.js +206 -0
- package/commands/github.js +38 -0
- package/commands/gm.js +285 -3
- package/commands/goal.js +247 -0
- package/commands/improve.js +642 -26
- package/commands/init.js +83 -43
- package/commands/integrations.js +39 -11
- package/commands/interview.js +209 -0
- package/commands/land.js +253 -52
- package/commands/lesson.js +112 -1
- package/commands/lifecycle.js +39 -3
- package/commands/linear.js +38 -0
- package/commands/log.js +84 -1
- package/commands/loops.js +220 -16
- package/commands/meet.js +220 -0
- package/commands/member.js +899 -66
- package/commands/mission.js +3512 -332
- package/commands/next.js +137 -0
- package/commands/now.js +240 -21
- package/commands/one-lap.js +776 -0
- package/commands/orb.js +314 -0
- package/commands/pack-craft.js +179 -0
- package/commands/pack.js +823 -0
- package/commands/play.js +3 -2
- package/commands/probe.js +30 -3
- package/commands/pulse.js +241 -46
- package/commands/push.js +260 -82
- package/commands/radar.js +259 -14
- package/commands/rainmaker.js +49 -0
- package/commands/report.js +415 -0
- package/commands/scout.js +147 -0
- package/commands/search.js +363 -0
- package/commands/serve.js +54 -0
- package/commands/skill.js +47 -3
- package/commands/slop.js +50 -2
- package/commands/soul.js +1 -1
- package/commands/status.js +50 -5
- package/commands/stream.js +861 -0
- package/commands/stripe.js +38 -0
- package/commands/study.js +693 -0
- package/commands/supabase.js +39 -0
- package/commands/sync.js +67 -54
- package/commands/task.js +2275 -182
- package/commands/team.js +73 -0
- package/commands/truth.js +29 -3
- package/commands/unknowns.js +627 -0
- package/commands/update.js +44 -0
- package/commands/vercel.js +38 -0
- package/commands/verify.js +96 -0
- package/commands/watch.js +303 -0
- package/commands/wish.js +500 -0
- package/commands/workflow.js +11 -5
- package/commands/worktree.js +299 -20
- package/commands/write.js +399 -0
- package/commands/xp.js +29 -11
- package/lib/auto-accept-certified.js +391 -43
- package/lib/autoland.js +353 -52
- package/lib/ax-auto-lane.js +79 -0
- package/lib/bench/context.js +147 -0
- package/lib/bench/engines.js +141 -0
- package/lib/bench/report.js +140 -0
- package/lib/bench/runner.js +512 -0
- package/lib/brief-ledger.js +350 -0
- package/lib/cloud-mission.js +259 -0
- package/lib/codex-flight.js +154 -0
- package/lib/default-runner.js +45 -0
- package/lib/default-verifier.js +70 -0
- package/lib/engine-registry.js +232 -0
- package/lib/experiments/daily.js +640 -0
- package/lib/fleet.js +2431 -38
- package/lib/improve-vitals-html.js +171 -0
- package/lib/known-commands.js +58 -0
- package/lib/loop-doctor.js +416 -0
- package/lib/member-switches.js +144 -0
- package/lib/memory-view.js +14 -5
- package/lib/mission-room.js +1 -0
- package/lib/mission-root.js +52 -0
- package/lib/mission-runtime-loop.js +7 -0
- package/lib/next-moves.js +327 -10
- package/lib/official-cli-integration.js +174 -0
- package/lib/one-lap-validator.js +60 -0
- package/lib/orb-context.js +477 -0
- package/lib/orb-scorecard.js +224 -0
- package/lib/outbound-send-gate.js +165 -0
- package/lib/permission-grants.js +293 -0
- package/lib/policy-lessons.js +52 -1
- package/lib/pulse.js +277 -3
- package/lib/receipt-block.js +168 -0
- package/lib/receipt-evidence.js +65 -4
- package/lib/review-integrity.js +147 -0
- package/lib/router-brain.js +352 -0
- package/lib/runner-command.js +33 -0
- package/lib/self-drive.js +258 -0
- package/lib/short-name.js +103 -0
- package/lib/spawn-env.js +18 -0
- package/lib/state-detection.js +56 -1
- package/lib/sync-status.js +59 -0
- package/lib/task-db.js +319 -27
- package/lib/task-proof.js +43 -1
- package/lib/task-receipt.js +93 -0
- package/lib/team-presence.js +260 -0
- package/lib/tool-result-encode.js +7 -0
- package/lib/trust-tiers.js +90 -0
- package/lib/usage.js +107 -0
- package/lib/voice-gate.js +163 -0
- package/lib/wish-audit.js +1368 -0
- package/lib/wish-delegate.js +1840 -0
- package/lib/wish-design.js +110 -0
- package/lib/wish-stats.js +183 -0
- package/lib/wish-store.js +354 -0
- package/lib/zip.js +221 -0
- package/package.json +3 -1
- package/templates/loops/atris/loops/LOOPS.md +55 -0
- package/templates/loops/atris/loops/TICK.md +24 -0
- package/templates/loops/atris/loops/feedback.md +22 -0
- package/templates/loops/atris/loops/quality.md +22 -0
- package/templates/loops/atris/wiki/systems/loops.md +41 -0
- package/utils/api.js +5 -1
- package/utils/auth.js +57 -21
- package/utils/update-check.js +27 -6
- package/atris/learnings.jsonl +0 -1
package/commands/member.js
CHANGED
|
@@ -6,6 +6,7 @@ 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');
|
|
9
|
+
const { defaultObjectiveRunner } = require('../lib/default-runner');
|
|
9
10
|
|
|
10
11
|
function todayLogName() {
|
|
11
12
|
const now = new Date();
|
|
@@ -211,6 +212,12 @@ function resolveMemberRuntime(name) {
|
|
|
211
212
|
function memberPaths(name) {
|
|
212
213
|
const teamDir = path.join(process.cwd(), 'atris', 'team');
|
|
213
214
|
const memberDir = path.join(teamDir, name || '');
|
|
215
|
+
// Steering signals live in the workspace's single .atris/state store. Resolve
|
|
216
|
+
// the shared workspace root (spine -> git -> cwd) so a member run from a
|
|
217
|
+
// subdirectory reads steering from the real store instead of a nonexistent
|
|
218
|
+
// nested .atris — the same resolver the mission/task/usage/autopilot state
|
|
219
|
+
// uses. (teamDir markdown discovery is a separate cwd concern, filed.)
|
|
220
|
+
const workspaceRoot = require('../lib/mission-root').resolveWorkspaceRoot(process.cwd());
|
|
214
221
|
return {
|
|
215
222
|
teamDir,
|
|
216
223
|
memberDir,
|
|
@@ -218,7 +225,7 @@ function memberPaths(name) {
|
|
|
218
225
|
missionFile: path.join(memberDir, 'MISSION.md'),
|
|
219
226
|
goalsJson: path.join(memberDir, 'goals.json'),
|
|
220
227
|
goalsMd: path.join(memberDir, 'goals.md'),
|
|
221
|
-
steeringJsonl: path.join(
|
|
228
|
+
steeringJsonl: path.join(workspaceRoot, '.atris', 'state', 'steering.jsonl'),
|
|
222
229
|
};
|
|
223
230
|
}
|
|
224
231
|
|
|
@@ -252,6 +259,50 @@ function ensureMissionFile(memberDir, { name, role, description } = {}) {
|
|
|
252
259
|
return missionPath;
|
|
253
260
|
}
|
|
254
261
|
|
|
262
|
+
function listExistingMemberSlugs() {
|
|
263
|
+
const teamDir = path.join(process.cwd(), 'atris', 'team');
|
|
264
|
+
try {
|
|
265
|
+
return fs.readdirSync(teamDir, { withFileTypes: true })
|
|
266
|
+
.filter(entry => entry.isDirectory() && !entry.name.startsWith('_'))
|
|
267
|
+
.map(entry => entry.name)
|
|
268
|
+
.filter(slug => fs.existsSync(path.join(teamDir, slug, 'MEMBER.md')))
|
|
269
|
+
.sort();
|
|
270
|
+
} catch {
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function editDistance(a, b) {
|
|
276
|
+
const rows = a.length + 1;
|
|
277
|
+
const cols = b.length + 1;
|
|
278
|
+
const dist = Array.from({ length: rows }, (_, i) => [i, ...Array(cols - 1).fill(0)]);
|
|
279
|
+
for (let j = 0; j < cols; j++) dist[0][j] = j;
|
|
280
|
+
for (let i = 1; i < rows; i++) {
|
|
281
|
+
for (let j = 1; j < cols; j++) {
|
|
282
|
+
dist[i][j] = Math.min(
|
|
283
|
+
dist[i - 1][j] + 1,
|
|
284
|
+
dist[i][j - 1] + 1,
|
|
285
|
+
dist[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return dist[rows - 1][cols - 1];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function nearestMemberSlug(name, slugs) {
|
|
293
|
+
const needle = String(name || '').toLowerCase();
|
|
294
|
+
let best = null;
|
|
295
|
+
let bestScore = Infinity;
|
|
296
|
+
for (const slug of slugs) {
|
|
297
|
+
const score = editDistance(needle, slug.toLowerCase());
|
|
298
|
+
if (score < bestScore) {
|
|
299
|
+
best = slug;
|
|
300
|
+
bestScore = score;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return bestScore <= Math.max(2, Math.floor(needle.length / 3)) ? best : null;
|
|
304
|
+
}
|
|
305
|
+
|
|
255
306
|
function requireMemberDir(name) {
|
|
256
307
|
if (!name) {
|
|
257
308
|
console.error('Usage: atris member <goal|tick|review> <name> ...');
|
|
@@ -266,6 +317,15 @@ function requireMemberDir(name) {
|
|
|
266
317
|
if (!fs.existsSync(paths.memberFile)) {
|
|
267
318
|
const aliasHint = resolved.aliasOf ? ` or atris/team/${resolved.aliasOf}/MEMBER.md` : '';
|
|
268
319
|
console.error(`Member "${name}" not found at atris/team/${name}/MEMBER.md${aliasHint}`);
|
|
320
|
+
const team = listExistingMemberSlugs();
|
|
321
|
+
const nearest = nearestMemberSlug(name, team);
|
|
322
|
+
if (nearest) {
|
|
323
|
+
console.error(`Did you mean "${nearest}"?`);
|
|
324
|
+
} else if (team.length) {
|
|
325
|
+
console.error(`Team here: ${team.join(', ')}`);
|
|
326
|
+
} else {
|
|
327
|
+
console.error(`No team members exist here yet. Create one: atris member create ${name} --role="..."`);
|
|
328
|
+
}
|
|
269
329
|
process.exit(1);
|
|
270
330
|
}
|
|
271
331
|
return {
|
|
@@ -440,6 +500,170 @@ function memberRunOwnedActiveMissionId(name, missionMap) {
|
|
|
440
500
|
return candidates[0]?.id || '';
|
|
441
501
|
}
|
|
442
502
|
|
|
503
|
+
const MEMBER_LIVE_MISSION_STATUSES = new Set(['running', 'planning']);
|
|
504
|
+
const MEMBER_ACTIVE_ACTIVITY_MS = 24 * 60 * 60 * 1000;
|
|
505
|
+
const MEMBER_RECENT_ACTIVITY_MS = 72 * 60 * 60 * 1000;
|
|
506
|
+
|
|
507
|
+
function memberSlugFromRecord(member) {
|
|
508
|
+
if (member?.format === 'directory' && member.dir) return path.basename(member.dir);
|
|
509
|
+
if (member?.format === 'flat' && member.path) return path.basename(member.path, '.md');
|
|
510
|
+
return lowerCompact(member?.name || '');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function isFleetMember(member) {
|
|
514
|
+
if (!member || member.format !== 'directory') return false;
|
|
515
|
+
const slug = memberSlugFromRecord(member);
|
|
516
|
+
return Boolean(slug) && !new Set(['_archive', '_archived', '_template']).has(slug);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function missionTimeMs(mission) {
|
|
520
|
+
return Date.parse(mission?.last_tick_at || mission?.updated_at || mission?.created_at || '') || 0;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function memberOwnedMissions(name, missionMap = memberRunMissionMap()) {
|
|
524
|
+
const owner = lowerCompact(name);
|
|
525
|
+
return Array.from(missionMap.values())
|
|
526
|
+
.filter((mission) => lowerCompact(mission?.owner) === owner)
|
|
527
|
+
.sort((a, b) => missionTimeMs(b) - missionTimeMs(a));
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function memberLatestMission(name, missionMap = memberRunMissionMap()) {
|
|
531
|
+
return memberOwnedMissions(name, missionMap)[0] || null;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function memberLiveMission(name, missionMap = memberRunMissionMap()) {
|
|
535
|
+
return memberOwnedMissions(name, missionMap)
|
|
536
|
+
.find((mission) => MEMBER_LIVE_MISSION_STATUSES.has(String(mission?.status || '').toLowerCase())) || null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function missionShortName(mission, max = 36) {
|
|
540
|
+
return compactSentence(mission?.slug || mission?.title || mission?.objective || mission?.id || '', max) || '-';
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function ageLabelFromMs(ms, nowMs = Date.now()) {
|
|
544
|
+
if (!ms) return '-';
|
|
545
|
+
const seconds = Math.max(0, Math.floor((nowMs - ms) / 1000));
|
|
546
|
+
if (seconds < 60) return 'now';
|
|
547
|
+
const minutes = Math.floor(seconds / 60);
|
|
548
|
+
if (minutes < 60) return `${minutes}m`;
|
|
549
|
+
const hours = Math.floor(minutes / 60);
|
|
550
|
+
if (hours < 48) return `${hours}h`;
|
|
551
|
+
return `${Math.floor(hours / 24)}d`;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function timestampMs(value) {
|
|
555
|
+
const ms = Date.parse(value || '');
|
|
556
|
+
return Number.isFinite(ms) ? ms : 0;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function newestMtimeRecursive(root) {
|
|
560
|
+
if (!fs.existsSync(root)) return 0;
|
|
561
|
+
let newest = 0;
|
|
562
|
+
const stack = [root];
|
|
563
|
+
while (stack.length) {
|
|
564
|
+
const current = stack.pop();
|
|
565
|
+
let stat = null;
|
|
566
|
+
try {
|
|
567
|
+
stat = fs.statSync(current);
|
|
568
|
+
} catch {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
newest = Math.max(newest, stat.mtimeMs || 0);
|
|
572
|
+
if (!stat.isDirectory()) continue;
|
|
573
|
+
let entries = [];
|
|
574
|
+
try {
|
|
575
|
+
entries = fs.readdirSync(current);
|
|
576
|
+
} catch {
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
for (const entry of entries) stack.push(path.join(current, entry));
|
|
580
|
+
}
|
|
581
|
+
return newest;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function newestRunActivityMtime(name, root = process.cwd()) {
|
|
585
|
+
const runsDir = path.join(root, 'atris', 'runs');
|
|
586
|
+
if (!fs.existsSync(runsDir)) return 0;
|
|
587
|
+
const needle = lowerCompact(name);
|
|
588
|
+
let newest = 0;
|
|
589
|
+
for (const entry of fs.readdirSync(runsDir)) {
|
|
590
|
+
if (!lowerCompact(entry).includes(needle)) continue;
|
|
591
|
+
try {
|
|
592
|
+
newest = Math.max(newest, fs.statSync(path.join(runsDir, entry)).mtimeMs || 0);
|
|
593
|
+
} catch { /* ignore unreadable activity entries */ }
|
|
594
|
+
}
|
|
595
|
+
return newest;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function memberLastActivity(name, paths = memberPaths(name), root = process.cwd()) {
|
|
599
|
+
const logsMs = newestMtimeRecursive(path.join(paths.memberDir, 'logs'));
|
|
600
|
+
const runsMs = newestRunActivityMtime(name, root);
|
|
601
|
+
const ms = Math.max(logsMs, runsMs);
|
|
602
|
+
return {
|
|
603
|
+
at_ms: ms,
|
|
604
|
+
age: ageLabelFromMs(ms),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function memberMissionSummary(name, missionMap = memberRunMissionMap()) {
|
|
609
|
+
const latest = memberLatestMission(name, missionMap);
|
|
610
|
+
const live = memberLiveMission(name, missionMap);
|
|
611
|
+
const lastTickMs = timestampMs(latest?.last_tick_at);
|
|
612
|
+
return {
|
|
613
|
+
latest,
|
|
614
|
+
live,
|
|
615
|
+
name: missionShortName(latest),
|
|
616
|
+
state: latest?.status || '-',
|
|
617
|
+
last_tick_at: latest?.last_tick_at || null,
|
|
618
|
+
last_tick_age: ageLabelFromMs(lastTickMs),
|
|
619
|
+
live_mission: Boolean(live),
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function memberVerdict({ mission, activity }, nowMs = Date.now()) {
|
|
624
|
+
const activityAge = activity?.at_ms ? nowMs - activity.at_ms : Number.POSITIVE_INFINITY;
|
|
625
|
+
if (mission?.live_mission || activityAge < MEMBER_ACTIVE_ACTIVITY_MS) return 'ACTIVE';
|
|
626
|
+
if (activityAge < MEMBER_RECENT_ACTIVITY_MS) return 'RECENT';
|
|
627
|
+
return 'IDLE';
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function memberGoalPlaneStatus(name, paths = requireMemberDir(name)) {
|
|
631
|
+
const state = loadMemberGoals(name, paths);
|
|
632
|
+
const goal = activeGoal(state);
|
|
633
|
+
const current = memberOpenExperiment(state);
|
|
634
|
+
const lastReviewed = memberLastReviewedExperiment(state);
|
|
635
|
+
const value = memberValueSummary(state);
|
|
636
|
+
const logs = recentLogLines(paths.memberDir);
|
|
637
|
+
const needsUser = current?.status === 'blocked';
|
|
638
|
+
const stateLabel = !goal
|
|
639
|
+
? 'no_goal'
|
|
640
|
+
: needsUser
|
|
641
|
+
? 'needs_user'
|
|
642
|
+
: current
|
|
643
|
+
? current.status
|
|
644
|
+
: 'ready';
|
|
645
|
+
const ask = needsUser ? (current.block?.ask || 'Needs operator input.') : null;
|
|
646
|
+
const nextCommand = !goal
|
|
647
|
+
? `atris member goal ${name} "..."`
|
|
648
|
+
: needsUser
|
|
649
|
+
? `atris member review ${name} ${current.id} --discard --proof "..."`
|
|
650
|
+
: current
|
|
651
|
+
? `atris member review ${name} ${current.id} --accept --proof "..." --value 4`
|
|
652
|
+
: `atris member tick ${name}`;
|
|
653
|
+
return {
|
|
654
|
+
state,
|
|
655
|
+
goal,
|
|
656
|
+
current,
|
|
657
|
+
lastReviewed,
|
|
658
|
+
value,
|
|
659
|
+
logs,
|
|
660
|
+
needsUser,
|
|
661
|
+
stateLabel,
|
|
662
|
+
ask,
|
|
663
|
+
nextCommand,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
443
667
|
function resolveMemberRunMissionId(name, args = []) {
|
|
444
668
|
const override = readFlag(args, '--mission', '') || readFlag(args, '--mission-id', '');
|
|
445
669
|
if (override) return override;
|
|
@@ -450,8 +674,10 @@ function resolveMemberRunMissionId(name, args = []) {
|
|
|
450
674
|
const runtimeId = memberRunRunnableMissionId(purpose.runtimeMission?.id, missionMap);
|
|
451
675
|
if (runtimeId) return runtimeId;
|
|
452
676
|
|
|
677
|
+
// strictly active: activeGoal()'s goals[0] fallback would resurrect a
|
|
678
|
+
// paused goal's stale mission pointer and error the leg every hour
|
|
453
679
|
const goals = loadMemberGoals(name, paths);
|
|
454
|
-
const goal =
|
|
680
|
+
const goal = (goals.goals || []).find((candidate) => candidate.status === 'active') || null;
|
|
455
681
|
const goalId = memberRunRunnableMissionId(goal?.mission_id, missionMap);
|
|
456
682
|
if (goalId) return goalId;
|
|
457
683
|
|
|
@@ -482,6 +708,7 @@ const MEMBER_RUN_START_VALUE_FLAGS = [
|
|
|
482
708
|
|
|
483
709
|
const MEMBER_RUN_START_BOOLEAN_FLAGS = [
|
|
484
710
|
'--json',
|
|
711
|
+
'--no-verify',
|
|
485
712
|
'--worktree',
|
|
486
713
|
'--shared-checkout',
|
|
487
714
|
'--no-worktree',
|
|
@@ -517,16 +744,34 @@ function memberRunTruthRule(args = []) {
|
|
|
517
744
|
|| 'say what changed, what was checked, and what is still unproven';
|
|
518
745
|
}
|
|
519
746
|
|
|
747
|
+
function operatorWaitingWishTexts(root = process.cwd()) {
|
|
748
|
+
// Wishes waiting on operator answers are not actionable candidates: seeding
|
|
749
|
+
// them into member runs spawned three parallel verifier-less wrapper
|
|
750
|
+
// missions chewing the same unanswered sentence (seen live 2026-07-07).
|
|
751
|
+
try {
|
|
752
|
+
const { waitingOperatorWishes } = require('../lib/wish-delegate');
|
|
753
|
+
return new Set(
|
|
754
|
+
waitingOperatorWishes(root)
|
|
755
|
+
.map((wish) => cleanMemberRunPhrase(wish?.text || wish?.task_text || '').toLowerCase())
|
|
756
|
+
.filter(Boolean),
|
|
757
|
+
);
|
|
758
|
+
} catch {
|
|
759
|
+
return new Set();
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
520
763
|
function memberRunUsefulTarget() {
|
|
521
764
|
try {
|
|
522
765
|
const { nextMoves: pickNextMoves, isGenericInboxPlaceholder } = require('../lib/next-moves');
|
|
523
766
|
const moves = pickNextMoves(process.cwd(), 5);
|
|
767
|
+
const waitingWishes = operatorWaitingWishTexts();
|
|
524
768
|
const candidate = moves.find((move) => {
|
|
525
769
|
const title = cleanMemberRunPhrase(move?.title);
|
|
526
770
|
if (!title) return false;
|
|
527
771
|
if (isGenericInboxPlaceholder(title)) return false;
|
|
528
772
|
if (/^mission xp\s*:/i.test(title)) return false;
|
|
529
773
|
if (/^decide and start the next useful mission after:/i.test(title)) return false;
|
|
774
|
+
if (waitingWishes.has(title.toLowerCase())) return false;
|
|
530
775
|
return true;
|
|
531
776
|
});
|
|
532
777
|
return candidate?.title ? cleanMemberRunPhrase(candidate.title) : '';
|
|
@@ -569,17 +814,18 @@ function pushFlagWhenPresent(out, args, name) {
|
|
|
569
814
|
if (hasFlag(args, name)) out.push(name);
|
|
570
815
|
}
|
|
571
816
|
|
|
572
|
-
function
|
|
573
|
-
const paths = requireMemberDir(name);
|
|
574
|
-
const owner = paths.storageName || name;
|
|
817
|
+
function buildMemberRunStartArgs(owner, missionText, args = [], cwd = process.cwd()) {
|
|
575
818
|
const startArgs = [
|
|
576
819
|
'mission',
|
|
577
820
|
'start',
|
|
578
821
|
missionText,
|
|
579
822
|
'--owner',
|
|
580
823
|
owner,
|
|
824
|
+
// codex_goal stalls unattended (no live codex session drives its native goal
|
|
825
|
+
// slot). Default to claude unless a live codex session is signalled; explicit
|
|
826
|
+
// --runner still wins. Proven footgun 2026-07-16. See lib/default-runner.js.
|
|
581
827
|
'--runner',
|
|
582
|
-
readFlag(args, '--runner',
|
|
828
|
+
readFlag(args, '--runner', defaultObjectiveRunner(args)),
|
|
583
829
|
'--lane',
|
|
584
830
|
readFlag(args, '--lane', 'workspace'),
|
|
585
831
|
];
|
|
@@ -591,14 +837,43 @@ function startMemberRunMission(name, missionText, args = []) {
|
|
|
591
837
|
pushFlagValue(startArgs, args, '--minutes');
|
|
592
838
|
pushFlagValue(startArgs, args, '--hours');
|
|
593
839
|
pushFlagValue(startArgs, args, '--base');
|
|
840
|
+
// A member-run mission is driven immediately by its runner, not parked on
|
|
841
|
+
// the queue — so the mission-start verifier gate (built for parked planning
|
|
842
|
+
// wishes) would only kill autonomous legs like autopilot's "member chooses
|
|
843
|
+
// useful work". When the caller named no verifier, opt out explicitly.
|
|
844
|
+
if (hasFlag(args, '--no-verify') || !readFlag(args, '--verify', '')) {
|
|
845
|
+
startArgs.push('--no-verify');
|
|
846
|
+
}
|
|
594
847
|
pushFlagWhenPresent(startArgs, args, '--always-on');
|
|
595
848
|
pushFlagWhenPresent(startArgs, args, '--xp-task');
|
|
596
849
|
pushFlagWhenPresent(startArgs, args, '--agent-xp');
|
|
597
850
|
pushFlagWhenPresent(startArgs, args, '--spend-full-budget');
|
|
598
851
|
pushFlagWhenPresent(startArgs, args, '--use-whole-budget');
|
|
599
852
|
pushFlagWhenPresent(startArgs, args, '--stop-when-done');
|
|
600
|
-
|
|
853
|
+
// Default to an isolated worktree only when there is a git repo to cut it
|
|
854
|
+
// from. A plain (non-git) workspace would fail every mission start with
|
|
855
|
+
// "fatal: not a git repository" — degrade to the shared checkout instead.
|
|
856
|
+
if (!hasFlag(args, '--shared-checkout') && !hasFlag(args, '--no-worktree') && insideGitRepo(cwd)) {
|
|
857
|
+
startArgs.push('--worktree');
|
|
858
|
+
}
|
|
601
859
|
if (hasFlag(args, '--json')) startArgs.push('--json');
|
|
860
|
+
return startArgs;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function insideGitRepo(startDir) {
|
|
864
|
+
let dir = path.resolve(startDir || process.cwd());
|
|
865
|
+
while (true) {
|
|
866
|
+
if (fs.existsSync(path.join(dir, '.git'))) return true;
|
|
867
|
+
const parent = path.dirname(dir);
|
|
868
|
+
if (parent === dir) return false;
|
|
869
|
+
dir = parent;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function startMemberRunMission(name, missionText, args = []) {
|
|
874
|
+
const paths = requireMemberDir(name);
|
|
875
|
+
const owner = paths.storageName || name;
|
|
876
|
+
const startArgs = buildMemberRunStartArgs(owner, missionText, args);
|
|
602
877
|
|
|
603
878
|
const cliPath = path.join(__dirname, '..', 'bin', 'atris.js');
|
|
604
879
|
try {
|
|
@@ -1439,14 +1714,43 @@ function readRecentWakeReceiptEvidence(name) {
|
|
|
1439
1714
|
}
|
|
1440
1715
|
|
|
1441
1716
|
function readJsonlRowsIfExists(filePath, maxRows = 80) {
|
|
1717
|
+
const tailBytes = 256 * 1024;
|
|
1718
|
+
let fd;
|
|
1442
1719
|
let text = '';
|
|
1720
|
+
let truncated = false;
|
|
1443
1721
|
try {
|
|
1444
|
-
|
|
1722
|
+
fd = fs.openSync(filePath, 'r');
|
|
1723
|
+
const stat = fs.fstatSync(fd);
|
|
1724
|
+
const bytesToRead = Math.min(stat.size, tailBytes);
|
|
1725
|
+
const start = Math.max(0, stat.size - bytesToRead);
|
|
1726
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
1727
|
+
let bytesRead = 0;
|
|
1728
|
+
while (bytesRead < bytesToRead) {
|
|
1729
|
+
const count = fs.readSync(
|
|
1730
|
+
fd,
|
|
1731
|
+
buffer,
|
|
1732
|
+
bytesRead,
|
|
1733
|
+
bytesToRead - bytesRead,
|
|
1734
|
+
start + bytesRead
|
|
1735
|
+
);
|
|
1736
|
+
if (count === 0) break;
|
|
1737
|
+
bytesRead += count;
|
|
1738
|
+
}
|
|
1739
|
+
text = buffer.toString('utf8', 0, bytesRead);
|
|
1740
|
+
truncated = start > 0;
|
|
1445
1741
|
} catch {
|
|
1446
1742
|
return [];
|
|
1743
|
+
} finally {
|
|
1744
|
+
if (fd !== undefined) {
|
|
1745
|
+
try { fs.closeSync(fd); } catch {}
|
|
1746
|
+
}
|
|
1447
1747
|
}
|
|
1448
1748
|
const rows = [];
|
|
1449
|
-
const lines = text
|
|
1749
|
+
const lines = text
|
|
1750
|
+
.split(/\r?\n/)
|
|
1751
|
+
.slice(truncated ? 1 : 0)
|
|
1752
|
+
.filter((line) => line.trim())
|
|
1753
|
+
.slice(-Math.max(1, maxRows));
|
|
1450
1754
|
for (const line of lines) {
|
|
1451
1755
|
try {
|
|
1452
1756
|
rows.push(JSON.parse(line));
|
|
@@ -2706,6 +3010,17 @@ function failureCoveredByPassLesson(line, passLessonText = '') {
|
|
|
2706
3010
|
return /spawnSync\s+\/bin\/sh\s+ETIMEDOUT/i.test(passLessonText)
|
|
2707
3011
|
&& (!target || passLessonText.includes(target) || (targetTail && passLessonText.includes(targetTail)));
|
|
2708
3012
|
}
|
|
3013
|
+
// General remediation channel: a pass lesson that quotes the failure's
|
|
3014
|
+
// normalized pattern marks it fixed. Old log lines stay on disk but stop
|
|
3015
|
+
// feeding the pain score, so pain can actually go down after a fix.
|
|
3016
|
+
const pattern = normalizeFailurePattern(text);
|
|
3017
|
+
const bare = pattern.replace(/^#+\s*[#:.]*\s*·\s*/, '').trim();
|
|
3018
|
+
for (const candidate of [pattern, bare]) {
|
|
3019
|
+
if (candidate && candidate.length >= 12
|
|
3020
|
+
&& passLessonText.toLowerCase().includes(candidate.toLowerCase())) {
|
|
3021
|
+
return true;
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
2709
3024
|
return false;
|
|
2710
3025
|
}
|
|
2711
3026
|
|
|
@@ -3540,8 +3855,8 @@ function findAllMembers(teamDir) {
|
|
|
3540
3855
|
const entries = fs.readdirSync(teamDir);
|
|
3541
3856
|
|
|
3542
3857
|
for (const entry of entries) {
|
|
3543
|
-
// Skip template
|
|
3544
|
-
if (entry === '_template' || entry.startsWith('.')) continue;
|
|
3858
|
+
// Skip template/archive directories and hidden files
|
|
3859
|
+
if (entry === '_template' || entry === '_archive' || entry === '_archived' || entry.startsWith('.')) continue;
|
|
3545
3860
|
|
|
3546
3861
|
const fullPath = path.join(teamDir, entry);
|
|
3547
3862
|
const stat = fs.statSync(fullPath);
|
|
@@ -3598,6 +3913,7 @@ function findAllMembers(teamDir) {
|
|
|
3598
3913
|
const content = fs.readFileSync(fullPath, 'utf8');
|
|
3599
3914
|
const fm = parseFrontmatter(content);
|
|
3600
3915
|
if (!fm) continue; // No frontmatter = not a member
|
|
3916
|
+
if (fm.type && fm.type !== 'member') continue; // Index/doc files (e.g. ASSIGNMENTS.md, type: index) are not members
|
|
3601
3917
|
|
|
3602
3918
|
const name = entry.replace('.md', '');
|
|
3603
3919
|
members.push({
|
|
@@ -6876,6 +7192,7 @@ function readMemberScope(name, paths) {
|
|
|
6876
7192
|
function isAutoGeneratedArtifact(filePath, name) {
|
|
6877
7193
|
const memberLogs = `atris/team/${name}/logs/`;
|
|
6878
7194
|
if (filePath.startsWith(memberLogs)) return true;
|
|
7195
|
+
if (filePath === `atris/team/${name}/goals.json`) return true;
|
|
6879
7196
|
if (filePath === `atris/team/${name}/now.md`) return true;
|
|
6880
7197
|
return false;
|
|
6881
7198
|
}
|
|
@@ -7404,6 +7721,127 @@ async function runMemberWake(name, { execute = false, confirmed = false, force =
|
|
|
7404
7721
|
};
|
|
7405
7722
|
}
|
|
7406
7723
|
|
|
7724
|
+
// --- Wake boot rendering: the human face of `gm <member>` ---
|
|
7725
|
+
// The JSON contract (atris.member_wake.v1) is untouched; this only changes what a person sees.
|
|
7726
|
+
|
|
7727
|
+
const WAKE_REASON_TEXT = {
|
|
7728
|
+
mission_missing_or_placeholder: "it has no North Star yet, and it won't guess at important work",
|
|
7729
|
+
no_active_goal: 'it has no active goal to push on yet',
|
|
7730
|
+
execute_requires_confirm_autonomy_policy: 'autonomous execution waits for your explicit go-ahead',
|
|
7731
|
+
workspace_dirty_in_member_scope: "there are uncommitted changes in its own lane — it won't build on a dirty floor",
|
|
7732
|
+
blocked_experiment: 'its current experiment is blocked on a human answer',
|
|
7733
|
+
tick_executed_experiment_proposed: 'it proposed one bounded experiment and queued it for your review',
|
|
7734
|
+
safe_next_bounded_step: 'it found a safe bounded next step',
|
|
7735
|
+
loop_already_active: 'another loop already holds the lease, so it stepped back instead of double-working',
|
|
7736
|
+
member_archived: 'this member is archived',
|
|
7737
|
+
llm_not_configured: 'no model is configured for autonomous reasoning here',
|
|
7738
|
+
insufficient_data: "it doesn't have enough evidence yet to act with confidence",
|
|
7739
|
+
proof_not_successful: 'the last proof did not pass, so it stopped instead of stacking work on a failure',
|
|
7740
|
+
auto_improver_task_create_failed: 'it found an improvement but could not put the task on the board',
|
|
7741
|
+
heuristic_cross_domain_proof_written: 'it wrote a cross-domain proof using its built-in heuristics',
|
|
7742
|
+
heuristic_objective_proposal_written: 'it drafted an objective proposal using its built-in heuristics',
|
|
7743
|
+
install_requires_clean_git: 'installing needs a clean git tree first',
|
|
7744
|
+
insufficient_world_model_data: 'its world model is too thin to act on yet',
|
|
7745
|
+
llm_json_parse_failed: 'the model reply did not parse, so it stopped rather than act on garbage',
|
|
7746
|
+
llm_json_parse_failed_heuristic_used: 'the model reply did not parse, so it fell back to built-in heuristics',
|
|
7747
|
+
missing_domain_input: 'it needs a domain file or domain text from you to work on',
|
|
7748
|
+
no_crontab: 'no crontab is available on this machine, so the loop cannot be scheduled',
|
|
7749
|
+
};
|
|
7750
|
+
|
|
7751
|
+
const WAKE_DECISION_TEXT = {
|
|
7752
|
+
ask: 'needs one thing from you',
|
|
7753
|
+
wait: 'decided to hold',
|
|
7754
|
+
tick: 'picked one bounded step',
|
|
7755
|
+
close_loop: 'is closing the loop',
|
|
7756
|
+
report_proof: 'has proof ready for you',
|
|
7757
|
+
create_missing_task: 'is putting the missing task on the board',
|
|
7758
|
+
create_task: 'is putting a new task on the board',
|
|
7759
|
+
stop: 'stopped on purpose',
|
|
7760
|
+
task_create_failed: 'tried to put a task on the board and could not',
|
|
7761
|
+
set_objective: 'set its own objective',
|
|
7762
|
+
generate_objective: 'drafted a new objective for review',
|
|
7763
|
+
supervise: 'reviewed the rest of the team',
|
|
7764
|
+
wiki_mine: 'is mining the wiki for its next move',
|
|
7765
|
+
process_domain_file: 'is digesting the domain file you gave it',
|
|
7766
|
+
cross_domain_generalize: 'is distilling patterns across domains',
|
|
7767
|
+
};
|
|
7768
|
+
|
|
7769
|
+
function wakeDecisionText(decision) {
|
|
7770
|
+
if (WAKE_DECISION_TEXT[decision]) return WAKE_DECISION_TEXT[decision];
|
|
7771
|
+
const words = String(decision || '').replace(/_/g, ' ').trim();
|
|
7772
|
+
return words ? `decided: ${words}` : 'decided nothing';
|
|
7773
|
+
}
|
|
7774
|
+
|
|
7775
|
+
function wakeStyle() {
|
|
7776
|
+
const on = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
7777
|
+
const wrap = (code) => (text) => (on ? `\x1b[${code}m${text}\x1b[0m` : String(text));
|
|
7778
|
+
return { bold: wrap('1'), dim: wrap('2'), cyan: wrap('36') };
|
|
7779
|
+
}
|
|
7780
|
+
|
|
7781
|
+
function wakeMemberRole(name) {
|
|
7782
|
+
try {
|
|
7783
|
+
const content = fs.readFileSync(memberPaths(name).memberFile, 'utf8');
|
|
7784
|
+
const fm = parseFrontmatter(content);
|
|
7785
|
+
return (fm && fm.role) || null;
|
|
7786
|
+
} catch {
|
|
7787
|
+
return null;
|
|
7788
|
+
}
|
|
7789
|
+
}
|
|
7790
|
+
|
|
7791
|
+
function wakeReasonText(reason) {
|
|
7792
|
+
if (WAKE_REASON_TEXT[reason]) return WAKE_REASON_TEXT[reason];
|
|
7793
|
+
if (reason === 'open_experiment_proposed') return 'it already has an experiment on the table, waiting for your review';
|
|
7794
|
+
if (reason === 'open_experiment_running') return 'its current experiment is still running';
|
|
7795
|
+
const openExperiment = String(reason || '').match(/^open_experiment_([a-z_]+)$/);
|
|
7796
|
+
if (openExperiment) return `its current experiment is ${openExperiment[1].replace(/_/g, ' ')}`;
|
|
7797
|
+
return String(reason || '').replace(/_/g, ' ');
|
|
7798
|
+
}
|
|
7799
|
+
|
|
7800
|
+
function wakeBootLines(name, result) {
|
|
7801
|
+
const s = wakeStyle();
|
|
7802
|
+
const role = wakeMemberRole(name);
|
|
7803
|
+
const decisionText = wakeDecisionText(result.decision);
|
|
7804
|
+
const goal = result.active_goal && result.active_goal.title;
|
|
7805
|
+
const experiment = result.current_experiment && result.current_experiment.title;
|
|
7806
|
+
const northStar = result.mission && result.mission.north_star;
|
|
7807
|
+
const laneCandidates = result.evidence && result.evidence.task_projection
|
|
7808
|
+
? result.evidence.task_projection.candidate_count
|
|
7809
|
+
: null;
|
|
7810
|
+
|
|
7811
|
+
const lines = [
|
|
7812
|
+
'',
|
|
7813
|
+
`${s.cyan('\u25c8')} ${s.bold(name)} is waking up${role ? ` ${s.dim(`\u00b7 ${role}`)}` : ''}`,
|
|
7814
|
+
'',
|
|
7815
|
+
` mission ${northStar ? clipText(northStar, 70) : s.dim('no North Star yet')}`,
|
|
7816
|
+
` goal ${goal ? clipText(goal, 70) : s.dim('none yet')}`,
|
|
7817
|
+
...(experiment ? [` working ${clipText(experiment, 70)}`] : []),
|
|
7818
|
+
...(laneCandidates !== null ? [` lane ${laneCandidates} task${laneCandidates === 1 ? '' : 's'} waiting in ${name}'s lane`] : []),
|
|
7819
|
+
'',
|
|
7820
|
+
` ${s.bold(name)} looked around and ${s.bold(decisionText)}.`,
|
|
7821
|
+
` ${s.dim(`Why: ${wakeReasonText(result.reason)}.`)}`,
|
|
7822
|
+
];
|
|
7823
|
+
|
|
7824
|
+
if (result.created_task && result.created_task.title) {
|
|
7825
|
+
lines.push('', ` On the board: ${result.created_task.display_id || result.created_task.legacy_ref || ''} ${clipText(result.created_task.title, 70)}`.trimEnd());
|
|
7826
|
+
}
|
|
7827
|
+
if (result.ask) {
|
|
7828
|
+
lines.push('', ` ${s.bold('One thing from you:')}`, ` ${result.ask}`);
|
|
7829
|
+
}
|
|
7830
|
+
if (result.next_command) {
|
|
7831
|
+
const ballWithHuman = result.needs_user === true
|
|
7832
|
+
|| result.decision === 'ask'
|
|
7833
|
+
|| /^open_experiment_/.test(result.reason || '');
|
|
7834
|
+
lines.push('', ` ${s.bold(ballWithHuman ? 'Your move' : 'Next step')}`, ` ${s.cyan(result.next_command)}`);
|
|
7835
|
+
}
|
|
7836
|
+
lines.push('', ` ${s.dim(`receipt \u00b7 ${path.relative(process.cwd(), result.receipt_path)}`)}`, '');
|
|
7837
|
+
return lines;
|
|
7838
|
+
}
|
|
7839
|
+
|
|
7840
|
+
function clipText(value, max = 70) {
|
|
7841
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
7842
|
+
return text.length <= max ? text : `${text.slice(0, Math.max(0, max - 1)).trim()}\u2026`;
|
|
7843
|
+
}
|
|
7844
|
+
|
|
7407
7845
|
async function memberWake(name, ...args) {
|
|
7408
7846
|
const asJson = hasFlag(args, '--json');
|
|
7409
7847
|
const execute = hasFlag(args, '--execute') && !hasFlag(args, '--dry-run');
|
|
@@ -7414,28 +7852,127 @@ async function memberWake(name, ...args) {
|
|
|
7414
7852
|
file: readFlag(args, '--domain-file', ''),
|
|
7415
7853
|
name: readFlag(args, '--domain-name', ''),
|
|
7416
7854
|
};
|
|
7855
|
+
// Honor per-member sleep switch before any tick dispatch.
|
|
7856
|
+
try {
|
|
7857
|
+
const { isMemberAwake } = require('../lib/member-switches');
|
|
7858
|
+
if (name && !isMemberAwake(name)) {
|
|
7859
|
+
const skipped = {
|
|
7860
|
+
ok: true,
|
|
7861
|
+
skipped: true,
|
|
7862
|
+
reason: 'asleep',
|
|
7863
|
+
member: name,
|
|
7864
|
+
decision: 'stop',
|
|
7865
|
+
};
|
|
7866
|
+
printJsonOrText(skipped, [`${name} is asleep`], asJson);
|
|
7867
|
+
return skipped;
|
|
7868
|
+
}
|
|
7869
|
+
} catch {
|
|
7870
|
+
// switch store unreadable: fail open and continue wake
|
|
7871
|
+
}
|
|
7417
7872
|
const result = await runMemberWake(name, { execute, confirmed, force, domainInput });
|
|
7418
|
-
printJsonOrText(
|
|
7419
|
-
result,
|
|
7420
|
-
[
|
|
7421
|
-
`Wake: ${name}`,
|
|
7422
|
-
`Decision: ${result.decision}`,
|
|
7423
|
-
`Reason: ${result.reason}`,
|
|
7424
|
-
`Mode: ${result.mode}${result.executed ? ' executed' : ''}`,
|
|
7425
|
-
...(result.ask ? [`Ask: ${result.ask}`] : []),
|
|
7426
|
-
`Next: ${result.next_command}`,
|
|
7427
|
-
`Receipt: ${path.relative(process.cwd(), result.receipt_path)}`,
|
|
7428
|
-
],
|
|
7429
|
-
asJson,
|
|
7430
|
-
);
|
|
7873
|
+
printJsonOrText(result, wakeBootLines(name, result), asJson);
|
|
7431
7874
|
}
|
|
7432
7875
|
|
|
7433
7876
|
function memberAlive(name, ...args) {
|
|
7877
|
+
if (name === '--all' || name === 'all') return memberAliveAll(...args);
|
|
7434
7878
|
const nextArgs = args.includes('--alive') ? args : [...args, '--alive'];
|
|
7435
7879
|
return memberLoop(name, ...nextArgs);
|
|
7436
7880
|
}
|
|
7437
7881
|
|
|
7882
|
+
function collectFleetActivationRows(root = process.cwd()) {
|
|
7883
|
+
const teamDir = path.join(root, 'atris', 'team');
|
|
7884
|
+
const missionMap = memberRunMissionMap();
|
|
7885
|
+
return findAllMembers(teamDir)
|
|
7886
|
+
.filter(isFleetMember)
|
|
7887
|
+
.map((member) => {
|
|
7888
|
+
const slug = memberSlugFromRecord(member);
|
|
7889
|
+
const paths = memberPaths(slug);
|
|
7890
|
+
const goalPlane = memberGoalPlaneStatus(slug, paths);
|
|
7891
|
+
const activeMission = memberLiveMission(slug, missionMap);
|
|
7892
|
+
const latestMission = memberLatestMission(slug, missionMap);
|
|
7893
|
+
const skipped = !goalPlane.goal && !activeMission;
|
|
7894
|
+
return {
|
|
7895
|
+
member: slug,
|
|
7896
|
+
goal: goalPlane.goal || null,
|
|
7897
|
+
active_mission: activeMission || null,
|
|
7898
|
+
latest_mission: latestMission || null,
|
|
7899
|
+
skipped,
|
|
7900
|
+
skip_reason: skipped ? 'no_goal_and_no_active_mission' : null,
|
|
7901
|
+
};
|
|
7902
|
+
})
|
|
7903
|
+
.sort((a, b) => a.member.localeCompare(b.member));
|
|
7904
|
+
}
|
|
7905
|
+
|
|
7906
|
+
async function callMemberLoopForFleet(kind, member, args, captureOutput) {
|
|
7907
|
+
if (!captureOutput) {
|
|
7908
|
+
if (kind === 'alive') return memberAlive(member, ...args);
|
|
7909
|
+
return memberLoop(member, ...args);
|
|
7910
|
+
}
|
|
7911
|
+
const captured = [];
|
|
7912
|
+
const originalLog = console.log;
|
|
7913
|
+
console.log = (...parts) => captured.push(parts.join(' '));
|
|
7914
|
+
try {
|
|
7915
|
+
const result = kind === 'alive'
|
|
7916
|
+
? await memberAlive(member, ...args)
|
|
7917
|
+
: await memberLoop(member, ...args);
|
|
7918
|
+
return { result, output: captured };
|
|
7919
|
+
} finally {
|
|
7920
|
+
console.log = originalLog;
|
|
7921
|
+
}
|
|
7922
|
+
}
|
|
7923
|
+
|
|
7924
|
+
async function memberLoopAll(kind, ...args) {
|
|
7925
|
+
const asJson = hasFlag(args, '--json');
|
|
7926
|
+
const rows = collectFleetActivationRows();
|
|
7927
|
+
const activated = [];
|
|
7928
|
+
const skipped = [];
|
|
7929
|
+
if (!asJson) console.log(`Member fleet ${kind}`);
|
|
7930
|
+
for (const row of rows) {
|
|
7931
|
+
if (row.skipped) {
|
|
7932
|
+
skipped.push(row);
|
|
7933
|
+
if (!asJson) console.log(`Skip ${row.member}: no goal and no active mission.`);
|
|
7934
|
+
continue;
|
|
7935
|
+
}
|
|
7936
|
+
if (!asJson) console.log(`Activate ${row.member}: ${row.goal ? 'goal' : `mission ${row.active_mission?.id || 'active'}`}`);
|
|
7937
|
+
const result = await callMemberLoopForFleet(kind, row.member, args, asJson);
|
|
7938
|
+
activated.push({
|
|
7939
|
+
member: row.member,
|
|
7940
|
+
reason: row.goal ? 'has_goal' : 'has_active_mission',
|
|
7941
|
+
mission_id: row.active_mission?.id || null,
|
|
7942
|
+
result: asJson ? result?.result || null : null,
|
|
7943
|
+
output: asJson ? result?.output || [] : undefined,
|
|
7944
|
+
});
|
|
7945
|
+
}
|
|
7946
|
+
const reasonCounts = skipped.reduce((acc, row) => {
|
|
7947
|
+
acc[row.skip_reason] = (acc[row.skip_reason] || 0) + 1;
|
|
7948
|
+
return acc;
|
|
7949
|
+
}, {});
|
|
7950
|
+
const payload = {
|
|
7951
|
+
ok: true,
|
|
7952
|
+
action: `${kind}_all`,
|
|
7953
|
+
activated: activated.length,
|
|
7954
|
+
skipped: skipped.length,
|
|
7955
|
+
skipped_reasons: reasonCounts,
|
|
7956
|
+
activated_members: activated,
|
|
7957
|
+
skipped_members: skipped.map((row) => ({
|
|
7958
|
+
member: row.member,
|
|
7959
|
+
reason: row.skip_reason,
|
|
7960
|
+
latest_mission_id: row.latest_mission?.id || null,
|
|
7961
|
+
latest_mission_status: row.latest_mission?.status || null,
|
|
7962
|
+
})),
|
|
7963
|
+
};
|
|
7964
|
+
printJsonOrText(payload, [
|
|
7965
|
+
`Summary: ${activated.length} activated, ${skipped.length} skipped (${Object.entries(reasonCounts).map(([reason, count]) => `${reason} x${count}`).join(', ') || 'none'}).`,
|
|
7966
|
+
], asJson);
|
|
7967
|
+
return payload;
|
|
7968
|
+
}
|
|
7969
|
+
|
|
7970
|
+
function memberAliveAll(...args) {
|
|
7971
|
+
return memberLoopAll('alive', ...args);
|
|
7972
|
+
}
|
|
7973
|
+
|
|
7438
7974
|
async function memberLoop(name, ...args) {
|
|
7975
|
+
if (name === '--all' || name === 'all') return memberLoopAll('loop', ...args);
|
|
7439
7976
|
requireMemberDir(name);
|
|
7440
7977
|
const asJson = hasFlag(args, '--json');
|
|
7441
7978
|
const aliveMode = hasFlag(args, '--alive');
|
|
@@ -7615,6 +8152,13 @@ async function memberLoop(name, ...args) {
|
|
|
7615
8152
|
let earlyExit = null;
|
|
7616
8153
|
let consecutiveIdle = 0;
|
|
7617
8154
|
const idleBreakThreshold = 2;
|
|
8155
|
+
// Repeated identical `ask:*` wake decisions (e.g. ask:mission_missing_or_placeholder) mean the
|
|
8156
|
+
// member is stuck asking the same question every tick regardless of --execute; the idle
|
|
8157
|
+
// early-exit above only fires in execute mode, so without this a dry-run loop burns every
|
|
8158
|
+
// remaining tick re-asking. Reuse the same blocked_on_human handoff once we see the ask repeat.
|
|
8159
|
+
let lastAskKey = null;
|
|
8160
|
+
let consecutiveIdenticalAsk = 0;
|
|
8161
|
+
const identicalAskBreakThreshold = 2;
|
|
7618
8162
|
|
|
7619
8163
|
try {
|
|
7620
8164
|
for (let index = 0; index < ticks; index += 1) {
|
|
@@ -7686,6 +8230,29 @@ async function memberLoop(name, ...args) {
|
|
|
7686
8230
|
};
|
|
7687
8231
|
tickResults.push(tick);
|
|
7688
8232
|
fs.appendFileSync(tickLogPath, JSON.stringify(tick) + '\n', 'utf8');
|
|
8233
|
+
|
|
8234
|
+
if (wake.decision === 'ask') {
|
|
8235
|
+
if (key === lastAskKey) {
|
|
8236
|
+
consecutiveIdenticalAsk += 1;
|
|
8237
|
+
} else {
|
|
8238
|
+
lastAskKey = key;
|
|
8239
|
+
consecutiveIdenticalAsk = 1;
|
|
8240
|
+
}
|
|
8241
|
+
if (consecutiveIdenticalAsk >= identicalAskBreakThreshold) {
|
|
8242
|
+
earlyExit = {
|
|
8243
|
+
after_tick: tick.tick,
|
|
8244
|
+
decision: tick.decision || null,
|
|
8245
|
+
reason: tick.reason || 'idle',
|
|
8246
|
+
needs_user: tick.needs_user === true,
|
|
8247
|
+
blocked_on_human: true,
|
|
8248
|
+
next_command: tick.next_command || null,
|
|
8249
|
+
stop: 'repeated_identical_ask',
|
|
8250
|
+
};
|
|
8251
|
+
}
|
|
8252
|
+
} else {
|
|
8253
|
+
lastAskKey = null;
|
|
8254
|
+
consecutiveIdenticalAsk = 0;
|
|
8255
|
+
}
|
|
7689
8256
|
}
|
|
7690
8257
|
} catch (error) {
|
|
7691
8258
|
failed = true;
|
|
@@ -7700,6 +8267,7 @@ async function memberLoop(name, ...args) {
|
|
|
7700
8267
|
fs.appendFileSync(tickLogPath, JSON.stringify(tick) + '\n', 'utf8');
|
|
7701
8268
|
break;
|
|
7702
8269
|
}
|
|
8270
|
+
if (earlyExit) break;
|
|
7703
8271
|
if (execute) {
|
|
7704
8272
|
const last = tickResults[tickResults.length - 1];
|
|
7705
8273
|
if (last && last.productive) {
|
|
@@ -7748,6 +8316,7 @@ async function memberLoop(name, ...args) {
|
|
|
7748
8316
|
duration_ms_actual: Date.parse(finishedAt) - Date.parse(startedAt),
|
|
7749
8317
|
decisions,
|
|
7750
8318
|
early_exit: earlyExit,
|
|
8319
|
+
stop: earlyExit?.stop || null,
|
|
7751
8320
|
blocked_on_human: earlyExit?.blocked_on_human === true,
|
|
7752
8321
|
needs_user: earlyExit?.needs_user === true,
|
|
7753
8322
|
next_command: earlyExit?.next_command || null,
|
|
@@ -7962,58 +8531,173 @@ function memberBlock(name, experimentId, ...args) {
|
|
|
7962
8531
|
);
|
|
7963
8532
|
}
|
|
7964
8533
|
|
|
8534
|
+
function collectFleetMemberStatus(root = process.cwd()) {
|
|
8535
|
+
const teamDir = path.join(root, 'atris', 'team');
|
|
8536
|
+
const missionMap = memberRunMissionMap();
|
|
8537
|
+
return findAllMembers(teamDir)
|
|
8538
|
+
.filter(isFleetMember)
|
|
8539
|
+
.map((member) => {
|
|
8540
|
+
const slug = memberSlugFromRecord(member);
|
|
8541
|
+
const paths = memberPaths(slug);
|
|
8542
|
+
const goalPlane = memberGoalPlaneStatus(slug, paths);
|
|
8543
|
+
const mission = memberMissionSummary(slug, missionMap);
|
|
8544
|
+
const activity = memberLastActivity(slug, paths, root);
|
|
8545
|
+
const verdict = memberVerdict({ mission, activity });
|
|
8546
|
+
return {
|
|
8547
|
+
member: slug,
|
|
8548
|
+
role: member.role,
|
|
8549
|
+
goal_state: goalPlane.stateLabel,
|
|
8550
|
+
goal_title: goalPlane.goal?.title || null,
|
|
8551
|
+
mission: mission.latest ? {
|
|
8552
|
+
id: mission.latest.id || null,
|
|
8553
|
+
name: mission.name,
|
|
8554
|
+
state: mission.state,
|
|
8555
|
+
last_tick_at: mission.last_tick_at,
|
|
8556
|
+
last_tick_age: mission.last_tick_age,
|
|
8557
|
+
live: mission.live_mission,
|
|
8558
|
+
} : null,
|
|
8559
|
+
last_activity: activity,
|
|
8560
|
+
verdict,
|
|
8561
|
+
};
|
|
8562
|
+
})
|
|
8563
|
+
.sort((a, b) => a.member.localeCompare(b.member));
|
|
8564
|
+
}
|
|
8565
|
+
|
|
8566
|
+
function clipCell(value, width) {
|
|
8567
|
+
const clean = String(value || '-').replace(/\s+/g, ' ').trim() || '-';
|
|
8568
|
+
if (clean.length <= width) return clean.padEnd(width);
|
|
8569
|
+
if (width <= 3) return clean.slice(0, width);
|
|
8570
|
+
return `${clean.slice(0, width - 3)}...`;
|
|
8571
|
+
}
|
|
8572
|
+
|
|
8573
|
+
function renderMemberFleetStatusTable(rows) {
|
|
8574
|
+
if (!rows.length) return ['No active team members found in atris/team/.'];
|
|
8575
|
+
const widths = {
|
|
8576
|
+
member: 18,
|
|
8577
|
+
goal: 11,
|
|
8578
|
+
mission: 34,
|
|
8579
|
+
state: 10,
|
|
8580
|
+
tick: 8,
|
|
8581
|
+
activity: 8,
|
|
8582
|
+
verdict: 8,
|
|
8583
|
+
};
|
|
8584
|
+
const header = [
|
|
8585
|
+
clipCell('Member', widths.member),
|
|
8586
|
+
clipCell('Goal', widths.goal),
|
|
8587
|
+
clipCell('Mission', widths.mission),
|
|
8588
|
+
clipCell('State', widths.state),
|
|
8589
|
+
clipCell('Tick', widths.tick),
|
|
8590
|
+
clipCell('Activity', widths.activity),
|
|
8591
|
+
clipCell('Verdict', widths.verdict),
|
|
8592
|
+
].join(' ');
|
|
8593
|
+
const rule = [
|
|
8594
|
+
'-'.repeat(widths.member),
|
|
8595
|
+
'-'.repeat(widths.goal),
|
|
8596
|
+
'-'.repeat(widths.mission),
|
|
8597
|
+
'-'.repeat(widths.state),
|
|
8598
|
+
'-'.repeat(widths.tick),
|
|
8599
|
+
'-'.repeat(widths.activity),
|
|
8600
|
+
'-'.repeat(widths.verdict),
|
|
8601
|
+
].join(' ');
|
|
8602
|
+
return [
|
|
8603
|
+
'Member fleet status',
|
|
8604
|
+
header,
|
|
8605
|
+
rule,
|
|
8606
|
+
...rows.map((row) => [
|
|
8607
|
+
clipCell(row.member, widths.member),
|
|
8608
|
+
clipCell(row.goal_state, widths.goal),
|
|
8609
|
+
clipCell(row.mission?.name || '-', widths.mission),
|
|
8610
|
+
clipCell(row.mission?.state || '-', widths.state),
|
|
8611
|
+
clipCell(row.mission?.last_tick_age || '-', widths.tick),
|
|
8612
|
+
clipCell(row.last_activity?.age || '-', widths.activity),
|
|
8613
|
+
clipCell(row.verdict, widths.verdict),
|
|
8614
|
+
].join(' ')),
|
|
8615
|
+
];
|
|
8616
|
+
}
|
|
8617
|
+
|
|
8618
|
+
function memberStatusAll(...args) {
|
|
8619
|
+
const asJson = hasFlag(args, '--json');
|
|
8620
|
+
const rows = collectFleetMemberStatus();
|
|
8621
|
+
printJsonOrText(
|
|
8622
|
+
{
|
|
8623
|
+
ok: true,
|
|
8624
|
+
action: 'status_all',
|
|
8625
|
+
members: rows,
|
|
8626
|
+
},
|
|
8627
|
+
renderMemberFleetStatusTable(rows),
|
|
8628
|
+
asJson,
|
|
8629
|
+
);
|
|
8630
|
+
}
|
|
8631
|
+
|
|
7965
8632
|
function memberStatus(name, ...args) {
|
|
8633
|
+
if (name === '--all' || name === 'all') return memberStatusAll(...args);
|
|
7966
8634
|
const paths = requireMemberDir(name);
|
|
7967
8635
|
const asJson = hasFlag(args, '--json');
|
|
7968
|
-
const
|
|
7969
|
-
const
|
|
7970
|
-
const
|
|
7971
|
-
const
|
|
7972
|
-
const
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
const
|
|
7983
|
-
const nextCommand = !goal
|
|
7984
|
-
? `atris member goal ${name} "..."`
|
|
7985
|
-
: needsUser
|
|
7986
|
-
? `atris member review ${name} ${current.id} --discard --proof "..."`
|
|
7987
|
-
: current
|
|
7988
|
-
? `atris member review ${name} ${current.id} --accept --proof "..." --value 4`
|
|
7989
|
-
: `atris member tick ${name}`;
|
|
8636
|
+
const goalPlane = memberGoalPlaneStatus(name, paths);
|
|
8637
|
+
const owner = paths.storageName || name;
|
|
8638
|
+
const mission = memberMissionSummary(owner, memberRunMissionMap());
|
|
8639
|
+
const activity = memberLastActivity(owner, paths);
|
|
8640
|
+
const verdict = memberVerdict({ mission, activity });
|
|
8641
|
+
let memberSwitch = { awake: true, loops: {} };
|
|
8642
|
+
try {
|
|
8643
|
+
const { getMemberSwitch } = require('../lib/member-switches');
|
|
8644
|
+
memberSwitch = getMemberSwitch(name);
|
|
8645
|
+
} catch {
|
|
8646
|
+
// default awake
|
|
8647
|
+
}
|
|
8648
|
+
const loopEntries = Object.entries(memberSwitch.loops || {})
|
|
8649
|
+
.map(([id, on]) => `${id}:${on === false ? 'asleep' : 'awake'}`);
|
|
8650
|
+
const switchLabel = memberSwitch.awake === false ? 'asleep' : 'awake';
|
|
7990
8651
|
const payload = {
|
|
7991
8652
|
ok: true,
|
|
7992
8653
|
action: 'status',
|
|
7993
8654
|
member: name,
|
|
7994
|
-
state: stateLabel,
|
|
7995
|
-
needs_user: needsUser,
|
|
7996
|
-
ask,
|
|
7997
|
-
active_goal: goal || null,
|
|
7998
|
-
current_experiment: current || null,
|
|
7999
|
-
last_reviewed: lastReviewed || null,
|
|
8000
|
-
value,
|
|
8001
|
-
|
|
8002
|
-
|
|
8655
|
+
state: goalPlane.stateLabel,
|
|
8656
|
+
needs_user: goalPlane.needsUser,
|
|
8657
|
+
ask: goalPlane.ask,
|
|
8658
|
+
active_goal: goalPlane.goal || null,
|
|
8659
|
+
current_experiment: goalPlane.current || null,
|
|
8660
|
+
last_reviewed: goalPlane.lastReviewed || null,
|
|
8661
|
+
value: goalPlane.value,
|
|
8662
|
+
switch: {
|
|
8663
|
+
awake: memberSwitch.awake !== false,
|
|
8664
|
+
loops: memberSwitch.loops || {},
|
|
8665
|
+
},
|
|
8666
|
+
mission: mission.latest ? {
|
|
8667
|
+
id: mission.latest.id || null,
|
|
8668
|
+
name: mission.name,
|
|
8669
|
+
state: mission.state,
|
|
8670
|
+
last_tick_at: mission.last_tick_at,
|
|
8671
|
+
last_tick_age: mission.last_tick_age,
|
|
8672
|
+
live: mission.live_mission,
|
|
8673
|
+
} : null,
|
|
8674
|
+
last_activity: activity,
|
|
8675
|
+
verdict,
|
|
8676
|
+
next_command: goalPlane.nextCommand,
|
|
8677
|
+
recent_log: goalPlane.logs,
|
|
8003
8678
|
goals_path: paths.goalsJson,
|
|
8004
8679
|
goals_md_path: paths.goalsMd,
|
|
8005
8680
|
};
|
|
8681
|
+
const s = wakeStyle();
|
|
8682
|
+
const role = wakeMemberRole(name);
|
|
8683
|
+
const ballWithHuman = goalPlane.needsUser === true || goalPlane.current?.status === 'proposed';
|
|
8006
8684
|
printJsonOrText(
|
|
8007
8685
|
payload,
|
|
8008
8686
|
[
|
|
8009
|
-
|
|
8010
|
-
`
|
|
8011
|
-
|
|
8012
|
-
`
|
|
8013
|
-
|
|
8014
|
-
`
|
|
8015
|
-
`
|
|
8016
|
-
|
|
8687
|
+
'',
|
|
8688
|
+
`${s.cyan('\u25c8')} ${s.bold(name)}${role ? ` ${s.dim(`\u00b7 ${role}`)}` : ''} ${s.dim(`\u00b7 ${verdict.toLowerCase()}, last activity ${activity.age}`)}`,
|
|
8689
|
+
'',
|
|
8690
|
+
` goal ${goalPlane.goal?.title ? clipText(goalPlane.goal.title, 70) : s.dim('none yet')}`,
|
|
8691
|
+
` working ${goalPlane.current ? `${clipText(goalPlane.current.title, 60)} ${s.dim(`(${goalPlane.current.status})`)}` : s.dim('no open experiment')}`,
|
|
8692
|
+
` value ${goalPlane.value.line}`,
|
|
8693
|
+
` switch ${switchLabel}${loopEntries.length ? ` ${s.dim(`(${loopEntries.join(', ')})`)}` : ''}`,
|
|
8694
|
+
` mission ${mission.latest ? `${mission.name} ${s.dim(`(${mission.state}, last tick ${mission.last_tick_age})`)}` : s.dim('none owned')}`,
|
|
8695
|
+
...(goalPlane.ask ? ['', ` ${s.bold('One thing from you:')}`, ` ${goalPlane.ask}`] : []),
|
|
8696
|
+
'',
|
|
8697
|
+
` ${s.bold(ballWithHuman ? 'Your move' : 'Next step')}`,
|
|
8698
|
+
` ${s.cyan(goalPlane.nextCommand)}`,
|
|
8699
|
+
...(goalPlane.logs.length ? ['', ` ${s.dim('recent')}`, ...goalPlane.logs.map((line) => ` ${s.dim(line)}`)] : []),
|
|
8700
|
+
'',
|
|
8017
8701
|
],
|
|
8018
8702
|
asJson,
|
|
8019
8703
|
);
|
|
@@ -8107,6 +8791,143 @@ function memberHistory(name, ...args) {
|
|
|
8107
8791
|
}
|
|
8108
8792
|
}
|
|
8109
8793
|
|
|
8794
|
+
// --- Live backend status (heartbeat + projection) ---
|
|
8795
|
+
|
|
8796
|
+
function parseLiveFlags(args) {
|
|
8797
|
+
const flags = { scope: 'personal', scopeId: null, json: false, state: null, note: null, days: null };
|
|
8798
|
+
const positional = [];
|
|
8799
|
+
for (let i = 0; i < args.length; i++) {
|
|
8800
|
+
const a = args[i];
|
|
8801
|
+
if (a === '--json') flags.json = true;
|
|
8802
|
+
else if (a === '--done') flags.state = 'done';
|
|
8803
|
+
else if (a === '--error') flags.state = 'error';
|
|
8804
|
+
else if (a === '--running') flags.state = 'running';
|
|
8805
|
+
else if (a === '--scope') flags.scope = args[++i];
|
|
8806
|
+
else if (a.startsWith('--scope=')) flags.scope = a.slice(8);
|
|
8807
|
+
else if (a === '--scope-id') flags.scopeId = args[++i];
|
|
8808
|
+
else if (a.startsWith('--scope-id=')) flags.scopeId = a.slice(11);
|
|
8809
|
+
else if (a === '--note' || a === '--on') flags.note = args[++i];
|
|
8810
|
+
else if (a.startsWith('--note=')) flags.note = a.slice(7);
|
|
8811
|
+
else if (a === '--days') flags.days = parseInt(args[++i], 10);
|
|
8812
|
+
else if (a.startsWith('--days=')) flags.days = parseInt(a.slice(7), 10);
|
|
8813
|
+
else positional.push(a);
|
|
8814
|
+
}
|
|
8815
|
+
return { flags, positional };
|
|
8816
|
+
}
|
|
8817
|
+
|
|
8818
|
+
function memberScopeQuery(flags) {
|
|
8819
|
+
let q = `scope=${encodeURIComponent(flags.scope)}`;
|
|
8820
|
+
if (flags.scopeId) q += `&scope_id=${encodeURIComponent(flags.scopeId)}`;
|
|
8821
|
+
return q;
|
|
8822
|
+
}
|
|
8823
|
+
|
|
8824
|
+
// atris member live — what the iPhone app and web see: GET /api/members projection.
|
|
8825
|
+
async function memberLive(...args) {
|
|
8826
|
+
const { flags } = parseLiveFlags(args);
|
|
8827
|
+
const creds = loadCredentials();
|
|
8828
|
+
if (!creds || !creds.token) {
|
|
8829
|
+
console.error('Not logged in. Run: atris login');
|
|
8830
|
+
process.exit(1);
|
|
8831
|
+
}
|
|
8832
|
+
const result = await apiRequestJson(`/members?${memberScopeQuery(flags)}`, { token: creds.token });
|
|
8833
|
+
if (!result.ok) {
|
|
8834
|
+
console.error(`Failed to fetch members: ${result.error || 'Unknown error'}`);
|
|
8835
|
+
process.exit(1);
|
|
8836
|
+
}
|
|
8837
|
+
const members = (result.data?.members || result.data || []).filter((m) => m && m.member_type === 'ai');
|
|
8838
|
+
if (flags.json) {
|
|
8839
|
+
console.log(JSON.stringify(members, null, 2));
|
|
8840
|
+
return;
|
|
8841
|
+
}
|
|
8842
|
+
console.log('');
|
|
8843
|
+
console.log(`live members (${flags.scope}${flags.scopeId ? `:${flags.scopeId}` : ''}) — same projection the app renders`);
|
|
8844
|
+
console.log('');
|
|
8845
|
+
for (const m of members) {
|
|
8846
|
+
const loop = m.loop_status || {};
|
|
8847
|
+
const dot = loop.running ? '●' : '○';
|
|
8848
|
+
const state = loop.state || m.status || '-';
|
|
8849
|
+
const workingOn = loop.working_on || loop.now || '';
|
|
8850
|
+
const lastActive = loop.last_active || '';
|
|
8851
|
+
console.log(` ${dot} ${m.display_name} [${state}]${workingOn ? ` ${workingOn}` : ''}${lastActive ? ` (last active ${lastActive})` : ''}`);
|
|
8852
|
+
}
|
|
8853
|
+
if (members.length === 0) console.log(' (no AI members in this scope)');
|
|
8854
|
+
console.log('');
|
|
8855
|
+
}
|
|
8856
|
+
|
|
8857
|
+
// atris member heartbeat — tell the backend this member is running/done, so
|
|
8858
|
+
// every surface (iOS, web) shows it live. POST /api/members/{name}/heartbeat.
|
|
8859
|
+
async function memberHeartbeat(name, ...args) {
|
|
8860
|
+
if (!name) {
|
|
8861
|
+
console.error('Usage: atris member heartbeat <name> [--note "what it is doing"] [--done|--error] [--scope business --scope-id <id>] [--json]');
|
|
8862
|
+
process.exit(1);
|
|
8863
|
+
}
|
|
8864
|
+
const { flags } = parseLiveFlags(args);
|
|
8865
|
+
const creds = loadCredentials();
|
|
8866
|
+
if (!creds || !creds.token) {
|
|
8867
|
+
console.error('Not logged in. Run: atris login');
|
|
8868
|
+
process.exit(1);
|
|
8869
|
+
}
|
|
8870
|
+
const body = { state: flags.state || 'running', scope: flags.scope };
|
|
8871
|
+
if (flags.note) body.working_on = flags.note;
|
|
8872
|
+
if (flags.scopeId) body.scope_id = flags.scopeId;
|
|
8873
|
+
const result = await apiRequestJson(`/members/${encodeURIComponent(name)}/heartbeat`, {
|
|
8874
|
+
method: 'POST',
|
|
8875
|
+
body: JSON.stringify(body),
|
|
8876
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8877
|
+
token: creds.token,
|
|
8878
|
+
});
|
|
8879
|
+
if (!result.ok) {
|
|
8880
|
+
console.error(`Heartbeat failed: ${result.error || 'Unknown error'}`);
|
|
8881
|
+
process.exit(1);
|
|
8882
|
+
}
|
|
8883
|
+
if (flags.json) {
|
|
8884
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
8885
|
+
return;
|
|
8886
|
+
}
|
|
8887
|
+
const d = result.data || {};
|
|
8888
|
+
console.log(`${d.running ? '●' : '○'} ${d.member || name} → ${d.state}${d.working_on ? ` — ${d.working_on}` : ''} (visible on every surface)`);
|
|
8889
|
+
}
|
|
8890
|
+
|
|
8891
|
+
// atris member improvements — what the business computer improved, day by day.
|
|
8892
|
+
// GET /api/business/{id}/improvements/daily.
|
|
8893
|
+
async function memberImprovements(businessId, ...args) {
|
|
8894
|
+
if (!businessId) {
|
|
8895
|
+
console.error('Usage: atris member improvements <business-id> [--days N] [--json]');
|
|
8896
|
+
process.exit(1);
|
|
8897
|
+
}
|
|
8898
|
+
const { flags } = parseLiveFlags(args);
|
|
8899
|
+
const creds = loadCredentials();
|
|
8900
|
+
if (!creds || !creds.token) {
|
|
8901
|
+
console.error('Not logged in. Run: atris login');
|
|
8902
|
+
process.exit(1);
|
|
8903
|
+
}
|
|
8904
|
+
const days = flags.days && Number.isFinite(flags.days) ? flags.days : 30;
|
|
8905
|
+
const result = await apiRequestJson(`/business/${encodeURIComponent(businessId)}/improvements/daily?days=${days}`, { token: creds.token });
|
|
8906
|
+
if (!result.ok) {
|
|
8907
|
+
console.error(`Failed to fetch improvements: ${result.error || 'Unknown error'}`);
|
|
8908
|
+
process.exit(1);
|
|
8909
|
+
}
|
|
8910
|
+
if (flags.json) {
|
|
8911
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
8912
|
+
return;
|
|
8913
|
+
}
|
|
8914
|
+
const history = result.data?.history || [];
|
|
8915
|
+
console.log('');
|
|
8916
|
+
console.log(`daily improvements — business ${businessId}`);
|
|
8917
|
+
console.log('');
|
|
8918
|
+
if (history.length === 0) {
|
|
8919
|
+
console.log(' (no improvement history yet — first entry lands after the next nightly self-improve run)');
|
|
8920
|
+
}
|
|
8921
|
+
for (const day of history) {
|
|
8922
|
+
console.log(` ${day.date}: ${day.agents_improved}/${day.agents_total} agents improved`);
|
|
8923
|
+
for (const agent of day.agents || []) {
|
|
8924
|
+
const note = agent.note ? ` — ${agent.note}` : '';
|
|
8925
|
+
console.log(` ${agent.kept_count > 0 ? '+' : ' '} ${agent.name} (kept ${agent.kept_count}, score ${agent.best_score})${note}`);
|
|
8926
|
+
}
|
|
8927
|
+
}
|
|
8928
|
+
console.log('');
|
|
8929
|
+
}
|
|
8930
|
+
|
|
8110
8931
|
// --- Command Dispatcher ---
|
|
8111
8932
|
|
|
8112
8933
|
async function memberCommand(subcommand, ...args) {
|
|
@@ -8158,6 +8979,13 @@ async function memberCommand(subcommand, ...args) {
|
|
|
8158
8979
|
return memberBlock(args[0], args[1], ...args.slice(2));
|
|
8159
8980
|
case 'status':
|
|
8160
8981
|
return memberStatus(args[0], ...args.slice(1));
|
|
8982
|
+
case 'live':
|
|
8983
|
+
return memberLive(...args);
|
|
8984
|
+
case 'heartbeat':
|
|
8985
|
+
case 'hb':
|
|
8986
|
+
return memberHeartbeat(args[0], ...args.slice(1));
|
|
8987
|
+
case 'improvements':
|
|
8988
|
+
return memberImprovements(args[0], ...args.slice(1));
|
|
8161
8989
|
case 'history':
|
|
8162
8990
|
return memberHistory(args[0], ...args.slice(1));
|
|
8163
8991
|
case 'supervisor':
|
|
@@ -8186,11 +9014,14 @@ async function memberCommand(subcommand, ...args) {
|
|
|
8186
9014
|
console.log(' goal-from-score <name> Create/reuse an active goal from Team score evidence');
|
|
8187
9015
|
console.log(' wake <name> Read Mission state and decide tick/wait/ask/stop');
|
|
8188
9016
|
console.log(' run <name> ["..."] Start a budgeted member mission, or run its active Mission Runtime');
|
|
8189
|
-
console.log(' loop <name>
|
|
9017
|
+
console.log(' loop <name|--all> Repeat wake on a bounded cadence with a no-overlap lease');
|
|
8190
9018
|
console.log(' tick <name> Propose the next bounded experiment');
|
|
8191
9019
|
console.log(' review <name> <id> Accept/discard an experiment with proof');
|
|
8192
9020
|
console.log(' block <name> <id> Mark an experiment blocked with a human/orchestrator ask');
|
|
8193
|
-
console.log(' status <name>
|
|
9021
|
+
console.log(' status <name|--all> Show goal, mission, activity, value, ask, and recent log');
|
|
9022
|
+
console.log(' live Show backend member projection (what iOS/web render) [--scope business --scope-id <id>]');
|
|
9023
|
+
console.log(' heartbeat <name> Mark a member running/done on every surface [--note "..."] [--done|--error]');
|
|
9024
|
+
console.log(' improvements <biz> Show daily self-improve history for a business [--days N]');
|
|
8194
9025
|
console.log(' history <name> Show git history of member identity files (MEMBER.md, SOUL.md)');
|
|
8195
9026
|
console.log(' supervisor recommendations Show advisory supervisor recommendations');
|
|
8196
9027
|
console.log(' objective-generator proposals Show autonomous objective proposal');
|
|
@@ -8225,10 +9056,12 @@ async function memberCommand(subcommand, ...args) {
|
|
|
8225
9056
|
console.log(' atris member loop growth --minutes 10 --interval 60 --json');
|
|
8226
9057
|
console.log(' atris member alive growth --hourly --forever --execute --confirm-autonomy-policy --json');
|
|
8227
9058
|
console.log(' atris member alive growth --install --hourly --forever --execute --confirm-autonomy-policy --json');
|
|
9059
|
+
console.log(' atris member alive --all --install --hourly --forever --dry-run');
|
|
8228
9060
|
console.log(' atris member alive growth --minutes 480 --interval 900 --execute --confirm-autonomy-policy --json');
|
|
8229
9061
|
console.log(' atris member loop growth --ticks 2 --interval 0 --json');
|
|
8230
9062
|
console.log(' atris member tick growth --json');
|
|
8231
9063
|
console.log(' atris member status growth');
|
|
9064
|
+
console.log(' atris member status --all');
|
|
8232
9065
|
console.log(' atris member review growth exp_123 --accept --proof "validated" --value 4');
|
|
8233
9066
|
console.log(' atris member archive old-member');
|
|
8234
9067
|
console.log(' atris member purge-archived --days=60 --confirm "delete archived members"');
|
|
@@ -8236,4 +9069,4 @@ async function memberCommand(subcommand, ...args) {
|
|
|
8236
9069
|
}
|
|
8237
9070
|
}
|
|
8238
9071
|
|
|
8239
|
-
module.exports = { memberCommand, findAllMembers, parseFrontmatter };
|
|
9072
|
+
module.exports = { memberCommand, findAllMembers, parseFrontmatter, wakeBootLines, buildMemberRunStartArgs };
|