atris 3.15.51 → 3.15.53
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/features/README.md +10 -7
- package/commands/business.js +4 -2
- package/commands/computer.js +19 -3
- package/commands/mission.js +52 -7
- package/commands/play.js +1 -1
- package/commands/radar.js +91 -15
- package/commands/task.js +1 -1
- package/commands/worktree.js +20 -1
- package/package.json +1 -1
package/atris/features/README.md
CHANGED
|
@@ -79,17 +79,20 @@ cp atris/features/_templates/validate.md.template atris/features/your-feature-na
|
|
|
79
79
|
|
|
80
80
|
### Active Features
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
Close remaining audit gaps from self-audit
|
|
84
|
-
- **Files:** atris/team/*.md, atris/features/README.md
|
|
85
|
-
- **Status:** complete
|
|
86
|
-
- **Keywords:** audit, persona, cleanup
|
|
87
|
-
- **What:** Add PERSONA.md reference to all 5 agent specs, clean up stale feature statuses
|
|
82
|
+
None.
|
|
88
83
|
|
|
89
84
|
---
|
|
90
85
|
|
|
91
86
|
### Completed Features
|
|
92
87
|
|
|
88
|
+
#### audit-gaps
|
|
89
|
+
Close remaining audit gaps from self-audit
|
|
90
|
+
- **Files:** atris/team/*/MEMBER.md, atris/features/README.md, atris/features/audit-gaps/*
|
|
91
|
+
- **Status:** complete
|
|
92
|
+
- **Keywords:** audit, persona, cleanup
|
|
93
|
+
- **What:** Agent member specs reference PERSONA.md for communication style, and stale feature statuses are cleaned up
|
|
94
|
+
- **Completed:** 2026-05-19
|
|
95
|
+
|
|
93
96
|
#### endstate
|
|
94
97
|
Public benchmark for proving a coordinated stack beats a pinned single-model baseline
|
|
95
98
|
- **Files:** atris/features/endstate/*, commands/autopilot.js, commands/experiments.js, commands/loop.js, lib/wiki.js
|
|
@@ -116,7 +119,7 @@ Local-first project wiki with cloud opt-in
|
|
|
116
119
|
|
|
117
120
|
#### self-improving-loop
|
|
118
121
|
Make Atris recursive — validate.md lessons feed back into the next idea.md
|
|
119
|
-
- **Files:** atris/lessons.md (new), atris.md, atris/team/navigator.md, atris/team/validator.md, atris/MAP.md
|
|
122
|
+
- **Files:** atris/lessons.md (new), atris.md, atris/team/navigator/MEMBER.md, atris/team/validator/MEMBER.md, atris/MAP.md
|
|
120
123
|
- **Status:** complete
|
|
121
124
|
- **Keywords:** recursion, lessons, feedback-loop, self-improving, lessons.md
|
|
122
125
|
- **What:** lessons.md accumulates validated learnings; navigator reads them before planning; validator harvests them after validating
|
package/commands/business.js
CHANGED
|
@@ -909,6 +909,7 @@ function renderBusinessShareHandoff(state, options = {}) {
|
|
|
909
909
|
`Local path: ${workspacePath}`,
|
|
910
910
|
`Ready to share: ${state.ready ? 'yes' : 'no'}`,
|
|
911
911
|
`Remote pull: ${state.remoteReady ? 'available' : 'local-only'}`,
|
|
912
|
+
`Agent setup: ${(state.missingRootAgentAdapters && state.missingRootAgentAdapters.length) ? 'missing root agent adapters' : 'ready'}`,
|
|
912
913
|
'',
|
|
913
914
|
'## Get The Workspace',
|
|
914
915
|
'',
|
|
@@ -946,9 +947,9 @@ function renderBusinessShareHandoff(state, options = {}) {
|
|
|
946
947
|
'',
|
|
947
948
|
'## What To Read',
|
|
948
949
|
'',
|
|
950
|
+
`- Protocol: atris/atris.md`,
|
|
949
951
|
`- Map: atris/MAP.md`,
|
|
950
952
|
`- Queue: atris/TODO.md`,
|
|
951
|
-
`- Agent adapters: ${state.rootAgentAdapters && state.rootAgentAdapters.length ? state.rootAgentAdapters.join(', ') : 'missing'}`,
|
|
952
953
|
`- Team start: ${state.teamStart || 'missing'}`,
|
|
953
954
|
`- Starter brief: ${state.starterBrief || 'missing'}`,
|
|
954
955
|
`- First loop: ${state.firstLoop || 'missing'}`,
|
|
@@ -1014,11 +1015,12 @@ function renderBusinessStartCard(state, options = {}) {
|
|
|
1014
1015
|
`Workspace: ${workspacePath}`,
|
|
1015
1016
|
`Ready: ${state.ready ? 'yes' : 'no'}`,
|
|
1016
1017
|
`Remote pull: ${state.remoteReady ? 'available' : 'local-only'}`,
|
|
1018
|
+
`Agent setup: ${(state.missingRootAgentAdapters && state.missingRootAgentAdapters.length) ? 'missing root agent adapters' : 'ready'}`,
|
|
1017
1019
|
'',
|
|
1018
1020
|
'Read:',
|
|
1021
|
+
`- atris/atris.md`,
|
|
1019
1022
|
`- atris/MAP.md`,
|
|
1020
1023
|
`- atris/TODO.md`,
|
|
1021
|
-
`- ${(state.rootAgentAdapters && state.rootAgentAdapters.length === 3) ? state.rootAgentAdapters.join(', ') : 'missing root agent adapters'}`,
|
|
1022
1024
|
`- ${state.teamStart || 'missing team start guide'}`,
|
|
1023
1025
|
`- ${state.starterBrief || 'missing starter brief'}`,
|
|
1024
1026
|
`- ${state.firstLoop || 'missing first loop'}`,
|
package/commands/computer.js
CHANGED
|
@@ -1260,6 +1260,18 @@ function formatWorkspaceRef(workspace) {
|
|
|
1260
1260
|
return workspace.name ? `${workspace.name} (${workspace.id})` : workspace.id;
|
|
1261
1261
|
}
|
|
1262
1262
|
|
|
1263
|
+
function workspaceMatchesInput(workspace, input) {
|
|
1264
|
+
if (!workspace || !input) return false;
|
|
1265
|
+
const wanted = String(input).trim().toLowerCase();
|
|
1266
|
+
if (!wanted) return false;
|
|
1267
|
+
return String(workspace.id || '').toLowerCase() === wanted
|
|
1268
|
+
|| String(workspace.name || '').toLowerCase() === wanted;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
function resolveWorkspaceFromList(workspaces, input) {
|
|
1272
|
+
return (workspaces || []).find((workspace) => workspaceMatchesInput(workspace, input)) || null;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1263
1275
|
function formatLeaseAge(seconds) {
|
|
1264
1276
|
const value = Number(seconds);
|
|
1265
1277
|
if (!Number.isFinite(value) || value < 0) return '-';
|
|
@@ -1519,7 +1531,11 @@ async function computerStatus(token, ctx = null) {
|
|
|
1519
1531
|
if (d.endpoint) console.log(` Endpoint: ${d.endpoint}`);
|
|
1520
1532
|
const workspaces = await listBusinessWorkspaces(token, ctx);
|
|
1521
1533
|
const defaultWorkspace = workspaces.find((workspace) => workspace.is_default);
|
|
1522
|
-
const
|
|
1534
|
+
const resolvedTargetWorkspace = resolveWorkspaceFromList(workspaces, ctx.workspaceId);
|
|
1535
|
+
const targetWorkspace = resolvedTargetWorkspace || (ctx.workspaceId ? { id: ctx.workspaceId } : null);
|
|
1536
|
+
const probeCtx = resolvedTargetWorkspace?.id
|
|
1537
|
+
? { ...ctx, workspaceId: resolvedTargetWorkspace.id }
|
|
1538
|
+
: ctx;
|
|
1523
1539
|
console.log(` Default workspace: ${formatWorkspaceRef(defaultWorkspace)}`);
|
|
1524
1540
|
console.log(` Target workspace: ${formatWorkspaceRef(targetWorkspace)}`);
|
|
1525
1541
|
const attachedFromStatus = d.attached_workspace_id
|
|
@@ -1534,8 +1550,8 @@ async function computerStatus(token, ctx = null) {
|
|
|
1534
1550
|
console.log(` Lease age: ${formatLeaseAge(d.lease_age_seconds)}`);
|
|
1535
1551
|
if (d.takeover_hint) console.log(` Takeover hint: ${d.takeover_hint}`);
|
|
1536
1552
|
}
|
|
1537
|
-
if (status === 'running' && d.endpoint &&
|
|
1538
|
-
const attached = await probeAttachedWorkspace(token,
|
|
1553
|
+
if (status === 'running' && d.endpoint && probeCtx.workspaceId) {
|
|
1554
|
+
const attached = await probeAttachedWorkspace(token, probeCtx);
|
|
1539
1555
|
if (!attachedFromStatus) {
|
|
1540
1556
|
const attachedWorkspace = workspaces.find((workspace) => workspace.id === attached.workspaceId) || (attached.workspaceId ? { id: attached.workspaceId } : null);
|
|
1541
1557
|
console.log(` Attached workspace: ${formatWorkspaceRef(attachedWorkspace)}`);
|
package/commands/mission.js
CHANGED
|
@@ -748,12 +748,18 @@ function missionVerifierPassed(mission) {
|
|
|
748
748
|
|
|
749
749
|
function missionDueAt(mission, now = new Date()) {
|
|
750
750
|
const cadenceSeconds = parseCadenceSeconds(mission.cadence);
|
|
751
|
-
if (!mission.last_tick_at
|
|
751
|
+
if (!mission.last_tick_at) return true;
|
|
752
|
+
if (cadenceSeconds === 0) return !(mission.always_on && missionVerifierPassed(mission));
|
|
752
753
|
const lastTickAt = Date.parse(mission.last_tick_at);
|
|
753
754
|
if (!Number.isFinite(lastTickAt)) return true;
|
|
754
755
|
return now.getTime() - lastTickAt >= cadenceSeconds * 1000;
|
|
755
756
|
}
|
|
756
757
|
|
|
758
|
+
function missionSelectableForLoop(mission, now = new Date()) {
|
|
759
|
+
return missionIsRunnable(mission)
|
|
760
|
+
&& !(mission.always_on && missionVerifierPassed(mission) && !missionDueAt(mission, now));
|
|
761
|
+
}
|
|
762
|
+
|
|
757
763
|
function secondsUntilMissionDue(mission, now = new Date()) {
|
|
758
764
|
const cadenceSeconds = parseCadenceSeconds(mission?.cadence);
|
|
759
765
|
if (!mission || !mission.last_tick_at || cadenceSeconds === 0) return 0;
|
|
@@ -763,8 +769,15 @@ function secondsUntilMissionDue(mission, now = new Date()) {
|
|
|
763
769
|
return Math.max(0, Math.ceil((dueAt - now.getTime()) / 1000));
|
|
764
770
|
}
|
|
765
771
|
|
|
772
|
+
function missionHasHumanAsks(mission) {
|
|
773
|
+
return Array.isArray(mission?.human_asks)
|
|
774
|
+
&& mission.human_asks.some((ask) => String(ask || '').trim());
|
|
775
|
+
}
|
|
776
|
+
|
|
766
777
|
function missionIsRunnable(mission) {
|
|
767
|
-
return mission
|
|
778
|
+
return mission
|
|
779
|
+
&& GOAL_LOOP_STATUSES.has(String(mission.status || ''))
|
|
780
|
+
&& !missionHasHumanAsks(mission);
|
|
768
781
|
}
|
|
769
782
|
|
|
770
783
|
function missionSortTime(mission) {
|
|
@@ -773,7 +786,7 @@ function missionSortTime(mission) {
|
|
|
773
786
|
|
|
774
787
|
function selectDueMission(root = process.cwd(), now = new Date()) {
|
|
775
788
|
const candidates = listMissions(root)
|
|
776
|
-
.filter(
|
|
789
|
+
.filter((mission) => missionSelectableForLoop(mission, now))
|
|
777
790
|
.filter((mission) => mission.verifier)
|
|
778
791
|
.filter((mission) => mission.always_on || !missionVerifierPassed(mission))
|
|
779
792
|
.filter((mission) => missionDueAt(mission, now));
|
|
@@ -792,7 +805,7 @@ function selectDueMission(root = process.cwd(), now = new Date()) {
|
|
|
792
805
|
|
|
793
806
|
function selectCodexGoalMission(root = process.cwd(), now = new Date()) {
|
|
794
807
|
const candidates = listMissions(root)
|
|
795
|
-
.filter(
|
|
808
|
+
.filter((mission) => missionSelectableForLoop(mission, now));
|
|
796
809
|
|
|
797
810
|
candidates.sort((a, b) => {
|
|
798
811
|
const aCaller = runnerUsesCallerSession(a.runner) ? 1 : 0;
|
|
@@ -1176,7 +1189,8 @@ function spawnClaudeTick(mission, opts) {
|
|
|
1176
1189
|
exitCode: code,
|
|
1177
1190
|
sessionIds: Array.from(observedSessionIds),
|
|
1178
1191
|
result: finalText,
|
|
1179
|
-
summary: (finalText
|
|
1192
|
+
summary: usefulClaudeReceiptSummary(finalText, ok ? 'no-text' : 'error'),
|
|
1193
|
+
receipt_text: cappedClaudeReceiptText(finalText),
|
|
1180
1194
|
api_equivalent_estimate: costEstimate,
|
|
1181
1195
|
duration_api_ms: durationApiMs,
|
|
1182
1196
|
duration_total_ms: Date.now() - startedAt,
|
|
@@ -1196,6 +1210,33 @@ function spawnClaudeTick(mission, opts) {
|
|
|
1196
1210
|
});
|
|
1197
1211
|
}
|
|
1198
1212
|
|
|
1213
|
+
function stripClaudeReceiptLine(line) {
|
|
1214
|
+
return String(line || '')
|
|
1215
|
+
.trim()
|
|
1216
|
+
.replace(/^#{1,6}\s+/, '')
|
|
1217
|
+
.replace(/^[-*]\s+/, '')
|
|
1218
|
+
.replace(/^\d+[.)]\s+/, '')
|
|
1219
|
+
.trim();
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
function usefulClaudeReceiptSummary(text, fallback = 'no-text') {
|
|
1223
|
+
const lines = String(text || '').split(/\r?\n/);
|
|
1224
|
+
for (const line of lines) {
|
|
1225
|
+
const clean = stripClaudeReceiptLine(line);
|
|
1226
|
+
if (!clean) continue;
|
|
1227
|
+
if (/^(receipt|summary|final|final answer|result)$/i.test(clean)) continue;
|
|
1228
|
+
return clean.slice(0, 240);
|
|
1229
|
+
}
|
|
1230
|
+
return fallback;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function cappedClaudeReceiptText(text, limit = 4000) {
|
|
1234
|
+
const clean = String(text || '').trim();
|
|
1235
|
+
if (!clean) return '';
|
|
1236
|
+
if (clean.length <= limit) return clean;
|
|
1237
|
+
return clean.slice(0, limit - 16).trimEnd() + '\n...[truncated]';
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1199
1240
|
async function runMission(args) {
|
|
1200
1241
|
const asJson = wantsJson(args);
|
|
1201
1242
|
const dueMode = hasFlag(args, '--due');
|
|
@@ -1323,7 +1364,7 @@ async function runMission(args) {
|
|
|
1323
1364
|
if (mission.verifier !== frozen.verifier) { pauseReason = 'verifier-mutated'; break; }
|
|
1324
1365
|
if ((mission.lane || 'workspace') !== frozen.lane) { pauseReason = 'lane-mutated'; break; }
|
|
1325
1366
|
|
|
1326
|
-
const tickIdx =
|
|
1367
|
+
const tickIdx = Number(mission.last_tick_index || 0) + 1;
|
|
1327
1368
|
const tickStart = stampIso();
|
|
1328
1369
|
const tickWorktreeBefore = gitWorktreeSnapshot(cwd);
|
|
1329
1370
|
let result = { status: 'skipped', reason: 'unknown', tick_index: tickIdx, ran: false, started_at: tickStart };
|
|
@@ -1358,6 +1399,7 @@ async function runMission(args) {
|
|
|
1358
1399
|
result.claude = {
|
|
1359
1400
|
ok: claudeResult.ok,
|
|
1360
1401
|
summary: claudeResult.summary,
|
|
1402
|
+
receipt_text: claudeResult.receipt_text,
|
|
1361
1403
|
stop_reason: claudeResult.stop_reason,
|
|
1362
1404
|
api_equivalent_estimate: claudeResult.api_equivalent_estimate,
|
|
1363
1405
|
duration_total_ms: claudeResult.duration_total_ms,
|
|
@@ -1424,7 +1466,7 @@ async function runMission(args) {
|
|
|
1424
1466
|
});
|
|
1425
1467
|
|
|
1426
1468
|
const xpReadyAction = missionXpReadyAction(mission, receiptPath);
|
|
1427
|
-
const newStatus = (verifierResult?.passed && mission.always_on) ? '
|
|
1469
|
+
const newStatus = (verifierResult?.passed && mission.always_on) ? 'ready' :
|
|
1428
1470
|
(verifierResult?.passed && xpReadyAction) ? 'ready' :
|
|
1429
1471
|
(verifierResult?.passed && completeOnPass) ? 'complete' :
|
|
1430
1472
|
(verifierResult?.passed ? 'ready' :
|
|
@@ -1448,6 +1490,7 @@ async function runMission(args) {
|
|
|
1448
1490
|
last_tick_at: finishedAt,
|
|
1449
1491
|
last_tick_status: result.status,
|
|
1450
1492
|
last_tick_reason: result.reason,
|
|
1493
|
+
last_tick_index: tickIdx,
|
|
1451
1494
|
verifier_result: verifierResult || mission.verifier_result || null,
|
|
1452
1495
|
receipt_path: receiptPath,
|
|
1453
1496
|
next_action: nextAction,
|
|
@@ -1915,4 +1958,6 @@ module.exports = {
|
|
|
1915
1958
|
renderMissionStatus,
|
|
1916
1959
|
selectDueMission,
|
|
1917
1960
|
selectCodexGoalMission,
|
|
1961
|
+
usefulClaudeReceiptSummary,
|
|
1962
|
+
cappedClaudeReceiptText,
|
|
1918
1963
|
};
|
package/commands/play.js
CHANGED
|
@@ -327,7 +327,7 @@ function playWorkspaceRoot(taskDb, workspaceArg) {
|
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
function nextCommands(task, player) {
|
|
330
|
-
const helper = '
|
|
330
|
+
const helper = player || 'player';
|
|
331
331
|
if (!task) {
|
|
332
332
|
return [
|
|
333
333
|
`atris task delegate "AgentXP first rep: one proof-backed mission" --to ${player} --tag agent-xp`,
|
package/commands/radar.js
CHANGED
|
@@ -114,18 +114,26 @@ function loadTasks(root, deps) {
|
|
|
114
114
|
if (!deps.exists(file)) return [];
|
|
115
115
|
const payload = safeJson(deps.readFile(file, 'utf8'), {});
|
|
116
116
|
const tasks = Array.isArray(payload.tasks) ? payload.tasks : [];
|
|
117
|
-
return tasks.map(task =>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
117
|
+
return tasks.map(task => {
|
|
118
|
+
const messages = Array.isArray(task.messages) ? task.messages : [];
|
|
119
|
+
const row = {
|
|
120
|
+
id: task.id,
|
|
121
|
+
display_id: task.display_id,
|
|
122
|
+
legacy_ref: task.legacy_ref,
|
|
123
|
+
title: task.title,
|
|
124
|
+
status: task.status,
|
|
125
|
+
tag: task.tag,
|
|
126
|
+
workspace_root: task.workspace_root,
|
|
127
|
+
claimed_by: task.claimed_by,
|
|
128
|
+
assigned_to: task.metadata?.assigned_to || task.assigned_to || null,
|
|
129
|
+
metadata: task.metadata || {},
|
|
130
|
+
};
|
|
131
|
+
Object.defineProperty(row, 'routing_text', {
|
|
132
|
+
enumerable: false,
|
|
133
|
+
value: messages.map(message => message && message.content).filter(Boolean).join(' '),
|
|
134
|
+
});
|
|
135
|
+
return row;
|
|
136
|
+
});
|
|
129
137
|
}
|
|
130
138
|
|
|
131
139
|
function findTaskWorkspaceRoot(cwd, deps) {
|
|
@@ -424,6 +432,31 @@ function ownerForTask(task) {
|
|
|
424
432
|
return task.assigned_to || task.claimed_by || task.metadata?.assigned_to || '-';
|
|
425
433
|
}
|
|
426
434
|
|
|
435
|
+
function taskSessionReason(task) {
|
|
436
|
+
if (!task) return null;
|
|
437
|
+
if (task.status === 'review') return task.metadata?.agent_certified ? 'certified review' : 'review task';
|
|
438
|
+
if (ownerActionRequired(task)) return 'owner action required';
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function taskSessionAction(agent, task, taskWorkspaceRoot) {
|
|
443
|
+
if (!task) return null;
|
|
444
|
+
const ref = taskRef(task);
|
|
445
|
+
const pid = agent?.pid || '?';
|
|
446
|
+
const actor = agent?.agent || 'agent';
|
|
447
|
+
const reviewCommand = taskWorkspaceRoot
|
|
448
|
+
? `cd ${shellQuote(taskWorkspaceRoot)} && atris task reviews --limit 5`
|
|
449
|
+
: 'atris task reviews --limit 5';
|
|
450
|
+
if (task.status === 'review') {
|
|
451
|
+
if (task.metadata?.agent_certified) {
|
|
452
|
+
return `handoff complete for ${ref}; close pid ${pid} or claim fresh work as ${actor}`;
|
|
453
|
+
}
|
|
454
|
+
return `review or hand off ${ref}: ${reviewCommand}`;
|
|
455
|
+
}
|
|
456
|
+
if (ownerActionRequired(task)) return `owner-gated ${ref}; close pid ${pid} or wait for owner action`;
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
427
460
|
function summarize(tasks, missions, worktrees, agents) {
|
|
428
461
|
const count = (rows, pred) => rows.filter(pred).length;
|
|
429
462
|
return {
|
|
@@ -439,9 +472,31 @@ function summarize(tasks, missions, worktrees, agents) {
|
|
|
439
472
|
};
|
|
440
473
|
}
|
|
441
474
|
|
|
475
|
+
function ownerActionRequired(task) {
|
|
476
|
+
const metadata = task?.metadata || {};
|
|
477
|
+
if (metadata.owner_action_required === true || metadata.agent_executable === false) return true;
|
|
478
|
+
if (String(metadata.agent_executable || '').toLowerCase() === 'false') return true;
|
|
479
|
+
const text = [
|
|
480
|
+
task?.title,
|
|
481
|
+
task?.tag,
|
|
482
|
+
metadata.status,
|
|
483
|
+
metadata.blocker,
|
|
484
|
+
metadata.human_revision_note,
|
|
485
|
+
metadata.latest_agent_proof,
|
|
486
|
+
metadata.latest_agent_lesson,
|
|
487
|
+
task?.routing_text,
|
|
488
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
489
|
+
if (text.includes('owner_action_required') || text.includes('agent_executable=false')) return true;
|
|
490
|
+
if (text.includes('owner-only') || text.includes('owner only') || text.includes('not agent-executable')) return true;
|
|
491
|
+
return text.includes('owner') && text.includes('billing') && (text.includes('spending limit') || text.includes('failed payments'));
|
|
492
|
+
}
|
|
493
|
+
|
|
442
494
|
function nextAction(tasks, missions, worktrees, agents, os = {}) {
|
|
443
|
-
const
|
|
495
|
+
const activeTasks = tasks.filter(t => t.status === 'claimed' || t.status === 'open');
|
|
496
|
+
const activeTask = activeTasks.find(t => !ownerActionRequired(t));
|
|
444
497
|
if (activeTask) return `work ${taskRef(activeTask)}: ${activeTask.title || 'active task'}`;
|
|
498
|
+
const ownerTask = activeTasks.find(ownerActionRequired);
|
|
499
|
+
if (ownerTask) return `owner action required ${taskRef(ownerTask)}: ${ownerTask.title || 'owner-only task'}`;
|
|
445
500
|
const needsReview = tasks.find(t => t.status === 'review' && !(t.metadata && t.metadata.agent_certified));
|
|
446
501
|
if (needsReview) return `review ${taskRef(needsReview)}: ${needsReview.title || 'uncertified review task'}`;
|
|
447
502
|
const certified = tasks.find(t => t.status === 'review' && t.metadata && t.metadata.agent_certified);
|
|
@@ -475,7 +530,7 @@ function collectRadar(options = {}) {
|
|
|
475
530
|
const taskWorkspaceRoot = findTaskWorkspaceRoot(agent.cwd, deps);
|
|
476
531
|
const agentTasks = taskWorkspaceRoot ? loadTasksCached(taskWorkspaceRoot, deps, taskCache) : [];
|
|
477
532
|
const task = taskForCwd(agentTasks, agent.cwd, taskWorkspaceRoot);
|
|
478
|
-
const taskReason = task ?
|
|
533
|
+
const taskReason = task ? taskSessionReason(task) : untaskedReason(agent, taskWorkspaceRoot, agentTasks);
|
|
479
534
|
return {
|
|
480
535
|
...agent,
|
|
481
536
|
task: taskRef(task),
|
|
@@ -483,7 +538,7 @@ function collectRadar(options = {}) {
|
|
|
483
538
|
owner: ownerForTask(task),
|
|
484
539
|
task_workspace: taskWorkspaceRoot ? repoLabel(taskWorkspaceRoot) : null,
|
|
485
540
|
task_reason: taskReason,
|
|
486
|
-
task_action: task ?
|
|
541
|
+
task_action: task ? taskSessionAction(agent, task, taskWorkspaceRoot) : untaskedAction(agent, taskWorkspaceRoot, agentTasks),
|
|
487
542
|
};
|
|
488
543
|
});
|
|
489
544
|
const osState = {
|
|
@@ -594,6 +649,11 @@ function sortedAgents(agents = []) {
|
|
|
594
649
|
function agentProcessNextAction(agents = [], fallback = 'no obvious process action') {
|
|
595
650
|
const stopped = agents.filter(agent => agent.status !== 'active').length;
|
|
596
651
|
if (stopped > 0) return `inspect ${stopped} stopped agent session${stopped === 1 ? '' : 's'}`;
|
|
652
|
+
const ownerGated = agents.filter(agent => agent.task_reason === 'owner action required');
|
|
653
|
+
if (ownerGated.length > 0) {
|
|
654
|
+
const tasks = [...new Set(ownerGated.map(agent => agent.task).filter(Boolean))].slice(0, 3).join(', ');
|
|
655
|
+
return `owner gate blocks ${ownerGated.length} session${ownerGated.length === 1 ? '' : 's'}${tasks ? ` on ${tasks}` : ''}; wait for owner action or close idle sessions`;
|
|
656
|
+
}
|
|
597
657
|
const taskLoad = summarizeTaskLoad(agents);
|
|
598
658
|
const reviewBound = taskLoad.find(row => row.status.split(/,\s*/).includes('review'));
|
|
599
659
|
if (reviewBound) return `close or hand off ${reviewBound.sessions} session${reviewBound.sessions === 1 ? '' : 's'} still bound to review task ${reviewBound.task}`;
|
|
@@ -714,6 +774,22 @@ function renderAgentTop(data) {
|
|
|
714
774
|
lines.push(`- ${agent.pid} ${agent.repo || agent.cwd || '-'}: ${agent.task_reason || 'unmapped'} -> ${agent.task_action || 'inspect session'}`);
|
|
715
775
|
}
|
|
716
776
|
}
|
|
777
|
+
const ownerGatedAgents = payload.agents.filter(row => row.task_reason === 'owner action required').slice(0, 8);
|
|
778
|
+
if (ownerGatedAgents.length) {
|
|
779
|
+
lines.push('');
|
|
780
|
+
lines.push(`Owner-gated: ${ownerGatedAgents.length} session${ownerGatedAgents.length === 1 ? '' : 's'} waiting on owner-only tasks.`);
|
|
781
|
+
for (const agent of ownerGatedAgents) {
|
|
782
|
+
lines.push(`- ${agent.pid} ${agent.repo || agent.cwd || '-'} ${agent.task}: ${agent.task_action || 'wait for owner action'}`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
const reviewBoundAgents = payload.agents.filter(row => row.task_status === 'review').slice(0, 8);
|
|
786
|
+
if (reviewBoundAgents.length) {
|
|
787
|
+
lines.push('');
|
|
788
|
+
lines.push(`Review-bound: ${reviewBoundAgents.length} session${reviewBoundAgents.length === 1 ? '' : 's'} still tied to Review tasks.`);
|
|
789
|
+
for (const agent of reviewBoundAgents) {
|
|
790
|
+
lines.push(`- ${agent.pid} ${agent.repo || agent.cwd || '-'} ${agent.task}: ${agent.task_reason || 'review'} -> ${agent.task_action || 'close or hand off session'}`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
717
793
|
const taskLoadRows = payload.task_load.filter(row => row.attention).slice(0, 8);
|
|
718
794
|
if (taskLoadRows.length) {
|
|
719
795
|
lines.push('');
|
package/commands/task.js
CHANGED
|
@@ -1708,7 +1708,7 @@ function cmdReady(args) {
|
|
|
1708
1708
|
next_action: agentCertified ? 'continue_work' : 'agent_review_again',
|
|
1709
1709
|
rule: agentCertified
|
|
1710
1710
|
? 'Agent double-check complete; continue work. AgentXP waits for human accept.'
|
|
1711
|
-
: 'Proof is in Review;
|
|
1711
|
+
: 'Proof is in Review; human accept can award AgentXP now. A second agent review only certifies autonomous continuation.',
|
|
1712
1712
|
};
|
|
1713
1713
|
if (wantsJson(args)) {
|
|
1714
1714
|
printJson({
|
package/commands/worktree.js
CHANGED
|
@@ -285,6 +285,12 @@ function createOrFindPr(root, branch, targetRef, title, dryRun) {
|
|
|
285
285
|
return created.stdout.trim();
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
function prMergeRef(prOutput) {
|
|
289
|
+
const text = String(prOutput || '').trim();
|
|
290
|
+
if (!text || text.startsWith('dry-run:')) return '';
|
|
291
|
+
return text.split(/\s+/)[0];
|
|
292
|
+
}
|
|
293
|
+
|
|
288
294
|
function shipWorktree(args) {
|
|
289
295
|
const root = repoRoot();
|
|
290
296
|
const dryRun = hasFlag(args, '--dry-run');
|
|
@@ -359,8 +365,20 @@ function shipWorktree(args) {
|
|
|
359
365
|
if (merge) {
|
|
360
366
|
console.log('merge: requested');
|
|
361
367
|
if (!dryRun) {
|
|
362
|
-
const
|
|
368
|
+
const mergeRef = prMergeRef(pr);
|
|
369
|
+
const mergeArgs = ['pr', 'merge'];
|
|
370
|
+
if (mergeRef) mergeArgs.push(mergeRef);
|
|
371
|
+
mergeArgs.push('--merge');
|
|
372
|
+
const merged = spawnSync('gh', mergeArgs, { cwd: root, encoding: 'utf8' });
|
|
363
373
|
if (merged.status !== 0) throw new Error((merged.stderr || merged.stdout || 'gh pr merge failed').trim());
|
|
374
|
+
console.log('merge: merged');
|
|
375
|
+
const deleted = runGit(['push', 'origin', '--delete', branch], { cwd: root, check: false });
|
|
376
|
+
if (deleted.status === 0) {
|
|
377
|
+
console.log(`merge: remote branch deleted ${branch}`);
|
|
378
|
+
} else {
|
|
379
|
+
const deleteOutput = (deleted.stderr || deleted.stdout || 'remote branch delete failed').trim();
|
|
380
|
+
console.log(`merge: remote branch delete skipped: ${deleteOutput}`);
|
|
381
|
+
}
|
|
364
382
|
}
|
|
365
383
|
}
|
|
366
384
|
} else {
|
|
@@ -461,6 +479,7 @@ module.exports = {
|
|
|
461
479
|
defaultWorktreePath,
|
|
462
480
|
parseWorktrees,
|
|
463
481
|
normalizeTargetRef,
|
|
482
|
+
prMergeRef,
|
|
464
483
|
slugify,
|
|
465
484
|
statusCounts,
|
|
466
485
|
swarloClaim,
|