atris 3.68.2 → 3.68.3
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 +1 -1
- package/atris/team/customer-lead/MEMBER.md +1 -1
- package/atris/team/executor/MEMBER.md +19 -18
- package/atris/team/executor/skills/isolated-slice-builder/SKILL.md +21 -0
- package/atris/team/navigator/MEMBER.md +3 -1
- package/atris/team/navigator/skills/loophole-proof-planner/SKILL.md +20 -0
- package/atris/team/researcher/MEMBER.md +2 -1
- package/atris/team/researcher/skills/grounded-evidence-brief/SKILL.md +19 -0
- package/atris/team/validator/MEMBER.md +2 -7
- package/atris/team/validator/skills/cross-model-referee/SKILL.md +20 -0
- package/commands/autoland.js +2 -1
- package/commands/engine.js +2 -2
- package/commands/goal.js +61 -2
- package/commands/land.js +34 -7
- package/commands/member.js +1542 -41
- package/commands/mission.js +506 -18
- package/commands/task.js +26 -3
- package/commands/worktree.js +144 -34
- package/lib/auto-accept-certified.js +7 -6
- package/lib/codex-flight.js +6 -12
- package/lib/conductor-artifacts.js +3 -1
- package/lib/default-runner.js +42 -7
- package/lib/engine-ask.js +8 -1
- package/lib/engine-registry.js +17 -0
- package/lib/fleet.js +163 -88
- package/lib/member-alive.js +1 -1
- package/lib/member-approval.js +153 -0
- package/lib/mission-task-gate.js +170 -0
- package/lib/one-lap-validator.js +2 -1
- package/lib/roster-runs.js +28 -0
- package/lib/runner-command.js +32 -8
- package/lib/task-db.js +25 -1
- package/package.json +5 -1
- package/scripts/det/ytnotes +49 -10
package/commands/mission.js
CHANGED
|
@@ -103,6 +103,8 @@ const {
|
|
|
103
103
|
normalizeHumanAsks,
|
|
104
104
|
openHumanAsks,
|
|
105
105
|
} = require('../lib/mission-human-asks');
|
|
106
|
+
const { missionLinkedTaskGate } = require('../lib/mission-task-gate');
|
|
107
|
+
const { memberApprovalAsks, runBlockingAsks } = require('../lib/member-approval');
|
|
106
108
|
const {
|
|
107
109
|
inspectMissionProtectedDiff,
|
|
108
110
|
prepareMissionGitGuard,
|
|
@@ -730,6 +732,7 @@ const MISSION_RUN_VALUE_FLAGS = [
|
|
|
730
732
|
'--stop',
|
|
731
733
|
'--model',
|
|
732
734
|
'--engine',
|
|
735
|
+
'--referee-engine',
|
|
733
736
|
'--budget',
|
|
734
737
|
'--repo',
|
|
735
738
|
'--native-goal-status',
|
|
@@ -750,6 +753,8 @@ const MISSION_RUN_BOOLEAN_FLAGS = [
|
|
|
750
753
|
'--land',
|
|
751
754
|
'--no-claude',
|
|
752
755
|
'--no-verify',
|
|
756
|
+
'--referee',
|
|
757
|
+
'--no-referee',
|
|
753
758
|
'--complete-on-pass',
|
|
754
759
|
'--no-drain',
|
|
755
760
|
'--create-next',
|
|
@@ -1621,6 +1626,28 @@ function appendJsonLine(file, payload) {
|
|
|
1621
1626
|
fs.appendFileSync(file, JSON.stringify(payload) + '\n', 'utf8');
|
|
1622
1627
|
}
|
|
1623
1628
|
|
|
1629
|
+
function resolveMemberWorkspaceRoot(root = process.cwd()) {
|
|
1630
|
+
const resolved = path.resolve(root || process.cwd());
|
|
1631
|
+
const agentWorktreesMarker = `${path.sep}.agent-worktrees${path.sep}`;
|
|
1632
|
+
const agentIdx = resolved.indexOf(agentWorktreesMarker);
|
|
1633
|
+
if (agentIdx > 0) {
|
|
1634
|
+
const parentDir = resolved.slice(0, agentIdx);
|
|
1635
|
+
const after = resolved.slice(agentIdx + agentWorktreesMarker.length);
|
|
1636
|
+
const repoName = after.split(path.sep)[0];
|
|
1637
|
+
if (repoName) {
|
|
1638
|
+
const primaryRoot = path.join(parentDir, repoName);
|
|
1639
|
+
if (fs.existsSync(path.join(primaryRoot, 'atris'))) return primaryRoot;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
const marker = `${path.sep}.atris${path.sep}worktrees${path.sep}`;
|
|
1643
|
+
const idx = resolved.indexOf(marker);
|
|
1644
|
+
if (idx > 0) {
|
|
1645
|
+
const parentRoot = resolved.slice(0, idx);
|
|
1646
|
+
if (fs.existsSync(path.join(parentRoot, 'atris'))) return parentRoot;
|
|
1647
|
+
}
|
|
1648
|
+
return resolved;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1624
1651
|
function appendEvent(type, mission, payload = {}, root = process.cwd()) {
|
|
1625
1652
|
const paths = statePaths(root);
|
|
1626
1653
|
const event = {
|
|
@@ -1632,6 +1659,12 @@ function appendEvent(type, mission, payload = {}, root = process.cwd()) {
|
|
|
1632
1659
|
payload,
|
|
1633
1660
|
};
|
|
1634
1661
|
appendJsonLine(paths.eventsJsonl, event);
|
|
1662
|
+
const parentRoot = resolveMemberWorkspaceRoot(root);
|
|
1663
|
+
if (type === 'mission_tick' && parentRoot !== path.resolve(root || process.cwd())) {
|
|
1664
|
+
try {
|
|
1665
|
+
appendJsonLine(statePaths(parentRoot).eventsJsonl, event);
|
|
1666
|
+
} catch {}
|
|
1667
|
+
}
|
|
1635
1668
|
return event;
|
|
1636
1669
|
}
|
|
1637
1670
|
|
|
@@ -1662,9 +1695,15 @@ function saveMission(mission, root = process.cwd(), eventType = 'mission_updated
|
|
|
1662
1695
|
|
|
1663
1696
|
function memberDir(owner, root = process.cwd()) {
|
|
1664
1697
|
if (!owner || !/^[a-zA-Z0-9._-]+$/.test(owner)) return null;
|
|
1665
|
-
const
|
|
1666
|
-
|
|
1667
|
-
|
|
1698
|
+
const parentRoot = resolveMemberWorkspaceRoot(root);
|
|
1699
|
+
const candidates = parentRoot !== path.resolve(root || process.cwd())
|
|
1700
|
+
? [parentRoot, root]
|
|
1701
|
+
: [root];
|
|
1702
|
+
for (const base of candidates) {
|
|
1703
|
+
const dir = path.join(base, 'atris', 'team', owner);
|
|
1704
|
+
if (fs.existsSync(path.join(dir, 'MEMBER.md'))) return dir;
|
|
1705
|
+
}
|
|
1706
|
+
return null;
|
|
1668
1707
|
}
|
|
1669
1708
|
|
|
1670
1709
|
function appendMemberLog(owner, title, fields = {}, root = process.cwd()) {
|
|
@@ -1686,6 +1725,158 @@ function appendMemberLog(owner, title, fields = {}, root = process.cwd()) {
|
|
|
1686
1725
|
return logPath;
|
|
1687
1726
|
}
|
|
1688
1727
|
|
|
1728
|
+
function crystallizeMemberSkillLesson(owner, entry = {}, root = process.cwd()) {
|
|
1729
|
+
const dir = memberDir(owner, root);
|
|
1730
|
+
if (!dir) return null;
|
|
1731
|
+
const summary = String(entry.summary || entry.objective || '').replace(/\s+/g, ' ').trim();
|
|
1732
|
+
if (!summary) return null;
|
|
1733
|
+
const skillDir = path.join(dir, 'skills', 'auto-learned');
|
|
1734
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
1735
|
+
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
1736
|
+
const existingBullets = [];
|
|
1737
|
+
if (fs.existsSync(skillPath)) {
|
|
1738
|
+
try {
|
|
1739
|
+
const raw = fs.readFileSync(skillPath, 'utf8');
|
|
1740
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
1741
|
+
if (line.trim().startsWith('- **')) existingBullets.push(line.trim());
|
|
1742
|
+
}
|
|
1743
|
+
} catch {}
|
|
1744
|
+
}
|
|
1745
|
+
const verifierText = String(entry.verifier || '').replace(/\s+/g, ' ').trim();
|
|
1746
|
+
const nextPushText = String(entry.next_push || '').replace(/\s+/g, ' ').trim();
|
|
1747
|
+
const layerLabel = String(entry.layer || 'capabilities').trim();
|
|
1748
|
+
const parts = [`- **${todayName()} (${layerLabel})**: ${summary.slice(0, 140)}`];
|
|
1749
|
+
if (verifierText) parts.push(`Verify: \`${verifierText.slice(0, 80)}\``);
|
|
1750
|
+
if (nextPushText) parts.push(`Next push: ${nextPushText.slice(0, 120)}`);
|
|
1751
|
+
const newBullet = parts.join(' -> ');
|
|
1752
|
+
const deduped = [
|
|
1753
|
+
newBullet,
|
|
1754
|
+
...existingBullets.filter((b) => !b.includes(summary.slice(0, 80))),
|
|
1755
|
+
].slice(0, 5);
|
|
1756
|
+
|
|
1757
|
+
const content = [
|
|
1758
|
+
'---',
|
|
1759
|
+
'name: auto-learned',
|
|
1760
|
+
`description: Verified patterns crystallized from passing runs for ${owner}. Bounded to 5 newest patterns so context never bloats.`,
|
|
1761
|
+
'---',
|
|
1762
|
+
'',
|
|
1763
|
+
'# Auto-Learned Verified Patterns',
|
|
1764
|
+
'',
|
|
1765
|
+
...deduped,
|
|
1766
|
+
'',
|
|
1767
|
+
].join('\n');
|
|
1768
|
+
fs.writeFileSync(skillPath, content, 'utf8');
|
|
1769
|
+
return skillPath;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
function autoQueueMemberNextPush(owner, nextPush, parentObjective = '', root = process.cwd(), { promote = false } = {}) {
|
|
1773
|
+
const cleanPush = String(nextPush || '').replace(/\s+/g, ' ').trim();
|
|
1774
|
+
if (!cleanPush) return null;
|
|
1775
|
+
const dir = memberDir(owner, root);
|
|
1776
|
+
if (!dir) return null;
|
|
1777
|
+
const goalsPath = path.join(dir, 'goals.json');
|
|
1778
|
+
let store = { schema_version: 1, member: owner, updated_at: stampIso(), goals: [] };
|
|
1779
|
+
if (fs.existsSync(goalsPath)) {
|
|
1780
|
+
try {
|
|
1781
|
+
const parsed = JSON.parse(fs.readFileSync(goalsPath, 'utf8'));
|
|
1782
|
+
if (parsed && Array.isArray(parsed.goals)) store = parsed;
|
|
1783
|
+
} catch {}
|
|
1784
|
+
}
|
|
1785
|
+
const now = stampIso();
|
|
1786
|
+
const activeIdx = store.goals.findIndex((g) => g && g.status === 'active');
|
|
1787
|
+
if (activeIdx >= 0) {
|
|
1788
|
+
const prev = store.goals[activeIdx];
|
|
1789
|
+
store.goals[activeIdx] = {
|
|
1790
|
+
...prev,
|
|
1791
|
+
...(promote ? { previous_title: prev.title, title: cleanPush } : {}),
|
|
1792
|
+
next_push: cleanPush,
|
|
1793
|
+
updated_at: now,
|
|
1794
|
+
};
|
|
1795
|
+
} else {
|
|
1796
|
+
store.goals.push({
|
|
1797
|
+
id: `goal_${Date.now().toString(36)}`,
|
|
1798
|
+
member: owner,
|
|
1799
|
+
title: cleanPush,
|
|
1800
|
+
why: parentObjective ? `Compounds after verified win on: ${parentObjective}` : 'Self-proposed next optimization from verified run.',
|
|
1801
|
+
metric: 'verified_slices_shipped',
|
|
1802
|
+
target: '1',
|
|
1803
|
+
horizon: '7d',
|
|
1804
|
+
acceptance: ['Passes test check and cross-model referee'],
|
|
1805
|
+
next_push: cleanPush,
|
|
1806
|
+
status: 'active',
|
|
1807
|
+
created_at: now,
|
|
1808
|
+
updated_at: now,
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
store.updated_at = now;
|
|
1812
|
+
fs.writeFileSync(goalsPath, `${JSON.stringify(store, null, 2)}\n`, 'utf8');
|
|
1813
|
+
return goalsPath;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
const DEFAULT_MEMBER_BATON_RELAY = {
|
|
1817
|
+
researcher: 'navigator',
|
|
1818
|
+
'customer-lead': 'navigator',
|
|
1819
|
+
navigator: 'executor',
|
|
1820
|
+
architect: 'executor',
|
|
1821
|
+
executor: 'validator',
|
|
1822
|
+
'auto-improver': 'validator',
|
|
1823
|
+
};
|
|
1824
|
+
|
|
1825
|
+
function recordMemberHandoff(fromMember, toMember, sliceSummary, root = process.cwd(), extra = {}) {
|
|
1826
|
+
const from = String(fromMember || '').trim().toLowerCase();
|
|
1827
|
+
const to = String(toMember || DEFAULT_MEMBER_BATON_RELAY[from] || '').trim().toLowerCase();
|
|
1828
|
+
const cleanSummary = String(sliceSummary || '').replace(/\s+/g, ' ').trim();
|
|
1829
|
+
if (!from || !to || !cleanSummary) return null;
|
|
1830
|
+
const parentRoot = resolveMemberWorkspaceRoot(root);
|
|
1831
|
+
if (!memberDir(to, parentRoot)) return null;
|
|
1832
|
+
const now = stampIso();
|
|
1833
|
+
const handoff = {
|
|
1834
|
+
id: `handoff_${Date.now().toString(36)}`,
|
|
1835
|
+
from,
|
|
1836
|
+
to,
|
|
1837
|
+
summary: cleanSummary.slice(0, 180),
|
|
1838
|
+
status: extra.status || 'open',
|
|
1839
|
+
next_command: `atris member go ${to}`,
|
|
1840
|
+
created_at: now,
|
|
1841
|
+
};
|
|
1842
|
+
try {
|
|
1843
|
+
const handoffsFile = path.join(parentRoot, '.atris', 'state', 'member-handoffs.jsonl');
|
|
1844
|
+
fs.mkdirSync(path.dirname(handoffsFile), { recursive: true });
|
|
1845
|
+
fs.appendFileSync(handoffsFile, `${JSON.stringify(handoff)}\n`, 'utf8');
|
|
1846
|
+
autoQueueMemberNextPush(to, `${from} handoff: ${cleanSummary}`, `Baton pass from ${from}`, parentRoot);
|
|
1847
|
+
} catch {}
|
|
1848
|
+
return handoff;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
function closeMemberHandoffsForReceiver(receiverMember, root = process.cwd()) {
|
|
1852
|
+
const receiver = String(receiverMember || '').trim().toLowerCase();
|
|
1853
|
+
if (!receiver) return 0;
|
|
1854
|
+
const parentRoot = resolveMemberWorkspaceRoot(root);
|
|
1855
|
+
const handoffsFile = path.join(parentRoot, '.atris', 'state', 'member-handoffs.jsonl');
|
|
1856
|
+
if (!fs.existsSync(handoffsFile)) return 0;
|
|
1857
|
+
try {
|
|
1858
|
+
const rawLines = fs.readFileSync(handoffsFile, 'utf8').split(/\r?\n/).filter((l) => l.trim());
|
|
1859
|
+
let closedCount = 0;
|
|
1860
|
+
const now = stampIso();
|
|
1861
|
+
const updatedLines = rawLines.map((line) => {
|
|
1862
|
+
try {
|
|
1863
|
+
const row = JSON.parse(line);
|
|
1864
|
+
if (row && String(row.to || '').toLowerCase() === receiver && (row.status || 'open') === 'open') {
|
|
1865
|
+
closedCount++;
|
|
1866
|
+
return JSON.stringify({ ...row, status: 'closed', closed_at: now });
|
|
1867
|
+
}
|
|
1868
|
+
} catch {}
|
|
1869
|
+
return line;
|
|
1870
|
+
});
|
|
1871
|
+
if (closedCount > 0) {
|
|
1872
|
+
fs.writeFileSync(handoffsFile, `${updatedLines.join('\n')}\n`, 'utf8');
|
|
1873
|
+
}
|
|
1874
|
+
return closedCount;
|
|
1875
|
+
} catch {
|
|
1876
|
+
return 0;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1689
1880
|
function memberMissionFile(owner, root = process.cwd()) {
|
|
1690
1881
|
const dir = memberDir(owner, root);
|
|
1691
1882
|
if (!dir) return null;
|
|
@@ -2940,6 +3131,7 @@ function missionFromArgs(args) {
|
|
|
2940
3131
|
'--cadence',
|
|
2941
3132
|
'--loop',
|
|
2942
3133
|
'--runner',
|
|
3134
|
+
'--engine',
|
|
2943
3135
|
'--lane',
|
|
2944
3136
|
'--verify',
|
|
2945
3137
|
'--stop',
|
|
@@ -2968,7 +3160,10 @@ function missionFromArgs(args) {
|
|
|
2968
3160
|
const maxTicksOverride = readPositiveIntegerFlag(args, '--max-ticks', null, { json: wantsJson(args) });
|
|
2969
3161
|
const requestedOwner = readFlag(args, '--owner', process.env.ATRIS_AGENT_ID || 'mission-lead');
|
|
2970
3162
|
const cadence = readFlag(args, '--cadence', readFlag(args, '--loop', 'manual')) || 'manual';
|
|
2971
|
-
const
|
|
3163
|
+
const engineFlag = readFlag(args, '--engine', '');
|
|
3164
|
+
const runnerSelection = engineFlag
|
|
3165
|
+
? resolveMissionRunnerSelection(engineFlag, { asJson: wantsJson(args), engineOnly: true, label: 'engine' })
|
|
3166
|
+
: resolveMissionRunnerSelection(readFlag(args, '--runner', 'manual'), { asJson: wantsJson(args), label: 'runner' });
|
|
2972
3167
|
const runner = runnerSelection.runner;
|
|
2973
3168
|
const model = readFlag(args, '--model', '') || (String(runner).toLowerCase() === 'atris2' ? 'atris:fast' : '');
|
|
2974
3169
|
const lane = readFlag(args, '--lane', 'workspace');
|
|
@@ -2985,7 +3180,8 @@ function missionFromArgs(args) {
|
|
|
2985
3180
|
assertMissionVerifier(verifier, wantsJson(args));
|
|
2986
3181
|
const stopCondition = readFlag(args, '--stop', budgetStopCondition(budgetContract) || (verifier ? 'verifier passes and no human asks remain' : 'human marks complete with proof'));
|
|
2987
3182
|
const taskIds = readRepeatedFlag(args, '--task');
|
|
2988
|
-
|
|
3183
|
+
// MEMBER.md approval-required becomes an open human ask the owner must clear.
|
|
3184
|
+
const humanAsks = [...readRepeatedFlag(args, '--ask'), ...memberApprovalAsks(owner, process.cwd(), objective)];
|
|
2989
3185
|
const drive = missionDrive(readFlag(args, '--destination', ''));
|
|
2990
3186
|
const alwaysOn = hasFlag(args, '--always-on');
|
|
2991
3187
|
const xpTaskEnabled = hasFlag(args, '--xp-task') || hasFlag(args, '--agent-xp');
|
|
@@ -8612,6 +8808,101 @@ function consumeMissionPings(mission, cwd) {
|
|
|
8612
8808
|
return { mission: saved, pings: pending };
|
|
8613
8809
|
}
|
|
8614
8810
|
|
|
8811
|
+
function readBoundedContextFile(filePath, maxChars = 1600) {
|
|
8812
|
+
try {
|
|
8813
|
+
if (!fs.existsSync(filePath)) return '';
|
|
8814
|
+
// Frontmatter is machine config (skills, permissions); the prompt only
|
|
8815
|
+
// needs the body.
|
|
8816
|
+
const raw = fs.readFileSync(filePath, 'utf8')
|
|
8817
|
+
.replace(/^---\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n|$)/, '')
|
|
8818
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
8819
|
+
.trim();
|
|
8820
|
+
if (!raw) return '';
|
|
8821
|
+
return raw.length > maxChars ? `${raw.slice(0, maxChars).trimEnd()}\n...[truncated]` : raw;
|
|
8822
|
+
} catch {
|
|
8823
|
+
return '';
|
|
8824
|
+
}
|
|
8825
|
+
}
|
|
8826
|
+
|
|
8827
|
+
function buildPreloadedMemberContextLines(mission, root = process.cwd()) {
|
|
8828
|
+
const owner = String(mission?.owner || '').trim();
|
|
8829
|
+
const baseRoot = mission?.worktree_root || mission?.worktree?.path || root;
|
|
8830
|
+
const sections = [];
|
|
8831
|
+
if (owner && /^[a-zA-Z0-9._-]+$/.test(owner)) {
|
|
8832
|
+
const teamMemberDir = memberDir(owner, baseRoot) || path.join(baseRoot, 'atris', 'team', owner);
|
|
8833
|
+
const memberMd = readBoundedContextFile(path.join(teamMemberDir, 'MEMBER.md'), 1800);
|
|
8834
|
+
const soulMd = readBoundedContextFile(path.join(teamMemberDir, 'SOUL.md'), 1200);
|
|
8835
|
+
const missionMd = readBoundedContextFile(path.join(teamMemberDir, 'MISSION.md'), 800);
|
|
8836
|
+
if (memberMd) {
|
|
8837
|
+
sections.push(`### Member Identity (atris/team/${owner}/MEMBER.md)`, memberMd);
|
|
8838
|
+
}
|
|
8839
|
+
if (soulMd) {
|
|
8840
|
+
sections.push(`### Member Soul (atris/team/${owner}/SOUL.md)`, soulMd);
|
|
8841
|
+
}
|
|
8842
|
+
if (missionMd) {
|
|
8843
|
+
sections.push(`### Member Standing Mission (atris/team/${owner}/MISSION.md)`, missionMd);
|
|
8844
|
+
}
|
|
8845
|
+
try {
|
|
8846
|
+
const skillsDir = path.join(teamMemberDir, 'skills');
|
|
8847
|
+
if (fs.existsSync(skillsDir)) {
|
|
8848
|
+
const skillSnippets = fs.readdirSync(skillsDir)
|
|
8849
|
+
.sort()
|
|
8850
|
+
.slice(0, 3)
|
|
8851
|
+
.map((slug) => {
|
|
8852
|
+
const skillText = readBoundedContextFile(path.join(skillsDir, slug, 'SKILL.md'), 500);
|
|
8853
|
+
return skillText ? `#### Skill: ${slug}\n${skillText}` : '';
|
|
8854
|
+
})
|
|
8855
|
+
.filter(Boolean);
|
|
8856
|
+
if (skillSnippets.length) {
|
|
8857
|
+
sections.push(`### Unlocked Member Skills (atris/team/${owner}/skills/)`, skillSnippets.join('\n\n'));
|
|
8858
|
+
}
|
|
8859
|
+
}
|
|
8860
|
+
} catch {}
|
|
8861
|
+
try {
|
|
8862
|
+
const logsDir = path.join(teamMemberDir, 'logs');
|
|
8863
|
+
if (fs.existsSync(logsDir)) {
|
|
8864
|
+
const latestLog = fs.readdirSync(logsDir)
|
|
8865
|
+
.filter((name) => name.endsWith('.md'))
|
|
8866
|
+
.sort()
|
|
8867
|
+
.pop();
|
|
8868
|
+
if (latestLog) {
|
|
8869
|
+
const rawLog = fs.readFileSync(path.join(logsDir, latestLog), 'utf8').trim();
|
|
8870
|
+
if (rawLog) {
|
|
8871
|
+
const tail = rawLog.length > 500 ? `...[earlier entries]\n${rawLog.slice(-500).trimStart()}` : rawLog;
|
|
8872
|
+
sections.push(`### Recent Member Memory (atris/team/${owner}/logs/${latestLog})`, tail);
|
|
8873
|
+
}
|
|
8874
|
+
}
|
|
8875
|
+
}
|
|
8876
|
+
} catch {}
|
|
8877
|
+
}
|
|
8878
|
+
const boundTask = mission?.xp_task || mission?.selected_target_task || null;
|
|
8879
|
+
if (boundTask && (boundTask.title || boundTask.ref || boundTask.task_id)) {
|
|
8880
|
+
const taskRef = boundTask.ref || boundTask.task_ref || boundTask.task_id;
|
|
8881
|
+
const taskTitle = boundTask.title ? `: ${boundTask.title}` : '';
|
|
8882
|
+
const taskStatus = boundTask.status ? ` (${boundTask.status})` : '';
|
|
8883
|
+
sections.push(`### Bound Task Context`, `- Active task: ${taskRef}${taskTitle}${taskStatus}`);
|
|
8884
|
+
}
|
|
8885
|
+
try {
|
|
8886
|
+
const tasteHistory = readTasteReviewHistory(baseRoot);
|
|
8887
|
+
const tasteLines = [];
|
|
8888
|
+
if (tasteHistory?.accepted?.length) {
|
|
8889
|
+
tasteLines.push(...tasteHistory.accepted.slice(0, 2).map((line) => `- Accepted: ${line}`));
|
|
8890
|
+
}
|
|
8891
|
+
if (tasteHistory?.revised?.length) {
|
|
8892
|
+
tasteLines.push(...tasteHistory.revised.slice(0, 2).map((line) => `- Sent back: ${line}`));
|
|
8893
|
+
}
|
|
8894
|
+
if (tasteLines.length) {
|
|
8895
|
+
sections.push(`### Operator Taste & Review Notes`, tasteLines.join('\n'));
|
|
8896
|
+
}
|
|
8897
|
+
} catch {}
|
|
8898
|
+
if (!sections.length) return [];
|
|
8899
|
+
return [
|
|
8900
|
+
``,
|
|
8901
|
+
`## Preloaded Member & Task Context`,
|
|
8902
|
+
...sections,
|
|
8903
|
+
];
|
|
8904
|
+
}
|
|
8905
|
+
|
|
8615
8906
|
function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = [], cwd = process.cwd()) {
|
|
8616
8907
|
const sharedProcess = memberProcessPrompt(cwd);
|
|
8617
8908
|
const pingLines = pings.length
|
|
@@ -8631,6 +8922,7 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = [], cwd =
|
|
|
8631
8922
|
checkFeedback,
|
|
8632
8923
|
]
|
|
8633
8924
|
: [];
|
|
8925
|
+
const preloadedContextLines = buildPreloadedMemberContextLines(mission, cwd);
|
|
8634
8926
|
const lines = [
|
|
8635
8927
|
`# Mission Tick ${tickIndex}/${maxTicks}`,
|
|
8636
8928
|
...pingLines,
|
|
@@ -8646,6 +8938,7 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = [], cwd =
|
|
|
8646
8938
|
...missionBudgetPromptLines(mission),
|
|
8647
8939
|
...checkFeedbackLines,
|
|
8648
8940
|
...(sharedProcess ? ['', sharedProcess] : []),
|
|
8941
|
+
...preloadedContextLines,
|
|
8649
8942
|
``,
|
|
8650
8943
|
`## Your task`,
|
|
8651
8944
|
`Do ONE increment of work toward the stop condition. ONE. No more.`,
|
|
@@ -8657,6 +8950,7 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = [], cwd =
|
|
|
8657
8950
|
`- The tick is not done until the verify step passes. If a frozen verifier exists, the harness runs it, but that does not replace the real-surface proof below. If no frozen verifier exists, finish by actually running the surface you changed on this computer and paste the real command output into your receipt.`,
|
|
8658
8951
|
`- After that one verify step, STOP. Never start a second slice in one tick.`,
|
|
8659
8952
|
`- Write the tick summary in operator language: what changed and what it buys or costs in plain words, with no flags, task ids, or code identifiers.`,
|
|
8953
|
+
`- Propose the next needle-mover: include a \`next_push: <one concrete feature or optimization to build next>\` line in your receipt and member log so the team compounds exponentially.`,
|
|
8660
8954
|
``,
|
|
8661
8955
|
`## No claimable work? Work the backstop ladder`,
|
|
8662
8956
|
`If the mission and task state offer no claimable work, inspect these rungs in order. Pick the FIRST rung with real fuel and do ONE bounded slice:`,
|
|
@@ -9217,10 +9511,25 @@ function engineVerifierResultFromRun(engineResult, engine = 'codex') {
|
|
|
9217
9511
|
};
|
|
9218
9512
|
}
|
|
9219
9513
|
|
|
9514
|
+
function pickCrossEngineVerifier(mission, cwd = process.cwd(), preferredEngine = '') {
|
|
9515
|
+
const explicit = canonicalEngineName(preferredEngine || mission?.referee_engine || '');
|
|
9516
|
+
if (explicit) return explicit;
|
|
9517
|
+
const workerEngine = canonicalEngineName(mission?.runner) || 'claude';
|
|
9518
|
+
const candidates = workerEngine === 'codex' ? ['claude', 'fable', 'codex'] : ['codex', 'claude'];
|
|
9519
|
+
try {
|
|
9520
|
+
const engines = engineRegistryView(cwd);
|
|
9521
|
+
for (const candidate of candidates) {
|
|
9522
|
+
const entry = engines.find((e) => e.id === candidate);
|
|
9523
|
+
if (entry && entry.installed && entry.health?.status === 'ready') return candidate;
|
|
9524
|
+
}
|
|
9525
|
+
} catch {}
|
|
9526
|
+
return workerEngine === 'codex' ? 'claude' : 'codex';
|
|
9527
|
+
}
|
|
9528
|
+
|
|
9220
9529
|
async function runEngineVerifier(mission, options = {}) {
|
|
9221
9530
|
const cwd = options.cwd || process.cwd();
|
|
9222
|
-
const engine =
|
|
9223
|
-
const verifyMission = { ...mission, runner: engine, runner_kind: 'engine' };
|
|
9531
|
+
const engine = pickCrossEngineVerifier(mission, cwd, options.refereeEngine);
|
|
9532
|
+
const verifyMission = { ...mission, runner: engine, runner_kind: 'engine', model: '' };
|
|
9224
9533
|
const restoreRunnerProfile = applyMissionRunnerProfile(engine);
|
|
9225
9534
|
let engineResult;
|
|
9226
9535
|
try {
|
|
@@ -9261,6 +9570,22 @@ function usefulClaudeReceiptSummary(text, fallback = 'no-text') {
|
|
|
9261
9570
|
return fallback;
|
|
9262
9571
|
}
|
|
9263
9572
|
|
|
9573
|
+
function extractNextPushFromReceiptText(text) {
|
|
9574
|
+
const raw = String(text || '');
|
|
9575
|
+
if (!raw.trim()) return null;
|
|
9576
|
+
const explicitMatch = raw.match(/^\s*(?:[-*]\s*)?(?:next_push|next push|next needle[- ]mover)\s*:\s*(.+)$/im);
|
|
9577
|
+
if (explicitMatch && explicitMatch[1]) {
|
|
9578
|
+
const cleaned = explicitMatch[1].replace(/`/g, '').replace(/\s+/g, ' ').trim();
|
|
9579
|
+
if (cleaned && !/^<one concrete/i.test(cleaned)) return cleaned.slice(0, 180);
|
|
9580
|
+
}
|
|
9581
|
+
const numberedMatch = raw.match(/^\s*(?:\(3\)|3[.)])\s*(?:next(?:\s+tick)?\s*:\s*)?(.+)$/im);
|
|
9582
|
+
if (numberedMatch && numberedMatch[1]) {
|
|
9583
|
+
const cleaned = numberedMatch[1].replace(/`/g, '').replace(/\s+/g, ' ').trim();
|
|
9584
|
+
if (cleaned && !/^layer\s*:/i.test(cleaned)) return cleaned.slice(0, 180);
|
|
9585
|
+
}
|
|
9586
|
+
return null;
|
|
9587
|
+
}
|
|
9588
|
+
|
|
9264
9589
|
function cappedClaudeReceiptText(text, limit = 4000) {
|
|
9265
9590
|
const clean = String(text || '').trim();
|
|
9266
9591
|
if (!clean) return '';
|
|
@@ -9316,6 +9641,8 @@ async function parseAndValidateMissionRunPhase(args) {
|
|
|
9316
9641
|
const selfDrive = dueMode || headlessOnly || hasFlag(args, '--self-drive');
|
|
9317
9642
|
const skipClaude = hasFlag(args, '--no-claude');
|
|
9318
9643
|
const verifyEach = !hasFlag(args, '--no-verify');
|
|
9644
|
+
const refereeEach = hasFlag(args, '--referee') && !hasFlag(args, '--no-referee');
|
|
9645
|
+
const refereeEngine = readFlag(args, '--referee-engine', '');
|
|
9319
9646
|
const completeOnPass = hasFlag(args, '--complete-on-pass');
|
|
9320
9647
|
const skipDrain = hasFlag(args, '--no-drain');
|
|
9321
9648
|
const createNext = hasFlag(args, '--create-next');
|
|
@@ -9351,6 +9678,8 @@ async function parseAndValidateMissionRunPhase(args) {
|
|
|
9351
9678
|
selfDrive,
|
|
9352
9679
|
skipClaude,
|
|
9353
9680
|
verifyEach,
|
|
9681
|
+
refereeEach,
|
|
9682
|
+
refereeEngine,
|
|
9354
9683
|
completeOnPass,
|
|
9355
9684
|
skipDrain,
|
|
9356
9685
|
createNext,
|
|
@@ -9376,7 +9705,6 @@ async function resolveMissionRunPhase(context) {
|
|
|
9376
9705
|
const {
|
|
9377
9706
|
args,
|
|
9378
9707
|
asJson,
|
|
9379
|
-
cwd,
|
|
9380
9708
|
dueMode,
|
|
9381
9709
|
headlessOnly,
|
|
9382
9710
|
budgetTier,
|
|
@@ -9389,7 +9717,7 @@ async function resolveMissionRunPhase(context) {
|
|
|
9389
9717
|
ref,
|
|
9390
9718
|
runArgs,
|
|
9391
9719
|
} = context;
|
|
9392
|
-
let { maxTicks, maxWallSeconds } = context;
|
|
9720
|
+
let { cwd, maxTicks, maxWallSeconds } = context;
|
|
9393
9721
|
|
|
9394
9722
|
if (!dueMode && !ref) {
|
|
9395
9723
|
if (asJson || !process.stdin.isTTY || !process.stderr.isTTY) {
|
|
@@ -9428,6 +9756,13 @@ async function resolveMissionRunPhase(context) {
|
|
|
9428
9756
|
if (!mission) {
|
|
9429
9757
|
exitMissionError('Usage: atris mission run <id|objective> [--max-ticks 4] [--max-wall 3600]', 1, asJson);
|
|
9430
9758
|
}
|
|
9759
|
+
const worktreeRoot = mission.worktree_root || mission.worktree?.path || '';
|
|
9760
|
+
if (worktreeRoot && fs.existsSync(worktreeRoot) && path.resolve(worktreeRoot) !== path.resolve(cwd)) {
|
|
9761
|
+
cwd = path.resolve(worktreeRoot);
|
|
9762
|
+
context.cwd = cwd;
|
|
9763
|
+
process.chdir(cwd);
|
|
9764
|
+
mission = resolveMission(mission.id, cwd) || mission;
|
|
9765
|
+
}
|
|
9431
9766
|
if (!maxTicksFlag && !budgetTier && Number(mission.max_ticks) > 0) {
|
|
9432
9767
|
maxTicks = Math.max(1, Number(mission.max_ticks));
|
|
9433
9768
|
}
|
|
@@ -9452,6 +9787,30 @@ async function resolveMissionRunPhase(context) {
|
|
|
9452
9787
|
console.error(`Mission ${mission.id} is ${mission.status}; nothing to run.`);
|
|
9453
9788
|
process.exit(0);
|
|
9454
9789
|
}
|
|
9790
|
+
// An explicit run must respect open human asks the same way auto-selection
|
|
9791
|
+
// does (missionIsRunnable), instead of ticking straight past them.
|
|
9792
|
+
const blockingAsks = runBlockingAsks(mission.human_asks);
|
|
9793
|
+
if (blockingAsks.length) {
|
|
9794
|
+
detachedDriverLifecycle?.setExitReason('human_asks_open');
|
|
9795
|
+
const lines = [
|
|
9796
|
+
`Mission ${mission.id} is waiting on ${blockingAsks.length} human ask${blockingAsks.length === 1 ? '' : 's'}; not ticking.`,
|
|
9797
|
+
...blockingAsks.map((ask) => ` - ${ask.text}${ask.answered_at ? ' (answered no)' : ''}`),
|
|
9798
|
+
'Answer with: atris decide (then atris decide <n> y), and re-run.',
|
|
9799
|
+
];
|
|
9800
|
+
if (asJson) {
|
|
9801
|
+
printJsonOrText({
|
|
9802
|
+
ok: false,
|
|
9803
|
+
action: 'run_blocked',
|
|
9804
|
+
reason: 'human_asks_open',
|
|
9805
|
+
mission_id: mission.id,
|
|
9806
|
+
human_asks: blockingAsks.map((ask) => ask.text),
|
|
9807
|
+
next_command: 'atris decide',
|
|
9808
|
+
}, [], true);
|
|
9809
|
+
} else {
|
|
9810
|
+
for (const line of lines) console.error(line);
|
|
9811
|
+
}
|
|
9812
|
+
process.exit(2);
|
|
9813
|
+
}
|
|
9455
9814
|
if (hasFlag(args, '--detach')) {
|
|
9456
9815
|
detachMissionRun(args, mission, cwd, asJson);
|
|
9457
9816
|
context.handled = true;
|
|
@@ -9671,6 +10030,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9671
10030
|
const tickEngineId = engineBackedTick ? (tickSelection.engine_id || inferredEngineId) : null;
|
|
9672
10031
|
if (engineBackedTick) {
|
|
9673
10032
|
result.engine_id = tickEngineId;
|
|
10033
|
+
if (tickRuntimeMission.model) result.model = tickRuntimeMission.model;
|
|
9674
10034
|
result.requested_engine = tickSelection.requested_engine;
|
|
9675
10035
|
result.engine_fallback_reason = tickSelection.engine_fallback_reason;
|
|
9676
10036
|
}
|
|
@@ -9945,6 +10305,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9945
10305
|
tickSkipWorker,
|
|
9946
10306
|
tickIdx,
|
|
9947
10307
|
frozen,
|
|
10308
|
+
storedVerifier,
|
|
9948
10309
|
};
|
|
9949
10310
|
let verifierResult = null;
|
|
9950
10311
|
if (cachedStep) {
|
|
@@ -9986,12 +10347,15 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9986
10347
|
|
|
9987
10348
|
// Layer classification needs the receipt text AND the worktree receipt, so it
|
|
9988
10349
|
// runs here, after both exist, covering the claude and atris2 branches alike.
|
|
10350
|
+
const tickReceiptText = result.atris2?.receipt_text || result.claude?.receipt_text || result.drill?.receipt_text || '';
|
|
9989
10351
|
if (!(cachedStep && cachedStep.tick && cachedStep.tick.layer)) {
|
|
9990
|
-
const tickReceiptText = result.atris2?.receipt_text || result.claude?.receipt_text || result.drill?.receipt_text || '';
|
|
9991
10352
|
const layerInfo = extractLayerFromReceiptText(tickReceiptText, tickWorktree?.new_since_baseline_sample);
|
|
9992
10353
|
result.layer = layerInfo.layer;
|
|
9993
10354
|
result.layer_source = layerInfo.source;
|
|
9994
10355
|
}
|
|
10356
|
+
if (!result.next_push) {
|
|
10357
|
+
result.next_push = extractNextPushFromReceiptText(tickReceiptText);
|
|
10358
|
+
}
|
|
9995
10359
|
|
|
9996
10360
|
// Persist tick to mission state + write structured receipt
|
|
9997
10361
|
const finishedAt = stampIso();
|
|
@@ -10017,9 +10381,14 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10017
10381
|
// reopen closed rooms, mission 32 complete@19:23:33 → ready@19:23:48).
|
|
10018
10382
|
const latestOnDisk = loadMissionMap(cwd).get(mission.id) || mission;
|
|
10019
10383
|
const alreadyTerminal = TERMINAL_STATUSES.has(String(latestOnDisk.status || ''));
|
|
10384
|
+
// A passing verifier alone never completes a mission whose linked task
|
|
10385
|
+
// is not accepted: park it ready and name the accept command instead.
|
|
10386
|
+
const acceptGate = (verifierResult?.passed && completeOnPass && !fullBudgetMode && !mission.always_on && !xpReadyAction)
|
|
10387
|
+
? missionLinkedTaskGate(mission, cwd)
|
|
10388
|
+
: { ok: true };
|
|
10020
10389
|
let newStatus = (verifierResult?.passed && mission.always_on) ? 'ready' :
|
|
10021
10390
|
(verifierResult?.passed && xpReadyAction) ? 'ready' :
|
|
10022
|
-
(verifierResult?.passed && completeOnPass && !fullBudgetMode) ? 'complete' :
|
|
10391
|
+
(verifierResult?.passed && completeOnPass && !fullBudgetMode && acceptGate.ok) ? 'complete' :
|
|
10023
10392
|
(verifierResult?.passed && fullBudgetMode) ? 'running' :
|
|
10024
10393
|
(verifierResult?.passed ? 'ready' :
|
|
10025
10394
|
(verifierResult ? 'blocked' :
|
|
@@ -10033,6 +10402,8 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10033
10402
|
nextAction = nextCandidateTickAction(mission);
|
|
10034
10403
|
} else if (verifierResult?.passed && xpReadyAction) {
|
|
10035
10404
|
nextAction = xpReadyAction;
|
|
10405
|
+
} else if (verifierResult?.passed && completeOnPass && !fullBudgetMode && !acceptGate.ok) {
|
|
10406
|
+
nextAction = missionAcceptNextAction(mission, acceptGate);
|
|
10036
10407
|
} else if (verifierResult?.passed && completeOnPass && !fullBudgetMode) {
|
|
10037
10408
|
nextAction = 'mission complete';
|
|
10038
10409
|
} else if (verifierResult?.passed && fullBudgetMode) {
|
|
@@ -10070,6 +10441,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10070
10441
|
last_tick_layer: result.layer,
|
|
10071
10442
|
last_tick_layer_source: result.layer_source,
|
|
10072
10443
|
error_streak_count: errorStreakCount,
|
|
10444
|
+
last_next_push: result.next_push || latestOnDisk.last_next_push || null,
|
|
10073
10445
|
verifier_result: verifierResult || (verifyEach && result.protected_lane_guard && result.protected_lane_guard.allowed === false ? null : latestOnDisk.verifier_result) || null,
|
|
10074
10446
|
last_check_feedback: verifierResult
|
|
10075
10447
|
? extractCheckFeedback(verifierResult)
|
|
@@ -10078,7 +10450,16 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10078
10450
|
next_action: nextAction,
|
|
10079
10451
|
claude_session_ticks: rotateSessionForContext ? 0 : claudeSessionTicks,
|
|
10080
10452
|
}, cwd, 'mission_tick', {
|
|
10081
|
-
tick_index: tickIdx,
|
|
10453
|
+
tick_index: tickIdx,
|
|
10454
|
+
status: result.status,
|
|
10455
|
+
reason: result.reason,
|
|
10456
|
+
receipt_path: receiptPath,
|
|
10457
|
+
layer: result.layer,
|
|
10458
|
+
next_push: result.next_push || undefined,
|
|
10459
|
+
engine: result.engine_id || runtimeMission.runner || mission.runner || null,
|
|
10460
|
+
task_type: 'executor',
|
|
10461
|
+
verified_passed: verifierResult && typeof verifierResult.passed === 'boolean' ? verifierResult.passed : undefined,
|
|
10462
|
+
duration_ms: Math.max(0, (Date.parse(finishedAt) || 0) - (Date.parse(tickStart) || 0)),
|
|
10082
10463
|
}).mission;
|
|
10083
10464
|
if (rotateSessionForContext) {
|
|
10084
10465
|
// Same shape as the stale-lock rotation above: mint the fresh pending
|
|
@@ -10093,6 +10474,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10093
10474
|
},
|
|
10094
10475
|
).mission;
|
|
10095
10476
|
}
|
|
10477
|
+
const tickSummaryText = String(result?.claude?.summary || '').replace(/\s+/g, ' ').trim();
|
|
10096
10478
|
appendMemberLog(mission.owner, `Mission run tick ${tickIdx}`, {
|
|
10097
10479
|
mission: mission.objective,
|
|
10098
10480
|
state: mission.status,
|
|
@@ -10100,8 +10482,39 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10100
10482
|
reason: result.reason,
|
|
10101
10483
|
layer: result.layer || undefined,
|
|
10102
10484
|
verifier: verifierResult ? (verifierResult.passed ? 'passed' : 'failed') : 'not_run',
|
|
10485
|
+
summary: tickSummaryText ? tickSummaryText.slice(0, 240) : undefined,
|
|
10486
|
+
next_push: result.next_push ? String(result.next_push).slice(0, 180) : undefined,
|
|
10487
|
+
feedback: verifierResult && !verifierResult.passed ? String(mission.last_check_feedback || '').slice(0, 240) : undefined,
|
|
10103
10488
|
receipt: receiptPath,
|
|
10104
|
-
});
|
|
10489
|
+
}, cwd);
|
|
10490
|
+
if (verifierResult && verifierResult.passed === true && result.status === 'ran') {
|
|
10491
|
+
try {
|
|
10492
|
+
crystallizeMemberSkillLesson(mission.owner, {
|
|
10493
|
+
objective: mission.objective,
|
|
10494
|
+
summary: tickSummaryText || mission.objective,
|
|
10495
|
+
verifier: frozen.verifier || mission.verifier || '',
|
|
10496
|
+
next_push: result.next_push || '',
|
|
10497
|
+
layer: result.layer || 'capabilities',
|
|
10498
|
+
}, cwd);
|
|
10499
|
+
} catch {}
|
|
10500
|
+
if (result.next_push) {
|
|
10501
|
+
try {
|
|
10502
|
+
autoQueueMemberNextPush(mission.owner, result.next_push, mission.objective, cwd, {
|
|
10503
|
+
promote: newStatus === 'complete',
|
|
10504
|
+
});
|
|
10505
|
+
} catch {}
|
|
10506
|
+
}
|
|
10507
|
+
if (mission.owner) {
|
|
10508
|
+
try {
|
|
10509
|
+
closeMemberHandoffsForReceiver(mission.owner, cwd);
|
|
10510
|
+
} catch {}
|
|
10511
|
+
}
|
|
10512
|
+
if (newStatus === 'complete' && mission.owner) {
|
|
10513
|
+
try {
|
|
10514
|
+
recordMemberHandoff(mission.owner, '', tickSummaryText || mission.objective, cwd);
|
|
10515
|
+
} catch {}
|
|
10516
|
+
}
|
|
10517
|
+
}
|
|
10105
10518
|
if (newStatus === 'complete') {
|
|
10106
10519
|
continuationGoal = seedMissionRunContinuation(mission, cwd, receiptPath);
|
|
10107
10520
|
if (continuationGoal?.parent) mission = continuationGoal.parent;
|
|
@@ -10262,12 +10675,37 @@ async function verifyMissionRunTickPhase(context) {
|
|
|
10262
10675
|
if (result.status === 'ran' && verifyEach) {
|
|
10263
10676
|
if (currentTick.frozen.verifier) {
|
|
10264
10677
|
verifierResult = runVerifier(currentTick.frozen.verifier);
|
|
10265
|
-
}
|
|
10266
|
-
|
|
10678
|
+
}
|
|
10679
|
+
const shouldRunReferee = !currentTick.tickSkipWorker
|
|
10680
|
+
&& (!verifierResult || verifierResult.passed)
|
|
10681
|
+
&& (
|
|
10682
|
+
!currentTick.frozen.verifier
|
|
10683
|
+
|| context.refereeEach
|
|
10684
|
+
|| (!currentTick.storedVerifier && !process.env.NODE_TEST_CONTEXT)
|
|
10685
|
+
);
|
|
10686
|
+
if (shouldRunReferee) {
|
|
10687
|
+
const refereeResult = await runEngineVerifier(currentTick.tickRuntimeMission, {
|
|
10267
10688
|
cwd,
|
|
10268
10689
|
signal: controller.signal,
|
|
10269
10690
|
tickIndex: currentTick.tickIdx,
|
|
10691
|
+
refereeEngine: context.refereeEngine,
|
|
10270
10692
|
});
|
|
10693
|
+
result.referee_result = refereeResult;
|
|
10694
|
+
if (!verifierResult || !refereeResult.passed) {
|
|
10695
|
+
verifierResult = verifierResult
|
|
10696
|
+
? {
|
|
10697
|
+
...verifierResult,
|
|
10698
|
+
passed: Boolean(verifierResult.passed && refereeResult.passed),
|
|
10699
|
+
referee: refereeResult,
|
|
10700
|
+
output: [verifierResult.stdout, verifierResult.stderr, refereeResult.output].filter(Boolean).join('\n'),
|
|
10701
|
+
}
|
|
10702
|
+
: refereeResult;
|
|
10703
|
+
} else {
|
|
10704
|
+
verifierResult = {
|
|
10705
|
+
...verifierResult,
|
|
10706
|
+
referee: refereeResult,
|
|
10707
|
+
};
|
|
10708
|
+
}
|
|
10271
10709
|
}
|
|
10272
10710
|
if (verifierResult) result.verifier_passed = verifierResult.passed;
|
|
10273
10711
|
}
|
|
@@ -10601,10 +11039,14 @@ function tickMission(args) {
|
|
|
10601
11039
|
const xpReadyAction = missionXpReadyAction(mission, receiptPath);
|
|
10602
11040
|
const budgetRemainingSeconds = missionFullBudgetRemainingSeconds(mission);
|
|
10603
11041
|
const budgetOpen = budgetRemainingSeconds > 0;
|
|
10604
|
-
|
|
11042
|
+
const acceptGate = (completeOnPass && !mission.always_on && !xpReadyAction && !budgetOpen)
|
|
11043
|
+
? missionLinkedTaskGate(mission, process.cwd())
|
|
11044
|
+
: { ok: true };
|
|
11045
|
+
status = (completeOnPass && !mission.always_on && !xpReadyAction && !budgetOpen && acceptGate.ok) ? 'complete'
|
|
10605
11046
|
: (budgetOpen && !mission.always_on && !xpReadyAction ? 'running' : 'ready');
|
|
10606
11047
|
nextAction = mission.always_on ? nextCandidateTickAction(mission) :
|
|
10607
11048
|
(xpReadyAction
|
|
11049
|
+
|| (!acceptGate.ok ? missionAcceptNextAction(mission, acceptGate) : null)
|
|
10608
11050
|
|| (budgetOpen
|
|
10609
11051
|
? `proof passed with ${durationLabel(budgetRemainingSeconds)} left on the budget; pick the next useful move, then: atris mission tick ${mission.id} --verify --summary "<what changed>"`
|
|
10610
11052
|
: (completeOnPass ? 'mission complete' : `review proof then run: atris mission complete ${mission.id} --proof "${receiptPath}"`)));
|
|
@@ -10747,6 +11189,38 @@ function receiptShowsPass(receipt) {
|
|
|
10747
11189
|
// verifier passed. Mirrors the task plane's proof-only accept guard so the
|
|
10748
11190
|
// final transition consumes the receipts instead of trusting free text.
|
|
10749
11191
|
function missionCompletionGate(mission, proof, root = process.cwd()) {
|
|
11192
|
+
const base = missionVerifierCompletionGate(mission, proof, root);
|
|
11193
|
+
if (!base.ok) {
|
|
11194
|
+
// The check not passing is the first thing to fix, but a linked task that
|
|
11195
|
+
// is not accepted would refuse the next try too: say so now.
|
|
11196
|
+
const pending = missionLinkedTaskGate(mission, root);
|
|
11197
|
+
if (pending.ok) return base;
|
|
11198
|
+
const how = pending.accept_command ? ` (${pending.accept_command})` : '';
|
|
11199
|
+
return { ...base, linked_task_note: `Also, ${pending.reason}${how}` };
|
|
11200
|
+
}
|
|
11201
|
+
// Done means an accepted task plus a receipt: a passing verifier is not
|
|
11202
|
+
// enough when the mission is linked to task(s) a human has not accepted.
|
|
11203
|
+
const linked = missionLinkedTaskGate(mission, root);
|
|
11204
|
+
if (!linked.ok) {
|
|
11205
|
+
return {
|
|
11206
|
+
ok: false,
|
|
11207
|
+
source: 'linked_task',
|
|
11208
|
+
reason: linked.reason,
|
|
11209
|
+
task_id: linked.task_id,
|
|
11210
|
+
task_ref: linked.task_ref,
|
|
11211
|
+
accept_command: linked.accept_command,
|
|
11212
|
+
archived_only: linked.archived_only === true,
|
|
11213
|
+
};
|
|
11214
|
+
}
|
|
11215
|
+
return linked.tasks.length ? { ...base, linked_tasks: linked.tasks } : base;
|
|
11216
|
+
}
|
|
11217
|
+
|
|
11218
|
+
function missionAcceptNextAction(mission, gate) {
|
|
11219
|
+
const how = gate.accept_command || (gate.archived_only ? 'link a live task and accept it' : 'attach a receipt to it');
|
|
11220
|
+
return `verifier passed but ${gate.reason}; accept it (${how}), then: atris mission complete ${mission.id} --proof "<receipt>"`;
|
|
11221
|
+
}
|
|
11222
|
+
|
|
11223
|
+
function missionVerifierCompletionGate(mission, proof, root = process.cwd()) {
|
|
10750
11224
|
const remainingSeconds = missionFullBudgetRemainingSeconds(mission);
|
|
10751
11225
|
if (remainingSeconds > 0) {
|
|
10752
11226
|
return {
|
|
@@ -10789,10 +11263,19 @@ function completeMission(args) {
|
|
|
10789
11263
|
}
|
|
10790
11264
|
const gate = missionCompletionGate(mission, proof, root);
|
|
10791
11265
|
if (!gate.ok && !force) {
|
|
10792
|
-
|
|
11266
|
+
const fix = gate.source === 'linked_task'
|
|
11267
|
+
? (gate.accept_command
|
|
11268
|
+
? `Accept it first: ${gate.accept_command}, then re-run mission complete`
|
|
11269
|
+
: (gate.archived_only ? 'Link a live task to the mission and accept it first' : 'Attach a receipt to it first'))
|
|
11270
|
+
: `Run: atris mission tick ${mission.id} --verify`;
|
|
11271
|
+
const also = gate.linked_task_note ? ` ${gate.linked_task_note}.` : '';
|
|
11272
|
+
exitMissionError(`[mission complete] ${gate.reason}. ${fix}.${also} (or override as operator with --force)`, 2, asJson);
|
|
11273
|
+
}
|
|
11274
|
+
if (!gate.ok && force && gate.source === 'linked_task') {
|
|
11275
|
+
console.error(`[mission complete] WARNING --force overrides the accepted-task gate: ${gate.reason}. Recorded as completion_gate.forced.`);
|
|
10793
11276
|
}
|
|
10794
11277
|
const baselineSummary = pruneMissionWorktreeBaseline(mission, root);
|
|
10795
|
-
const completionGate = { ...gate, forced: force && !gate.ok };
|
|
11278
|
+
const completionGate = { ...gate, forced: force && !gate.ok, ...(force && !gate.ok ? { forced_reason: gate.reason } : {}) };
|
|
10796
11279
|
const baseNext = {
|
|
10797
11280
|
...mission,
|
|
10798
11281
|
status: 'complete',
|
|
@@ -11849,6 +12332,7 @@ module.exports = {
|
|
|
11849
12332
|
startMission,
|
|
11850
12333
|
resolveMission,
|
|
11851
12334
|
spawnMissionDriver,
|
|
12335
|
+
missionDriverHealth,
|
|
11852
12336
|
missionRunnerNeedsLiveSession,
|
|
11853
12337
|
completeMission,
|
|
11854
12338
|
inspectMission,
|
|
@@ -11874,6 +12358,9 @@ module.exports = {
|
|
|
11874
12358
|
selectCodexGoalMission,
|
|
11875
12359
|
codexGoalNextCommand,
|
|
11876
12360
|
usefulClaudeReceiptSummary,
|
|
12361
|
+
autoQueueMemberNextPush,
|
|
12362
|
+
recordMemberHandoff,
|
|
12363
|
+
closeMemberHandoffsForReceiver,
|
|
11877
12364
|
cappedClaudeReceiptText,
|
|
11878
12365
|
extractLayerFromReceiptText,
|
|
11879
12366
|
classifyPathsByLayer,
|
|
@@ -11893,6 +12380,7 @@ module.exports = {
|
|
|
11893
12380
|
missionVerifierHighLevelTestText,
|
|
11894
12381
|
buildEngineVerifyPrompt,
|
|
11895
12382
|
engineVerifierResultFromRun,
|
|
12383
|
+
pickCrossEngineVerifier,
|
|
11896
12384
|
runVerifier,
|
|
11897
12385
|
missionFullBudgetRemainingSeconds,
|
|
11898
12386
|
missionBudgetContinuationText,
|