atris 3.51.0 → 3.53.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/atris/skills/engines/SKILL.md +8 -7
- package/atris/skills/youtube/SKILL.md +8 -8
- package/ax +592 -91
- package/bin/atris.js +17 -5
- package/commands/computer.js +2 -2
- package/commands/mission.js +52 -24
- package/commands/now.js +10 -3
- package/commands/radar.js +65 -33
- package/commands/youtube.js +206 -23
- package/lib/auto-accept-certified.js +2 -1
- package/lib/engine-ask.js +11 -3
- package/lib/engine-registry.js +32 -7
- package/lib/mission-ledger-compact.js +127 -0
- package/lib/mission-runtime-loop.js +30 -2
- package/lib/next-moves.js +34 -34
- package/lib/permission-grants.js +10 -1
- package/lib/runner-command.js +5 -4
- package/lib/task-db.js +18 -2
- package/lib/task-proof.js +6 -1
- package/lib/task-receipt.js +5 -1
- package/package.json +2 -1
- package/scripts/outbound-artifact-gate.js +227 -0
package/bin/atris.js
CHANGED
|
@@ -3140,7 +3140,7 @@ function inspectAgentCliWiring() {
|
|
|
3140
3140
|
},
|
|
3141
3141
|
];
|
|
3142
3142
|
|
|
3143
|
-
const binaries = ['atris', 'ax', 'claude', 'codex', 'cursor-agent', 'devin', '
|
|
3143
|
+
const binaries = ['atris', 'ax', 'claude', 'codex', 'cursor-agent', 'devin', 'agy'].map((name) => ({
|
|
3144
3144
|
name,
|
|
3145
3145
|
path: commandOnPath(name),
|
|
3146
3146
|
}));
|
|
@@ -3319,7 +3319,7 @@ async function chatAtris() {
|
|
|
3319
3319
|
for (const arg of rawArgs) {
|
|
3320
3320
|
if (arg === '--agent') {
|
|
3321
3321
|
agentLane = true;
|
|
3322
|
-
} else if (arg === '--print' || arg === '--headless') {
|
|
3322
|
+
} else if (arg === '--print' || arg === '--headless' || arg === '--rapid' || arg === '--auto-approve') {
|
|
3323
3323
|
fastLaneFlags.push(arg);
|
|
3324
3324
|
} else {
|
|
3325
3325
|
messageArgs.push(arg);
|
|
@@ -3332,9 +3332,12 @@ async function chatAtris() {
|
|
|
3332
3332
|
console.log('Usage: atris chat ["message"]');
|
|
3333
3333
|
console.log('');
|
|
3334
3334
|
console.log(' Chat with Atris 2 Fast in this workspace: tools attached, same turn as `ax --fast`.');
|
|
3335
|
+
console.log(' Pass --rapid to use Atris Rapid instead.');
|
|
3335
3336
|
console.log(' Requires `atris login`.');
|
|
3336
3337
|
console.log('');
|
|
3337
3338
|
console.log(' atris chat Interactive chat (ax --fast --chat)');
|
|
3339
|
+
console.log(' atris chat --rapid Interactive Rapid chat (ax --rapid --chat)');
|
|
3340
|
+
console.log(' atris chat --rapid --auto-approve Rapid chat that auto-approves safe git push');
|
|
3338
3341
|
console.log(' atris chat "what now?" One-shot message (ax --fast)');
|
|
3339
3342
|
console.log(' atris chat --print "..." Headless JSON result (ax --fast --print)');
|
|
3340
3343
|
console.log(' atris chat --agent [...] Legacy cloud-agent lane (needs `atris agent`)');
|
|
@@ -3361,9 +3364,16 @@ async function chatAtris() {
|
|
|
3361
3364
|
if (!agentLane) {
|
|
3362
3365
|
try {
|
|
3363
3366
|
const axPath = path.join(__dirname, '..', 'ax');
|
|
3364
|
-
const
|
|
3367
|
+
const lane = fastLaneFlags.includes('--rapid') ? '--rapid' : '--fast';
|
|
3368
|
+
const extraFlags = fastLaneFlags.filter((flag) => flag !== '--rapid');
|
|
3369
|
+
const axArgs = message
|
|
3370
|
+
? [lane, ...extraFlags, message].filter(Boolean)
|
|
3371
|
+
: [lane, ...extraFlags, '--chat'];
|
|
3365
3372
|
const run = spawnSync(process.execPath, [axPath, ...axArgs], { stdio: 'inherit' });
|
|
3366
|
-
|
|
3373
|
+
// spawnSync reports spawn failure via run.error (status stays null), and
|
|
3374
|
+
// a signal-killed ax also leaves status null; neither is a success.
|
|
3375
|
+
if (!run.error) process.exit(run.status ?? 1);
|
|
3376
|
+
// ax unavailable: fall through to the agent lane.
|
|
3367
3377
|
} catch {
|
|
3368
3378
|
// ax unavailable: fall through to the agent lane.
|
|
3369
3379
|
}
|
|
@@ -3632,7 +3642,9 @@ async function atrisFastChat() {
|
|
|
3632
3642
|
const axModule = require('../ax');
|
|
3633
3643
|
if (axModule.resolveRoute(message) === 'local') {
|
|
3634
3644
|
const run = spawnSync(process.execPath, [path.join(__dirname, '..', 'ax'), '--fast', message], { stdio: 'inherit' });
|
|
3635
|
-
|
|
3645
|
+
// Same rule as the chat lane: spawn failure or a signal kill leaves
|
|
3646
|
+
// status null and must not read as success.
|
|
3647
|
+
if (!run.error) process.exit(run.status ?? 1);
|
|
3636
3648
|
}
|
|
3637
3649
|
} catch {
|
|
3638
3650
|
// ax unavailable: fall through to the plain cloud one-shot.
|
package/commands/computer.js
CHANGED
|
@@ -2141,7 +2141,7 @@ async function runBusinessPromptViaRunnerProxy(token, ctx, prompt, options = {})
|
|
|
2141
2141
|
const payload = {
|
|
2142
2142
|
prompt,
|
|
2143
2143
|
permission_mode: 'bypassPermissions',
|
|
2144
|
-
max_turns: Math.min(Math.max(Number(options.maxTurns ||
|
|
2144
|
+
max_turns: Math.min(Math.max(Number(options.maxTurns || 40), 1), 100),
|
|
2145
2145
|
reset_context: Boolean(options.resetContext),
|
|
2146
2146
|
};
|
|
2147
2147
|
if (options.worker) payload.worker = options.worker;
|
|
@@ -3399,7 +3399,7 @@ async function sendBusinessChat(token, ctx, message, sessionId, resetContext = f
|
|
|
3399
3399
|
const fallback = await runBusinessPromptViaRunnerProxy(token, ctx, message, {
|
|
3400
3400
|
...options,
|
|
3401
3401
|
resetContext,
|
|
3402
|
-
maxTurns:
|
|
3402
|
+
maxTurns: 60,
|
|
3403
3403
|
});
|
|
3404
3404
|
if (!fallback.ok) {
|
|
3405
3405
|
if (typeof options.onFailure === 'function') {
|
package/commands/mission.js
CHANGED
|
@@ -53,6 +53,7 @@ const {
|
|
|
53
53
|
runsPruneLines,
|
|
54
54
|
formatBytes,
|
|
55
55
|
} = require('../lib/runs-prune');
|
|
56
|
+
const { compactMissionLedger } = require('../lib/mission-ledger-compact');
|
|
56
57
|
const autolandLib = require('../lib/autoland');
|
|
57
58
|
const { gateForHuman, landingWhyClause } = require('../lib/voice-gate');
|
|
58
59
|
const { operatorReady, hasAgentJargon } = require('./autoland');
|
|
@@ -1347,6 +1348,11 @@ function saveMission(mission, root = process.cwd(), eventType = 'mission_updated
|
|
|
1347
1348
|
updated_at: stampIso(),
|
|
1348
1349
|
});
|
|
1349
1350
|
appendJsonLine(paths.missionsJsonl, next);
|
|
1351
|
+
// Self-maintenance: once history outweighs live missions 4:1 the ledger is
|
|
1352
|
+
// rewritten to one row per mission (latest state, first display number —
|
|
1353
|
+
// exactly what loadMissionMap reconstructs). Every status/tick path parses
|
|
1354
|
+
// this file several times, so unbounded snapshot history taxes everything.
|
|
1355
|
+
try { compactMissionLedger(paths.missionsJsonl); } catch {}
|
|
1350
1356
|
const event = appendEvent(eventType, next, payload, root);
|
|
1351
1357
|
renderMissionStatus(root);
|
|
1352
1358
|
renderMemberMissionState(next.owner, root);
|
|
@@ -4810,27 +4816,60 @@ function missionTickVerification(tick) {
|
|
|
4810
4816
|
return { verified: false, state: ran ? 'unchecked' : 'skipped', unchecked: ran };
|
|
4811
4817
|
}
|
|
4812
4818
|
|
|
4813
|
-
//
|
|
4814
|
-
//
|
|
4815
|
-
//
|
|
4816
|
-
//
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
+
// Per-process index of mission run receipts, keyed by runs dir. Reading and
|
|
4820
|
+
// parsing ~1400 receipt files per mission made `mission list` scale as
|
|
4821
|
+
// O(missions × runs-files); building the mission_id → receipts map once per
|
|
4822
|
+
// invocation keeps outputs identical while scanning the dir a single time.
|
|
4823
|
+
// The dir mtime guards the memo so a receipt written mid-process (new file in
|
|
4824
|
+
// the dir) invalidates it; malformed receipts stay silently skipped, matching
|
|
4825
|
+
// readJson's existing tolerance.
|
|
4826
|
+
const missionRunsIndexCache = new Map();
|
|
4827
|
+
|
|
4828
|
+
function missionRunsReceiptIndex(root = process.cwd()) {
|
|
4829
|
+
const runsDir = statePaths(root).runsDir;
|
|
4830
|
+
let dirMtime = -1;
|
|
4831
|
+
try {
|
|
4832
|
+
dirMtime = fs.statSync(runsDir).mtimeMs;
|
|
4833
|
+
} catch {
|
|
4834
|
+
dirMtime = -1;
|
|
4835
|
+
}
|
|
4836
|
+
const cached = missionRunsIndexCache.get(runsDir);
|
|
4837
|
+
if (cached && cached.dirMtime === dirMtime) return cached.byMission;
|
|
4838
|
+
|
|
4819
4839
|
let files = [];
|
|
4820
4840
|
try {
|
|
4821
|
-
files = fs.readdirSync(
|
|
4841
|
+
files = fs.readdirSync(runsDir)
|
|
4822
4842
|
.filter((file) => file.startsWith('mission-') && file.endsWith('.json'))
|
|
4823
|
-
.map((file) => path.join(
|
|
4843
|
+
.map((file) => path.join(runsDir, file));
|
|
4824
4844
|
} catch {
|
|
4825
4845
|
files = [];
|
|
4826
4846
|
}
|
|
4847
|
+
const byMission = new Map();
|
|
4848
|
+
for (const file of files) {
|
|
4849
|
+
const receipt = readJson(file);
|
|
4850
|
+
if (!receipt || !receipt.mission_id) continue;
|
|
4851
|
+
let bucket = byMission.get(receipt.mission_id);
|
|
4852
|
+
if (!bucket) {
|
|
4853
|
+
bucket = [];
|
|
4854
|
+
byMission.set(receipt.mission_id, bucket);
|
|
4855
|
+
}
|
|
4856
|
+
bucket.push({ file, receipt });
|
|
4857
|
+
}
|
|
4858
|
+
missionRunsIndexCache.set(runsDir, { dirMtime, byMission });
|
|
4859
|
+
return byMission;
|
|
4860
|
+
}
|
|
4861
|
+
|
|
4862
|
+
// Roll every ran tick for a mission into a verification-debt tally. `unchecked`
|
|
4863
|
+
// counts ran ticks that recorded no verifier result at all — the "tick recorded
|
|
4864
|
+
// but nothing was checked" red the report and now.md rollup surface and count
|
|
4865
|
+
// against the mission.
|
|
4866
|
+
function missionVerificationDebt(mission, root = process.cwd()) {
|
|
4867
|
+
const entries = missionRunsReceiptIndex(root).get(mission.id) || [];
|
|
4827
4868
|
let ran = 0;
|
|
4828
4869
|
let verified = 0;
|
|
4829
4870
|
let unchecked = 0;
|
|
4830
4871
|
const seen = new Set();
|
|
4831
|
-
for (const
|
|
4832
|
-
const receipt = readJson(file);
|
|
4833
|
-
if (!receipt || receipt.mission_id !== mission.id) continue;
|
|
4872
|
+
for (const { receipt } of entries) {
|
|
4834
4873
|
for (const tick of missionReceiptTicks(receipt)) {
|
|
4835
4874
|
if (!tick || tick.status !== 'ran') continue;
|
|
4836
4875
|
const key = `${tick.tick_index || ''}:${tick.finished_at || tick.started_at || ''}`;
|
|
@@ -4853,21 +4892,10 @@ function missionVerificationDebtLine(debt) {
|
|
|
4853
4892
|
}
|
|
4854
4893
|
|
|
4855
4894
|
function missionReportTimeline(mission, root = process.cwd(), limit = 6) {
|
|
4856
|
-
const
|
|
4857
|
-
let files = [];
|
|
4858
|
-
try {
|
|
4859
|
-
files = fs.readdirSync(paths.runsDir)
|
|
4860
|
-
.filter((file) => file.startsWith('mission-') && file.endsWith('.json'))
|
|
4861
|
-
.map((file) => path.join(paths.runsDir, file));
|
|
4862
|
-
} catch {
|
|
4863
|
-
files = [];
|
|
4864
|
-
}
|
|
4865
|
-
|
|
4895
|
+
const entries = missionRunsReceiptIndex(root).get(mission.id) || [];
|
|
4866
4896
|
const items = [];
|
|
4867
4897
|
const seen = new Set();
|
|
4868
|
-
for (const file of
|
|
4869
|
-
const receipt = readJson(file);
|
|
4870
|
-
if (!receipt || receipt.mission_id !== mission.id) continue;
|
|
4898
|
+
for (const { file, receipt } of entries) {
|
|
4871
4899
|
const receiptPath = path.relative(root, file);
|
|
4872
4900
|
for (const tick of missionReceiptTicks(receipt)) {
|
|
4873
4901
|
const summary = missionTickReportSummary(tick);
|
package/commands/now.js
CHANGED
|
@@ -270,7 +270,13 @@ function taskReceiptTitle(row) {
|
|
|
270
270
|
// one per receipt, landed result + human proof state. Shares the collector
|
|
271
271
|
// with the count so the number and the visible list always match.
|
|
272
272
|
function todayTaskReceiptLines(root = process.cwd(), date = new Date(), limit = 6) {
|
|
273
|
-
return collectTaskReceiptsToday(root, date)
|
|
273
|
+
return formatTaskReceiptLines(collectTaskReceiptsToday(root, date), limit);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Format an already-collected receipt list, so a caller that needs both the
|
|
277
|
+
// count and the lines (renderDefaultNow) parses the receipt files only once.
|
|
278
|
+
function formatTaskReceiptLines(receipts, limit = 6) {
|
|
279
|
+
return receipts
|
|
274
280
|
.slice(-limit)
|
|
275
281
|
.reverse()
|
|
276
282
|
.map((r) => {
|
|
@@ -411,7 +417,8 @@ function renderDefaultNow(root = process.cwd()) {
|
|
|
411
417
|
const journalPath = currentJournalPath(root);
|
|
412
418
|
const openTodoCount = countOpenWorkItems(root, todoPath);
|
|
413
419
|
const inboxCount = countMatches(journalPath, /^-\s+\*\*I\d+:/gm);
|
|
414
|
-
const
|
|
420
|
+
const taskReceipts = collectTaskReceiptsToday(root);
|
|
421
|
+
const taskReceiptCount = taskReceipts.length;
|
|
415
422
|
const missionReceiptCount = countMissionReceiptsToday(root);
|
|
416
423
|
const completedCount = taskReceiptCount + missionReceiptCount || countJournalCompletedReceipts(journalPath);
|
|
417
424
|
const generated = formatLocalTimestamp();
|
|
@@ -419,7 +426,7 @@ function renderDefaultNow(root = process.cwd()) {
|
|
|
419
426
|
const whatMattersNow = moveLine
|
|
420
427
|
? `${moveLine}\n\n- Run the named next action and leave proof before choosing another.`
|
|
421
428
|
: '- Decide the next useful move before opening more context.';
|
|
422
|
-
const receiptLines =
|
|
429
|
+
const receiptLines = formatTaskReceiptLines(taskReceipts);
|
|
423
430
|
const missionReceiptLines = todayMissionReceiptLines(root);
|
|
424
431
|
const commitLines = landedCommitLines(root);
|
|
425
432
|
const hiddenReceiptCount = Math.max(0, completedCount - missionReceiptLines.length - receiptLines.length);
|
package/commands/radar.js
CHANGED
|
@@ -54,23 +54,39 @@ function agentTypeForCommand(command) {
|
|
|
54
54
|
if (/(^|\s|\/)claude(\s|$)/.test(cmd) && !/Claude\.app/.test(cmd)) return 'claude';
|
|
55
55
|
if (/(^|\s|\/)opencode(\s|$)/.test(cmd)) return 'opencode';
|
|
56
56
|
if (/(^|\s|\/)devin(\s|$)/.test(cmd)) return 'devin';
|
|
57
|
+
if (/(^|\s|\/)agy(\s|$)/.test(cmd)) return 'agy';
|
|
57
58
|
if (/(^|\s|\/)droid(\s|$)/.test(cmd)) return 'droid';
|
|
58
59
|
return null;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
// Resolve cwd for many pids at once: one lsof call on darwin instead of one
|
|
63
|
+
// per process. Returns a Map of pid (string) -> cwd; missing pids stay absent.
|
|
64
|
+
function processCwds(pids, deps) {
|
|
62
65
|
const { platform, execFile } = deps;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
const byPid = new Map();
|
|
67
|
+
if (!pids.length) return byPid;
|
|
68
|
+
if (platform === 'linux') {
|
|
69
|
+
for (const pid of pids) {
|
|
70
|
+
try { byPid.set(String(pid), deps.readlink(`/proc/${pid}/cwd`)); } catch {}
|
|
71
|
+
}
|
|
72
|
+
return byPid;
|
|
73
|
+
}
|
|
74
|
+
if (platform === 'darwin') {
|
|
75
|
+
let out = '';
|
|
76
|
+
try {
|
|
77
|
+
out = execFile('lsof', ['-a', '-d', 'cwd', '-Fn', '-p', pids.map(String).join(',')], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
// lsof exits non-zero when any pid in the batch is gone; keep the rows it
|
|
80
|
+
// did print instead of losing every cwd.
|
|
81
|
+
out = error && error.stdout ? error.stdout : '';
|
|
66
82
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
let currentPid = '';
|
|
84
|
+
for (const line of String(out).split(/\r?\n/)) {
|
|
85
|
+
if (line.startsWith('p')) currentPid = line.slice(1).trim();
|
|
86
|
+
else if (line.startsWith('n') && currentPid) byPid.set(currentPid, line.slice(1));
|
|
71
87
|
}
|
|
72
|
-
}
|
|
73
|
-
return
|
|
88
|
+
}
|
|
89
|
+
return byPid;
|
|
74
90
|
}
|
|
75
91
|
|
|
76
92
|
function gitBranch(cwd, execFile) {
|
|
@@ -93,22 +109,28 @@ function collectAgents(deps) {
|
|
|
93
109
|
.map(row => ({ ...row, agent: agentTypeForCommand(row.command) }))
|
|
94
110
|
.filter(row => row.agent);
|
|
95
111
|
const parentPids = new Set(agentRows.map(row => String(row.ppid || '')).filter(Boolean));
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
const topRows = agentRows.filter(row => !parentPids.has(String(row.pid)));
|
|
113
|
+
const cwdByPid = processCwds(topRows.map(row => row.pid), deps);
|
|
114
|
+
const branchByCwd = new Map();
|
|
115
|
+
const branchFor = (cwd) => {
|
|
116
|
+
if (!cwd) return '';
|
|
117
|
+
if (!branchByCwd.has(cwd)) branchByCwd.set(cwd, gitBranch(cwd, deps.execFile));
|
|
118
|
+
return branchByCwd.get(cwd);
|
|
119
|
+
};
|
|
120
|
+
return topRows.map(row => {
|
|
121
|
+
const cwd = cwdByPid.get(String(row.pid)) || '';
|
|
122
|
+
return {
|
|
123
|
+
pid: row.pid,
|
|
124
|
+
agent: row.agent,
|
|
125
|
+
command: row.command,
|
|
126
|
+
status: row.stat.includes('Z') ? 'zombie' : row.stat.includes('T') ? 'stopped' : 'active',
|
|
127
|
+
cwd,
|
|
128
|
+
repo: repoLabel(cwd),
|
|
129
|
+
branch: branchFor(cwd),
|
|
130
|
+
cpu: row.cpu,
|
|
131
|
+
mem: row.mem,
|
|
132
|
+
};
|
|
133
|
+
});
|
|
112
134
|
}
|
|
113
135
|
|
|
114
136
|
function loadTasks(root, deps) {
|
|
@@ -287,8 +309,11 @@ function loadTeam(root, deps) {
|
|
|
287
309
|
};
|
|
288
310
|
}
|
|
289
311
|
|
|
290
|
-
function
|
|
291
|
-
|
|
312
|
+
function loadScorecards(root, deps) {
|
|
313
|
+
return readJsonLines(path.join(root, '.atris', 'state', 'scorecards.jsonl'), deps.readFile, deps.exists);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function loadBrain(root, deps, scorecards = loadScorecards(root, deps)) {
|
|
292
317
|
const operatorDir = path.join(root, '.atris', 'state', 'operator-scorecards');
|
|
293
318
|
let operatorScorecards = 0;
|
|
294
319
|
try {
|
|
@@ -316,7 +341,7 @@ function countDirectoryEntries(dir, deps, predicate = () => true) {
|
|
|
316
341
|
return listNames(dir, deps).filter(predicate).length;
|
|
317
342
|
}
|
|
318
343
|
|
|
319
|
-
function loadBusinessCollaboration(root, deps, team = {}) {
|
|
344
|
+
function loadBusinessCollaboration(root, deps, team = {}, scorecardCount = countJsonLines(path.join(root, '.atris', 'state', 'scorecards.jsonl'), deps)) {
|
|
320
345
|
const business = readJsonFile(path.join(root, '.atris', 'business.json'), deps, null);
|
|
321
346
|
const runtime = readJsonFile(path.join(root, '.atris', 'state', 'runtime.json'), deps, null);
|
|
322
347
|
const sync = readJsonFile(path.join(root, '.atris', 'state', '_sync.json'), deps, null);
|
|
@@ -335,7 +360,7 @@ function loadBusinessCollaboration(root, deps, team = {}) {
|
|
|
335
360
|
const localReceipts = countDirectoryEntries(path.join(root, '.atris', 'receipts'), deps, name => /\.(json|md|txt)$/i.test(name));
|
|
336
361
|
const events = countJsonLines(path.join(root, '.atris', 'state', 'events.jsonl'), deps);
|
|
337
362
|
const episodes = countJsonLines(path.join(root, '.atris', 'state', 'episodes.jsonl'), deps);
|
|
338
|
-
const scorecards =
|
|
363
|
+
const scorecards = scorecardCount;
|
|
339
364
|
const computerDirs = countDirectoryEntries(path.join(root, 'atris', 'computers'), deps, name => !name.startsWith('.'));
|
|
340
365
|
const hasOnboarding = ingestPacks > 0 || starterBriefs > 0 || onePagers > 0;
|
|
341
366
|
const hasProofLoop = events > 0 || episodes > 0 || scorecards > 0 || localReceipts > 0;
|
|
@@ -786,12 +811,18 @@ function collectRadar(options = {}) {
|
|
|
786
811
|
const missions = loadMissions(root, deps, nowMs);
|
|
787
812
|
identityCache.set(root, { missions, missionLocks: loadMissionLocks(path.join(root, '.atris', 'state'), deps), memberLocks: loadMemberLoopLocks(path.join(root, '.atris', 'state'), deps) });
|
|
788
813
|
const worktrees = loadWorktrees(root, deps);
|
|
814
|
+
const branchIdentityCache = new Map();
|
|
815
|
+
const branchIdentityFor = (cwd) => {
|
|
816
|
+
if (!cwd) return null;
|
|
817
|
+
if (!branchIdentityCache.has(cwd)) branchIdentityCache.set(cwd, branchIdentityAtCwd(cwd, deps.execFile));
|
|
818
|
+
return branchIdentityCache.get(cwd);
|
|
819
|
+
};
|
|
789
820
|
const agents = collectAgents(deps).map(agent => {
|
|
790
821
|
const taskWorkspaceRoot = findTaskWorkspaceRoot(agent.cwd, deps);
|
|
791
822
|
const agentTasks = taskWorkspaceRoot ? loadTasksCached(taskWorkspaceRoot, deps, taskCache) : [];
|
|
792
823
|
const identity = taskWorkspaceRoot ? loadWorkspaceIdentity(taskWorkspaceRoot, deps, identityCache, nowMs) : { missions: [], missionLocks: [], memberLocks: [] };
|
|
793
824
|
const sidecar = loadAgentWorktreeSidecar(agent.cwd, deps);
|
|
794
|
-
const branchIdentity =
|
|
825
|
+
const branchIdentity = branchIdentityFor(agent.cwd);
|
|
795
826
|
const sessionId = parseSessionIdFromCommand(agent.command);
|
|
796
827
|
const interactive = agent.agent === 'claude' && isInteractiveClaudeCommand(agent.command);
|
|
797
828
|
const resolved = resolveAgentTaskBinding({
|
|
@@ -823,14 +854,15 @@ function collectRadar(options = {}) {
|
|
|
823
854
|
task_action: task ? taskSessionAction(agent, task, taskWorkspaceRoot) : untaskedAction(agent, taskWorkspaceRoot, agentTasks, resolved),
|
|
824
855
|
};
|
|
825
856
|
});
|
|
857
|
+
const scorecards = loadScorecards(root, deps);
|
|
826
858
|
const osState = {
|
|
827
859
|
xp: loadXp(root, deps),
|
|
828
860
|
team: loadTeam(root, deps),
|
|
829
|
-
brain: loadBrain(root, deps),
|
|
861
|
+
brain: loadBrain(root, deps, scorecards),
|
|
830
862
|
swarlo: loadSwarlo(tasks),
|
|
831
863
|
loop: loadLoop(missions, root, deps),
|
|
832
864
|
};
|
|
833
|
-
osState.business = loadBusinessCollaboration(root, deps, osState.team);
|
|
865
|
+
osState.business = loadBusinessCollaboration(root, deps, osState.team, scorecards.length);
|
|
834
866
|
return { root, generated_at: new Date(nowMs).toISOString(), summary: summarize(tasks, missions, worktrees, agents), os: osState, next_action: nextAction(tasks, missions, worktrees, agents, osState), agents, tasks, missions, worktrees };
|
|
835
867
|
}
|
|
836
868
|
|