atris 3.35.0 → 3.37.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 +37 -0
- 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 +22 -4
- package/atris/skills/fable-method/SKILL.md +66 -0
- package/atris/skills/improve/SKILL.md +65 -45
- package/atris/skills/youtube/SKILL.md +10 -1
- package/atris.md +2 -0
- package/ax +147 -19
- package/bin/atris.js +576 -267
- package/commands/activate.js +194 -88
- package/commands/agents.js +166 -0
- package/commands/autoland.js +459 -107
- 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/clean.js +50 -24
- package/commands/close.js +1083 -0
- package/commands/cloud.js +245 -0
- 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/engine.js +993 -32
- package/commands/experiments.js +28 -0
- package/commands/feedback.js +34 -12
- package/commands/fleet-report.js +206 -0
- package/commands/gm.js +23 -0
- package/commands/goal.js +247 -0
- package/commands/improve.js +642 -26
- package/commands/init.js +72 -44
- package/commands/interview.js +67 -1
- package/commands/land.js +152 -52
- package/commands/lifecycle.js +39 -3
- package/commands/log.js +84 -1
- package/commands/loops.js +220 -16
- package/commands/meet.js +220 -0
- package/commands/member.js +511 -34
- package/commands/mission.js +3144 -338
- package/commands/next.js +137 -0
- package/commands/now.js +220 -25
- 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/rainmaker.js +49 -0
- package/commands/report.js +415 -0
- package/commands/scout.js +147 -0
- package/commands/search.js +363 -0
- package/commands/skill.js +47 -3
- package/commands/slop.js +50 -2
- package/commands/soul.js +1 -1
- package/commands/stream.js +861 -0
- package/commands/study.js +693 -0
- package/commands/sync.js +67 -54
- package/commands/task.js +1346 -117
- package/commands/team.js +73 -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 +234 -13
- package/commands/xp.js +29 -11
- package/lib/auto-accept-certified.js +331 -34
- package/lib/autoland.js +319 -54
- package/lib/ax-auto-lane.js +96 -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 +2219 -67
- 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/mission-room.js +1 -0
- package/lib/mission-root.js +52 -0
- package/lib/next-moves.js +327 -10
- package/lib/one-lap-validator.js +60 -0
- package/lib/orb-context.js +477 -0
- package/lib/orb-scorecard.js +224 -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/router-brain.js +352 -0
- package/lib/runner-command.js +10 -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 +108 -29
- package/lib/task-proof.js +23 -1
- 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/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 {
|
|
@@ -614,8 +674,10 @@ function resolveMemberRunMissionId(name, args = []) {
|
|
|
614
674
|
const runtimeId = memberRunRunnableMissionId(purpose.runtimeMission?.id, missionMap);
|
|
615
675
|
if (runtimeId) return runtimeId;
|
|
616
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
|
|
617
679
|
const goals = loadMemberGoals(name, paths);
|
|
618
|
-
const goal =
|
|
680
|
+
const goal = (goals.goals || []).find((candidate) => candidate.status === 'active') || null;
|
|
619
681
|
const goalId = memberRunRunnableMissionId(goal?.mission_id, missionMap);
|
|
620
682
|
if (goalId) return goalId;
|
|
621
683
|
|
|
@@ -646,6 +708,7 @@ const MEMBER_RUN_START_VALUE_FLAGS = [
|
|
|
646
708
|
|
|
647
709
|
const MEMBER_RUN_START_BOOLEAN_FLAGS = [
|
|
648
710
|
'--json',
|
|
711
|
+
'--no-verify',
|
|
649
712
|
'--worktree',
|
|
650
713
|
'--shared-checkout',
|
|
651
714
|
'--no-worktree',
|
|
@@ -681,16 +744,34 @@ function memberRunTruthRule(args = []) {
|
|
|
681
744
|
|| 'say what changed, what was checked, and what is still unproven';
|
|
682
745
|
}
|
|
683
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
|
+
|
|
684
763
|
function memberRunUsefulTarget() {
|
|
685
764
|
try {
|
|
686
765
|
const { nextMoves: pickNextMoves, isGenericInboxPlaceholder } = require('../lib/next-moves');
|
|
687
766
|
const moves = pickNextMoves(process.cwd(), 5);
|
|
767
|
+
const waitingWishes = operatorWaitingWishTexts();
|
|
688
768
|
const candidate = moves.find((move) => {
|
|
689
769
|
const title = cleanMemberRunPhrase(move?.title);
|
|
690
770
|
if (!title) return false;
|
|
691
771
|
if (isGenericInboxPlaceholder(title)) return false;
|
|
692
772
|
if (/^mission xp\s*:/i.test(title)) return false;
|
|
693
773
|
if (/^decide and start the next useful mission after:/i.test(title)) return false;
|
|
774
|
+
if (waitingWishes.has(title.toLowerCase())) return false;
|
|
694
775
|
return true;
|
|
695
776
|
});
|
|
696
777
|
return candidate?.title ? cleanMemberRunPhrase(candidate.title) : '';
|
|
@@ -733,17 +814,18 @@ function pushFlagWhenPresent(out, args, name) {
|
|
|
733
814
|
if (hasFlag(args, name)) out.push(name);
|
|
734
815
|
}
|
|
735
816
|
|
|
736
|
-
function
|
|
737
|
-
const paths = requireMemberDir(name);
|
|
738
|
-
const owner = paths.storageName || name;
|
|
817
|
+
function buildMemberRunStartArgs(owner, missionText, args = [], cwd = process.cwd()) {
|
|
739
818
|
const startArgs = [
|
|
740
819
|
'mission',
|
|
741
820
|
'start',
|
|
742
821
|
missionText,
|
|
743
822
|
'--owner',
|
|
744
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.
|
|
745
827
|
'--runner',
|
|
746
|
-
readFlag(args, '--runner',
|
|
828
|
+
readFlag(args, '--runner', defaultObjectiveRunner(args)),
|
|
747
829
|
'--lane',
|
|
748
830
|
readFlag(args, '--lane', 'workspace'),
|
|
749
831
|
];
|
|
@@ -755,14 +837,43 @@ function startMemberRunMission(name, missionText, args = []) {
|
|
|
755
837
|
pushFlagValue(startArgs, args, '--minutes');
|
|
756
838
|
pushFlagValue(startArgs, args, '--hours');
|
|
757
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
|
+
}
|
|
758
847
|
pushFlagWhenPresent(startArgs, args, '--always-on');
|
|
759
848
|
pushFlagWhenPresent(startArgs, args, '--xp-task');
|
|
760
849
|
pushFlagWhenPresent(startArgs, args, '--agent-xp');
|
|
761
850
|
pushFlagWhenPresent(startArgs, args, '--spend-full-budget');
|
|
762
851
|
pushFlagWhenPresent(startArgs, args, '--use-whole-budget');
|
|
763
852
|
pushFlagWhenPresent(startArgs, args, '--stop-when-done');
|
|
764
|
-
|
|
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
|
+
}
|
|
765
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);
|
|
766
877
|
|
|
767
878
|
const cliPath = path.join(__dirname, '..', 'bin', 'atris.js');
|
|
768
879
|
try {
|
|
@@ -1603,14 +1714,43 @@ function readRecentWakeReceiptEvidence(name) {
|
|
|
1603
1714
|
}
|
|
1604
1715
|
|
|
1605
1716
|
function readJsonlRowsIfExists(filePath, maxRows = 80) {
|
|
1717
|
+
const tailBytes = 256 * 1024;
|
|
1718
|
+
let fd;
|
|
1606
1719
|
let text = '';
|
|
1720
|
+
let truncated = false;
|
|
1607
1721
|
try {
|
|
1608
|
-
|
|
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;
|
|
1609
1741
|
} catch {
|
|
1610
1742
|
return [];
|
|
1743
|
+
} finally {
|
|
1744
|
+
if (fd !== undefined) {
|
|
1745
|
+
try { fs.closeSync(fd); } catch {}
|
|
1746
|
+
}
|
|
1611
1747
|
}
|
|
1612
1748
|
const rows = [];
|
|
1613
|
-
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));
|
|
1614
1754
|
for (const line of lines) {
|
|
1615
1755
|
try {
|
|
1616
1756
|
rows.push(JSON.parse(line));
|
|
@@ -2870,6 +3010,17 @@ function failureCoveredByPassLesson(line, passLessonText = '') {
|
|
|
2870
3010
|
return /spawnSync\s+\/bin\/sh\s+ETIMEDOUT/i.test(passLessonText)
|
|
2871
3011
|
&& (!target || passLessonText.includes(target) || (targetTail && passLessonText.includes(targetTail)));
|
|
2872
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
|
+
}
|
|
2873
3024
|
return false;
|
|
2874
3025
|
}
|
|
2875
3026
|
|
|
@@ -3762,6 +3913,7 @@ function findAllMembers(teamDir) {
|
|
|
3762
3913
|
const content = fs.readFileSync(fullPath, 'utf8');
|
|
3763
3914
|
const fm = parseFrontmatter(content);
|
|
3764
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
|
|
3765
3917
|
|
|
3766
3918
|
const name = entry.replace('.md', '');
|
|
3767
3919
|
members.push({
|
|
@@ -7569,6 +7721,127 @@ async function runMemberWake(name, { execute = false, confirmed = false, force =
|
|
|
7569
7721
|
};
|
|
7570
7722
|
}
|
|
7571
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
|
+
|
|
7572
7845
|
async function memberWake(name, ...args) {
|
|
7573
7846
|
const asJson = hasFlag(args, '--json');
|
|
7574
7847
|
const execute = hasFlag(args, '--execute') && !hasFlag(args, '--dry-run');
|
|
@@ -7579,20 +7852,25 @@ async function memberWake(name, ...args) {
|
|
|
7579
7852
|
file: readFlag(args, '--domain-file', ''),
|
|
7580
7853
|
name: readFlag(args, '--domain-name', ''),
|
|
7581
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
|
+
}
|
|
7582
7872
|
const result = await runMemberWake(name, { execute, confirmed, force, domainInput });
|
|
7583
|
-
printJsonOrText(
|
|
7584
|
-
result,
|
|
7585
|
-
[
|
|
7586
|
-
`Wake: ${name}`,
|
|
7587
|
-
`Decision: ${result.decision}`,
|
|
7588
|
-
`Reason: ${result.reason}`,
|
|
7589
|
-
`Mode: ${result.mode}${result.executed ? ' executed' : ''}`,
|
|
7590
|
-
...(result.ask ? [`Ask: ${result.ask}`] : []),
|
|
7591
|
-
`Next: ${result.next_command}`,
|
|
7592
|
-
`Receipt: ${path.relative(process.cwd(), result.receipt_path)}`,
|
|
7593
|
-
],
|
|
7594
|
-
asJson,
|
|
7595
|
-
);
|
|
7873
|
+
printJsonOrText(result, wakeBootLines(name, result), asJson);
|
|
7596
7874
|
}
|
|
7597
7875
|
|
|
7598
7876
|
function memberAlive(name, ...args) {
|
|
@@ -7874,6 +8152,13 @@ async function memberLoop(name, ...args) {
|
|
|
7874
8152
|
let earlyExit = null;
|
|
7875
8153
|
let consecutiveIdle = 0;
|
|
7876
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;
|
|
7877
8162
|
|
|
7878
8163
|
try {
|
|
7879
8164
|
for (let index = 0; index < ticks; index += 1) {
|
|
@@ -7945,6 +8230,29 @@ async function memberLoop(name, ...args) {
|
|
|
7945
8230
|
};
|
|
7946
8231
|
tickResults.push(tick);
|
|
7947
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
|
+
}
|
|
7948
8256
|
}
|
|
7949
8257
|
} catch (error) {
|
|
7950
8258
|
failed = true;
|
|
@@ -7959,6 +8267,7 @@ async function memberLoop(name, ...args) {
|
|
|
7959
8267
|
fs.appendFileSync(tickLogPath, JSON.stringify(tick) + '\n', 'utf8');
|
|
7960
8268
|
break;
|
|
7961
8269
|
}
|
|
8270
|
+
if (earlyExit) break;
|
|
7962
8271
|
if (execute) {
|
|
7963
8272
|
const last = tickResults[tickResults.length - 1];
|
|
7964
8273
|
if (last && last.productive) {
|
|
@@ -8007,6 +8316,7 @@ async function memberLoop(name, ...args) {
|
|
|
8007
8316
|
duration_ms_actual: Date.parse(finishedAt) - Date.parse(startedAt),
|
|
8008
8317
|
decisions,
|
|
8009
8318
|
early_exit: earlyExit,
|
|
8319
|
+
stop: earlyExit?.stop || null,
|
|
8010
8320
|
blocked_on_human: earlyExit?.blocked_on_human === true,
|
|
8011
8321
|
needs_user: earlyExit?.needs_user === true,
|
|
8012
8322
|
next_command: earlyExit?.next_command || null,
|
|
@@ -8328,6 +8638,16 @@ function memberStatus(name, ...args) {
|
|
|
8328
8638
|
const mission = memberMissionSummary(owner, memberRunMissionMap());
|
|
8329
8639
|
const activity = memberLastActivity(owner, paths);
|
|
8330
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';
|
|
8331
8651
|
const payload = {
|
|
8332
8652
|
ok: true,
|
|
8333
8653
|
action: 'status',
|
|
@@ -8339,6 +8659,10 @@ function memberStatus(name, ...args) {
|
|
|
8339
8659
|
current_experiment: goalPlane.current || null,
|
|
8340
8660
|
last_reviewed: goalPlane.lastReviewed || null,
|
|
8341
8661
|
value: goalPlane.value,
|
|
8662
|
+
switch: {
|
|
8663
|
+
awake: memberSwitch.awake !== false,
|
|
8664
|
+
loops: memberSwitch.loops || {},
|
|
8665
|
+
},
|
|
8342
8666
|
mission: mission.latest ? {
|
|
8343
8667
|
id: mission.latest.id || null,
|
|
8344
8668
|
name: mission.name,
|
|
@@ -8354,20 +8678,26 @@ function memberStatus(name, ...args) {
|
|
|
8354
8678
|
goals_path: paths.goalsJson,
|
|
8355
8679
|
goals_md_path: paths.goalsMd,
|
|
8356
8680
|
};
|
|
8681
|
+
const s = wakeStyle();
|
|
8682
|
+
const role = wakeMemberRole(name);
|
|
8683
|
+
const ballWithHuman = goalPlane.needsUser === true || goalPlane.current?.status === 'proposed';
|
|
8357
8684
|
printJsonOrText(
|
|
8358
8685
|
payload,
|
|
8359
8686
|
[
|
|
8360
|
-
|
|
8361
|
-
`
|
|
8362
|
-
|
|
8363
|
-
`
|
|
8364
|
-
|
|
8365
|
-
`
|
|
8366
|
-
`
|
|
8367
|
-
`
|
|
8368
|
-
`
|
|
8369
|
-
|
|
8370
|
-
|
|
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
|
+
'',
|
|
8371
8701
|
],
|
|
8372
8702
|
asJson,
|
|
8373
8703
|
);
|
|
@@ -8461,6 +8791,143 @@ function memberHistory(name, ...args) {
|
|
|
8461
8791
|
}
|
|
8462
8792
|
}
|
|
8463
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
|
+
|
|
8464
8931
|
// --- Command Dispatcher ---
|
|
8465
8932
|
|
|
8466
8933
|
async function memberCommand(subcommand, ...args) {
|
|
@@ -8512,6 +8979,13 @@ async function memberCommand(subcommand, ...args) {
|
|
|
8512
8979
|
return memberBlock(args[0], args[1], ...args.slice(2));
|
|
8513
8980
|
case 'status':
|
|
8514
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));
|
|
8515
8989
|
case 'history':
|
|
8516
8990
|
return memberHistory(args[0], ...args.slice(1));
|
|
8517
8991
|
case 'supervisor':
|
|
@@ -8545,6 +9019,9 @@ async function memberCommand(subcommand, ...args) {
|
|
|
8545
9019
|
console.log(' review <name> <id> Accept/discard an experiment with proof');
|
|
8546
9020
|
console.log(' block <name> <id> Mark an experiment blocked with a human/orchestrator ask');
|
|
8547
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]');
|
|
8548
9025
|
console.log(' history <name> Show git history of member identity files (MEMBER.md, SOUL.md)');
|
|
8549
9026
|
console.log(' supervisor recommendations Show advisory supervisor recommendations');
|
|
8550
9027
|
console.log(' objective-generator proposals Show autonomous objective proposal');
|
|
@@ -8592,4 +9069,4 @@ async function memberCommand(subcommand, ...args) {
|
|
|
8592
9069
|
}
|
|
8593
9070
|
}
|
|
8594
9071
|
|
|
8595
|
-
module.exports = { memberCommand, findAllMembers, parseFrontmatter };
|
|
9072
|
+
module.exports = { memberCommand, findAllMembers, parseFrontmatter, wakeBootLines, buildMemberRunStartArgs };
|