atris 3.30.7 → 3.30.12
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/README.md +1 -1
- package/ax +271 -65
- package/bin/atris.js +190 -1
- package/commands/mission.js +349 -12
- package/lib/mission-runtime-loop.js +320 -0
- package/package.json +1 -1
package/commands/mission.js
CHANGED
|
@@ -346,6 +346,8 @@ function statePaths(root = process.cwd()) {
|
|
|
346
346
|
eventsJsonl: path.join(stateDir, 'mission_events.jsonl'),
|
|
347
347
|
codexGoalJson: path.join(stateDir, 'codex_goal.json'),
|
|
348
348
|
codexGoalStatus: path.join(root, 'atris', 'status', 'codex-goal.md'),
|
|
349
|
+
atrisGoalJson: path.join(stateDir, 'atris_goal.json'),
|
|
350
|
+
atrisGoalStatus: path.join(root, 'atris', 'status', 'atris-goal.md'),
|
|
349
351
|
statusNow: path.join(root, 'atris', 'status', 'now.md'),
|
|
350
352
|
runsDir: path.join(root, 'atris', 'runs'),
|
|
351
353
|
};
|
|
@@ -899,6 +901,9 @@ function missionRunSmokeVerifier() {
|
|
|
899
901
|
"if(p.action!=='mission_run_started')process.exit(2)",
|
|
900
902
|
"if(p.mission?.runner!=='codex_goal')process.exit(3)",
|
|
901
903
|
"if(p.codex_goal_state?.goal?.visible_goal?.schema!=='atris.visible_chat_goal_bridge.v1')process.exit(4)",
|
|
904
|
+
"if(p.requires_native_goal_start!==true)process.exit(5)",
|
|
905
|
+
"if(p.native_goal_action?.tool!=='create_goal')process.exit(6)",
|
|
906
|
+
"if(!/create_goal/.test(p.next_command||''))process.exit(7)",
|
|
902
907
|
].join(';');
|
|
903
908
|
return `node -e ${JSON.stringify(script)}`;
|
|
904
909
|
}
|
|
@@ -1004,12 +1009,14 @@ function seedMissionRunContinuation(parent, root = process.cwd(), proof = '') {
|
|
|
1004
1009
|
|
|
1005
1010
|
const owner = parent.owner || process.env.ATRIS_AGENT_ID || 'mission-lead';
|
|
1006
1011
|
const objective = continuationObjective(parent);
|
|
1012
|
+
const parentRunner = String(parent.runner || '').trim().toLowerCase();
|
|
1013
|
+
const continuationRunner = parentRunner === 'atris2' ? 'atris2' : 'codex_goal';
|
|
1007
1014
|
const mission = missionFromArgs([
|
|
1008
1015
|
objective,
|
|
1009
1016
|
'--owner',
|
|
1010
1017
|
owner,
|
|
1011
1018
|
'--runner',
|
|
1012
|
-
|
|
1019
|
+
continuationRunner,
|
|
1013
1020
|
'--lane',
|
|
1014
1021
|
parent.lane || 'workspace',
|
|
1015
1022
|
'--cadence',
|
|
@@ -1151,7 +1158,11 @@ function startMissionFromRunObjective(objective, args) {
|
|
|
1151
1158
|
});
|
|
1152
1159
|
const worktreeBaseline = captureMissionWorktreeBaseline(saved, process.cwd());
|
|
1153
1160
|
const completedContinuationGoal = completeActiveContinuationForStartedMission(saved, process.cwd());
|
|
1154
|
-
const
|
|
1161
|
+
const atrisGoalState = refreshAtrisGoalController(process.cwd(), { missionId: saved.id });
|
|
1162
|
+
const codexGoalState = runnerUsesCallerSession(saved.runner)
|
|
1163
|
+
? refreshCodexGoalController(process.cwd(), { missionId: saved.id })
|
|
1164
|
+
: null;
|
|
1165
|
+
const nativeGoal = codexGoalState?.goal?.requires_native_goal_start ? codexGoalState.goal.native_goal_action : null;
|
|
1155
1166
|
printJsonOrText(
|
|
1156
1167
|
{
|
|
1157
1168
|
ok: true,
|
|
@@ -1167,14 +1178,19 @@ function startMissionFromRunObjective(objective, args) {
|
|
|
1167
1178
|
dirty_hash: worktreeBaseline.dirty_hash,
|
|
1168
1179
|
} : null,
|
|
1169
1180
|
completed_continuation_goal: completedContinuationGoal,
|
|
1181
|
+
atris_goal_state: atrisGoalState,
|
|
1170
1182
|
codex_goal_state: codexGoalState,
|
|
1171
|
-
|
|
1183
|
+
requires_native_goal_start: codexGoalState?.goal?.requires_native_goal_start === true,
|
|
1184
|
+
native_goal_action: nativeGoal,
|
|
1185
|
+
native_goal_ack_command: codexGoalState?.goal?.native_goal_ack_command || null,
|
|
1186
|
+
next_command: codexGoalState?.goal?.next_command || atrisGoalState.goal?.next_command || `atris mission tick ${saved.id} --summary "<what changed>"`,
|
|
1172
1187
|
},
|
|
1173
1188
|
[
|
|
1174
1189
|
`Started mission: ${saved.objective}`,
|
|
1175
1190
|
`Owner: ${saved.owner}`,
|
|
1176
1191
|
`Runner: ${saved.runner}`,
|
|
1177
|
-
...(
|
|
1192
|
+
...(atrisGoalState.goal ? [`Atris goal: ${atrisGoalState.goal.objective}`] : []),
|
|
1193
|
+
...(codexGoalState?.goal ? [`Codex goal: ${codexGoalState.goal.objective}`] : []),
|
|
1178
1194
|
...warnings.map((warning) => `Warning: ${warning.message}`),
|
|
1179
1195
|
],
|
|
1180
1196
|
asJson,
|
|
@@ -1721,6 +1737,10 @@ function runnerUsesCallerSession(runner) {
|
|
|
1721
1737
|
return new Set(['codex_goal', 'caller_session', 'current_agent']).has(String(runner || '').trim().toLowerCase());
|
|
1722
1738
|
}
|
|
1723
1739
|
|
|
1740
|
+
function isCodexGoalMission(mission) {
|
|
1741
|
+
return String(mission?.runner || '').trim().toLowerCase() === 'codex_goal';
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1724
1744
|
function nextCandidateTickAction(mission) {
|
|
1725
1745
|
const completeFlag = mission.always_on ? '' : ' --complete-on-pass';
|
|
1726
1746
|
return `next move: run atris mission run ${mission.id}${completeFlag}`;
|
|
@@ -1826,9 +1846,18 @@ function missionSelectableForCodexGoal(mission, now = new Date()) {
|
|
|
1826
1846
|
return true;
|
|
1827
1847
|
}
|
|
1828
1848
|
|
|
1829
|
-
function selectCodexGoalMission(root = process.cwd(), now = new Date()) {
|
|
1849
|
+
function selectCodexGoalMission(root = process.cwd(), options = {}, now = new Date()) {
|
|
1850
|
+
const requestedId = String(options.missionId || '').trim();
|
|
1830
1851
|
const candidates = listMissions(root)
|
|
1852
|
+
.filter((mission) => runnerUsesCallerSession(mission.runner))
|
|
1831
1853
|
.filter((mission) => missionSelectableForCodexGoal(mission, now));
|
|
1854
|
+
if (requestedId) {
|
|
1855
|
+
const exact = candidates.find((mission) => mission.id === requestedId || mission.id.startsWith(requestedId));
|
|
1856
|
+
if (exact) {
|
|
1857
|
+
const due = exact.verifier && missionDueAt(exact, now);
|
|
1858
|
+
return { mission: exact, reason: due ? 'due' : 'selected' };
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1832
1861
|
|
|
1833
1862
|
candidates.sort((a, b) => {
|
|
1834
1863
|
const aCaller = runnerUsesCallerSession(a.runner) ? 1 : 0;
|
|
@@ -1850,11 +1879,91 @@ function selectCodexGoalMission(root = process.cwd(), now = new Date()) {
|
|
|
1850
1879
|
return { mission, reason: due ? 'due' : 'active' };
|
|
1851
1880
|
}
|
|
1852
1881
|
|
|
1882
|
+
function selectAtrisGoalMission(root = process.cwd(), options = {}, now = new Date()) {
|
|
1883
|
+
const requestedId = String(options.missionId || '').trim();
|
|
1884
|
+
const runnable = listMissions(root).filter((mission) => missionIsRunnable(mission));
|
|
1885
|
+
if (requestedId) {
|
|
1886
|
+
const exact = runnable.find((mission) => mission.id === requestedId || mission.id.startsWith(requestedId));
|
|
1887
|
+
if (exact) return { mission: exact, reason: 'selected' };
|
|
1888
|
+
}
|
|
1889
|
+
runnable.sort((a, b) => {
|
|
1890
|
+
const aDue = a.verifier && missionDueAt(a, now) ? 1 : 0;
|
|
1891
|
+
const bDue = b.verifier && missionDueAt(b, now) ? 1 : 0;
|
|
1892
|
+
if (aDue !== bDue) return bDue - aDue;
|
|
1893
|
+
|
|
1894
|
+
const aTime = missionSortTime(a);
|
|
1895
|
+
const bTime = missionSortTime(b);
|
|
1896
|
+
return bTime - aTime;
|
|
1897
|
+
});
|
|
1898
|
+
const mission = runnable[0] || null;
|
|
1899
|
+
if (!mission) return null;
|
|
1900
|
+
const due = mission.verifier && missionDueAt(mission, now);
|
|
1901
|
+
return { mission, reason: due ? 'due' : 'active' };
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1853
1904
|
function codexGoalObjective(mission) {
|
|
1854
1905
|
return mission.objective;
|
|
1855
1906
|
}
|
|
1856
1907
|
|
|
1908
|
+
function codexNativeGoalAck(mission, objective = codexGoalObjective(mission)) {
|
|
1909
|
+
const ack = mission?.native_goal_ack || null;
|
|
1910
|
+
if (!ack || String(ack.runtime || '').toLowerCase() !== 'codex') return null;
|
|
1911
|
+
if (ack.status !== 'active') return null;
|
|
1912
|
+
if (String(ack.objective || '') !== String(objective || '')) return null;
|
|
1913
|
+
return ack;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
function codexGoalAckCommand(mission, objective = codexGoalObjective(mission)) {
|
|
1917
|
+
return `atris mission goal ack ${mission.id} --runtime codex --status active --objective ${shellQuote(objective)} --json`;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
function codexNativeGoalAction(mission, objective = codexGoalObjective(mission)) {
|
|
1921
|
+
return {
|
|
1922
|
+
runtime: 'codex',
|
|
1923
|
+
tool: 'create_goal',
|
|
1924
|
+
args: { objective },
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
function codexNativeGoalStartInstruction(mission, objective = codexGoalObjective(mission)) {
|
|
1929
|
+
return `Call native Codex create_goal({ objective: ${JSON.stringify(objective)} }), then run ${codexGoalAckCommand(mission, objective)}`;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
function codexNativeGoalBlockPayload(mission) {
|
|
1933
|
+
const objective = codexGoalObjective(mission);
|
|
1934
|
+
return {
|
|
1935
|
+
ok: false,
|
|
1936
|
+
code: 'native_goal_not_started',
|
|
1937
|
+
mission_id: mission.id,
|
|
1938
|
+
objective,
|
|
1939
|
+
requires_native_goal_start: true,
|
|
1940
|
+
native_goal_action: codexNativeGoalAction(mission, objective),
|
|
1941
|
+
native_goal_ack_command: codexGoalAckCommand(mission, objective),
|
|
1942
|
+
next_action: codexNativeGoalStartInstruction(mission, objective),
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
function maybeBlockUntilCodexNativeGoalStarted(mission, asJson) {
|
|
1947
|
+
if (!isCodexGoalMission(mission) || codexNativeGoalAck(mission)) return;
|
|
1948
|
+
const payload = codexNativeGoalBlockPayload(mission);
|
|
1949
|
+
if (asJson) console.log(JSON.stringify(payload, null, 2));
|
|
1950
|
+
else console.error(payload.next_action);
|
|
1951
|
+
process.exit(2);
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
function returnIfCodexNativeGoalNotStarted(mission, asJson) {
|
|
1955
|
+
if (!isCodexGoalMission(mission) || codexNativeGoalAck(mission)) return false;
|
|
1956
|
+
const payload = codexNativeGoalBlockPayload(mission);
|
|
1957
|
+
if (asJson) console.log(JSON.stringify(payload, null, 2));
|
|
1958
|
+
else console.error(payload.next_action);
|
|
1959
|
+
process.exitCode = 2;
|
|
1960
|
+
return true;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1857
1963
|
function codexGoalNextCommand(mission) {
|
|
1964
|
+
if (isCodexGoalMission(mission) && !codexNativeGoalAck(mission)) {
|
|
1965
|
+
return codexNativeGoalStartInstruction(mission);
|
|
1966
|
+
}
|
|
1858
1967
|
const taskSpine = missionTaskSpine(mission);
|
|
1859
1968
|
if (taskSpine && !taskSpine.has_task && taskSpine.ensure_task_command) {
|
|
1860
1969
|
return taskSpine.ensure_task_command;
|
|
@@ -1874,19 +1983,22 @@ function codexGoalNextCommand(mission) {
|
|
|
1874
1983
|
}
|
|
1875
1984
|
|
|
1876
1985
|
function codexVisibleGoalBridge(mission, goalObjective) {
|
|
1986
|
+
const ack = codexNativeGoalAck(mission, goalObjective);
|
|
1877
1987
|
return {
|
|
1878
1988
|
schema: 'atris.visible_chat_goal_bridge.v1',
|
|
1879
1989
|
runtime: 'codex',
|
|
1880
1990
|
source: 'atris_mission',
|
|
1881
1991
|
mission_id: mission.id,
|
|
1882
1992
|
desired_objective: goalObjective,
|
|
1883
|
-
status: 'needs_runtime_write',
|
|
1993
|
+
status: ack ? 'active' : 'needs_runtime_write',
|
|
1994
|
+
acknowledged_at: ack?.acknowledged_at || null,
|
|
1884
1995
|
state_file: '.atris/state/codex_goal.json',
|
|
1885
1996
|
status_file: 'atris/status/codex-goal.md',
|
|
1886
1997
|
operations: {
|
|
1887
1998
|
read_current_goal: 'get_goal',
|
|
1888
1999
|
keep_if_matching: 'if current goal objective equals goal.objective, continue the mission',
|
|
1889
2000
|
create_when_empty_or_completed: 'create_goal({ objective: goal.objective })',
|
|
2001
|
+
ack_after_create: codexGoalAckCommand(mission, goalObjective),
|
|
1890
2002
|
complete_after_proof: 'update_goal({ status: "complete" })',
|
|
1891
2003
|
refresh_on_phase_change: 'atris mission goal --json before continuing changed work',
|
|
1892
2004
|
refresh_next_candidate: 'atris mission goal --json',
|
|
@@ -1894,6 +2006,7 @@ function codexVisibleGoalBridge(mission, goalObjective) {
|
|
|
1894
2006
|
guardrails: [
|
|
1895
2007
|
'Do not complete a human-set active goal unless it matches this mission goal or the mission receipt proves handoff.',
|
|
1896
2008
|
'If create_goal fails because another goal is active, keep this bridge waiting for the visible goal slot.',
|
|
2009
|
+
'Do not run mission work for runner=codex_goal until ack_after_create has been recorded.',
|
|
1897
2010
|
'When the work changes phase or objective, refresh the mission goal before continuing.',
|
|
1898
2011
|
],
|
|
1899
2012
|
};
|
|
@@ -1909,7 +2022,7 @@ function codexGoalToolContract(mission) {
|
|
|
1909
2022
|
visible_goal_bridge: 'goal.visible_goal',
|
|
1910
2023
|
platform_requirement: 'Codex runtime must expose replace_goal/set_goal, or allow update_goal({ status: "complete" }) followed by create_goal({ objective }).',
|
|
1911
2024
|
phase_change_refresh: 'before changed follow-up work, run atris mission goal --json and mirror the returned visible goal',
|
|
1912
|
-
runtime_tool_sequence: 'get_goal -> update_goal({ status: "complete" }) after proof or phase change -> atris mission goal --json
|
|
2025
|
+
runtime_tool_sequence: 'get_goal -> create_goal({ objective }) -> atris mission goal ack <mission-id> --runtime codex --status active --objective "<objective>" --json -> do work -> update_goal({ status: "complete" }) after proof or phase change -> atris mission goal --json',
|
|
1913
2026
|
blocked_without_platform_goal_write: true,
|
|
1914
2027
|
mission_id: mission.id,
|
|
1915
2028
|
};
|
|
@@ -1972,7 +2085,7 @@ function writeCodexGoalState(payload, root = process.cwd()) {
|
|
|
1972
2085
|
|
|
1973
2086
|
function buildCodexGoalPayload(root = process.cwd(), options = {}) {
|
|
1974
2087
|
const heartbeatMode = options.heartbeat === true;
|
|
1975
|
-
const selected = selectCodexGoalMission(root);
|
|
2088
|
+
const selected = selectCodexGoalMission(root, options);
|
|
1976
2089
|
if (!selected) {
|
|
1977
2090
|
const heartbeat = heartbeatMode ? codexGoalHeartbeat(null, null) : undefined;
|
|
1978
2091
|
return {
|
|
@@ -1987,6 +2100,7 @@ function buildCodexGoalPayload(root = process.cwd(), options = {}) {
|
|
|
1987
2100
|
const taskSpine = missionTaskSpine(mission);
|
|
1988
2101
|
const missionView = missionStatusView(mission);
|
|
1989
2102
|
const objective = codexGoalObjective(mission);
|
|
2103
|
+
const ack = codexNativeGoalAck(mission, objective);
|
|
1990
2104
|
const goal = {
|
|
1991
2105
|
objective,
|
|
1992
2106
|
mission_id: mission.id,
|
|
@@ -2000,6 +2114,10 @@ function buildCodexGoalPayload(root = process.cwd(), options = {}) {
|
|
|
2000
2114
|
replace_after: 'After proof or verifier pass, run atris mission goal --json again and replace the Codex /goal with the returned objective.',
|
|
2001
2115
|
visible_goal: codexVisibleGoalBridge(mission, objective),
|
|
2002
2116
|
codex_tool_contract: codexGoalToolContract(mission),
|
|
2117
|
+
requires_native_goal_start: !ack,
|
|
2118
|
+
native_goal_action: ack ? null : codexNativeGoalAction(mission, objective),
|
|
2119
|
+
native_goal_ack_command: codexGoalAckCommand(mission, objective),
|
|
2120
|
+
native_goal_ack: ack,
|
|
2003
2121
|
};
|
|
2004
2122
|
const heartbeat = heartbeatMode ? codexGoalHeartbeat(goal, mission) : undefined;
|
|
2005
2123
|
return {
|
|
@@ -2008,6 +2126,8 @@ function buildCodexGoalPayload(root = process.cwd(), options = {}) {
|
|
|
2008
2126
|
goal,
|
|
2009
2127
|
mission: missionView,
|
|
2010
2128
|
heartbeat,
|
|
2129
|
+
requires_native_goal_start: goal.requires_native_goal_start,
|
|
2130
|
+
native_goal_action: goal.native_goal_action,
|
|
2011
2131
|
};
|
|
2012
2132
|
}
|
|
2013
2133
|
|
|
@@ -2021,6 +2141,138 @@ function refreshCodexGoalController(root = process.cwd(), options = {}) {
|
|
|
2021
2141
|
};
|
|
2022
2142
|
}
|
|
2023
2143
|
|
|
2144
|
+
function atrisVisibleGoalBridge(mission, goalObjective) {
|
|
2145
|
+
return {
|
|
2146
|
+
schema: 'atris.visible_goal_bridge.v1',
|
|
2147
|
+
runtime: String(mission.runner || 'manual'),
|
|
2148
|
+
source: 'atris_mission',
|
|
2149
|
+
mission_id: mission.id,
|
|
2150
|
+
desired_objective: goalObjective,
|
|
2151
|
+
status: 'active',
|
|
2152
|
+
state_file: '.atris/state/atris_goal.json',
|
|
2153
|
+
status_file: 'atris/status/atris-goal.md',
|
|
2154
|
+
operations: {
|
|
2155
|
+
read_current_goal: 'atris mission goal --runtime atris --json',
|
|
2156
|
+
update_from_mission: 'atris mission tick <mission-id> --summary "<what changed>" --json',
|
|
2157
|
+
refresh_on_phase_change: 'atris mission goal --runtime atris --json before continuing changed work',
|
|
2158
|
+
complete_after_proof: 'atris mission complete <mission-id> --proof "<receipt_path>" --json',
|
|
2159
|
+
},
|
|
2160
|
+
guardrails: [
|
|
2161
|
+
'Atris owns this goal state; no external native-goal tool ack is required.',
|
|
2162
|
+
'Do not claim completion until the mission has proof or a recorded blocked human ask.',
|
|
2163
|
+
'Use the mission task spine when task_spine.current_step_command is present.',
|
|
2164
|
+
],
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
function atrisGoalToolContract(mission) {
|
|
2169
|
+
return {
|
|
2170
|
+
current_policy: 'keep one Atris-owned visible goal active for mission/chat/fast runtimes',
|
|
2171
|
+
read_current_goal: 'atris mission goal --runtime atris --json',
|
|
2172
|
+
set_next_goal: 'atris mission run "<objective>" --runner atris2|manual|claude',
|
|
2173
|
+
complete_current_goal: 'atris mission complete <mission-id> --proof "<receipt_path>" --json',
|
|
2174
|
+
visible_goal_bridge: 'goal.visible_goal',
|
|
2175
|
+
platform_requirement: 'No native platform goal tool is required; Atris mission/task state is the source of truth.',
|
|
2176
|
+
runtime_tool_sequence: 'atris mission run -> atris_goal_state active -> ax fast / atris chat shows goal -> mission tick/task proof -> mission complete',
|
|
2177
|
+
blocked_without_platform_goal_write: false,
|
|
2178
|
+
mission_id: mission.id,
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
function buildAtrisGoalPayload(root = process.cwd(), options = {}) {
|
|
2183
|
+
const heartbeatMode = options.heartbeat === true;
|
|
2184
|
+
const selected = selectAtrisGoalMission(root, options);
|
|
2185
|
+
if (!selected) {
|
|
2186
|
+
return {
|
|
2187
|
+
ok: true,
|
|
2188
|
+
action: heartbeatMode ? 'atris_goal_heartbeat' : 'no_goal_candidate',
|
|
2189
|
+
mission: null,
|
|
2190
|
+
heartbeat: heartbeatMode ? { heavy_work_performed: false, next_heavy_command: null } : undefined,
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
const { mission, reason } = selected;
|
|
2195
|
+
const taskSpine = missionTaskSpine(mission);
|
|
2196
|
+
const missionView = missionStatusView(mission);
|
|
2197
|
+
const objective = mission.objective;
|
|
2198
|
+
const goal = {
|
|
2199
|
+
objective,
|
|
2200
|
+
mission_id: mission.id,
|
|
2201
|
+
mission_objective: mission.objective,
|
|
2202
|
+
mission_status: mission.status,
|
|
2203
|
+
owner: taskSpine?.owner || mission.owner,
|
|
2204
|
+
executed_by: taskSpine?.executed_by || mission.executed_by || null,
|
|
2205
|
+
runner: mission.runner || 'manual',
|
|
2206
|
+
model: mission.model || null,
|
|
2207
|
+
task_spine: taskSpine,
|
|
2208
|
+
reason,
|
|
2209
|
+
next_command: codexGoalNextCommand(mission),
|
|
2210
|
+
replace_after: 'After proof or phase change, run atris mission goal --runtime atris --json and continue from the returned next_command.',
|
|
2211
|
+
visible_goal: atrisVisibleGoalBridge(mission, objective),
|
|
2212
|
+
atris_tool_contract: atrisGoalToolContract(mission),
|
|
2213
|
+
requires_native_goal_start: false,
|
|
2214
|
+
native_goal_action: null,
|
|
2215
|
+
};
|
|
2216
|
+
return {
|
|
2217
|
+
ok: true,
|
|
2218
|
+
action: heartbeatMode ? 'atris_goal_heartbeat' : 'atris_goal_candidate',
|
|
2219
|
+
goal,
|
|
2220
|
+
mission: missionView,
|
|
2221
|
+
heartbeat: heartbeatMode ? codexGoalHeartbeat(goal, mission) : undefined,
|
|
2222
|
+
requires_native_goal_start: false,
|
|
2223
|
+
native_goal_action: null,
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
function writeAtrisGoalState(payload, root = process.cwd()) {
|
|
2228
|
+
const paths = statePaths(root);
|
|
2229
|
+
const state = {
|
|
2230
|
+
schema: 'atris.goal_controller.v1',
|
|
2231
|
+
updated_at: stampIso(),
|
|
2232
|
+
...payload,
|
|
2233
|
+
};
|
|
2234
|
+
fs.mkdirSync(path.dirname(paths.atrisGoalJson), { recursive: true });
|
|
2235
|
+
fs.writeFileSync(paths.atrisGoalJson, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
2236
|
+
|
|
2237
|
+
const lines = [
|
|
2238
|
+
'# Atris Goal Controller',
|
|
2239
|
+
'',
|
|
2240
|
+
'<!-- Generated by Atris. Do not hand-edit. -->',
|
|
2241
|
+
'',
|
|
2242
|
+
`- updated: ${state.updated_at}`,
|
|
2243
|
+
`- action: ${state.action}`,
|
|
2244
|
+
];
|
|
2245
|
+
if (state.goal) {
|
|
2246
|
+
lines.push(`- mission: ${state.goal.mission_id}`);
|
|
2247
|
+
lines.push(`- runner: ${state.goal.runner}`);
|
|
2248
|
+
lines.push(`- status: ${state.goal.mission_status}`);
|
|
2249
|
+
lines.push(`- reason: ${state.goal.reason}`);
|
|
2250
|
+
lines.push(`- objective: ${state.goal.objective}`);
|
|
2251
|
+
lines.push(`- next: ${state.goal.next_command}`);
|
|
2252
|
+
lines.push(`- visible goal: ${state.goal.visible_goal.status}`);
|
|
2253
|
+
} else {
|
|
2254
|
+
lines.push('- mission: none');
|
|
2255
|
+
}
|
|
2256
|
+
lines.push('');
|
|
2257
|
+
fs.mkdirSync(path.dirname(paths.atrisGoalStatus), { recursive: true });
|
|
2258
|
+
fs.writeFileSync(paths.atrisGoalStatus, lines.join('\n'), 'utf8');
|
|
2259
|
+
return {
|
|
2260
|
+
state_path: paths.atrisGoalJson,
|
|
2261
|
+
status_path: paths.atrisGoalStatus,
|
|
2262
|
+
state,
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
function refreshAtrisGoalController(root = process.cwd(), options = {}) {
|
|
2267
|
+
const payload = buildAtrisGoalPayload(root, options);
|
|
2268
|
+
const rendered = writeAtrisGoalState(payload, root);
|
|
2269
|
+
return {
|
|
2270
|
+
...payload,
|
|
2271
|
+
state_path: rendered.state_path,
|
|
2272
|
+
status_path: rendered.status_path,
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2024
2276
|
function runAtrisMissionJsonCommand(root, args, options = {}) {
|
|
2025
2277
|
const cliPath = path.join(__dirname, '..', 'bin', 'atris.js');
|
|
2026
2278
|
const result = spawnSync(process.execPath, [cliPath, ...args], {
|
|
@@ -2480,6 +2732,8 @@ async function runMission(args) {
|
|
|
2480
2732
|
process.exit(0);
|
|
2481
2733
|
}
|
|
2482
2734
|
|
|
2735
|
+
maybeBlockUntilCodexNativeGoalStarted(mission, asJson);
|
|
2736
|
+
|
|
2483
2737
|
const preLockRunner = String(mission.runner || '').trim().toLowerCase();
|
|
2484
2738
|
const preLockCallerSession = runnerUsesCallerSession(mission.runner);
|
|
2485
2739
|
if (!skipClaude && !preLockCallerSession && preLockRunner !== 'atris2') {
|
|
@@ -2520,6 +2774,7 @@ async function runMission(args) {
|
|
|
2520
2774
|
console.error(`Mission ${mission.id} is ${mission.status}; nothing to run.`);
|
|
2521
2775
|
return;
|
|
2522
2776
|
}
|
|
2777
|
+
if (returnIfCodexNativeGoalNotStarted(mission, asJson)) return;
|
|
2523
2778
|
if (mission.status === 'paused') {
|
|
2524
2779
|
mission = saveMission({
|
|
2525
2780
|
...mission,
|
|
@@ -2849,10 +3104,11 @@ async function runMission(args) {
|
|
|
2849
3104
|
elapsed_seconds: (Date.now() - startedAt) / 1000,
|
|
2850
3105
|
worktree: summaryWorktree,
|
|
2851
3106
|
});
|
|
3107
|
+
const atrisGoalState = refreshAtrisGoalController(cwd, { missionId: mission.id });
|
|
2852
3108
|
const codexGoalState = refreshCodexGoalController(cwd);
|
|
2853
3109
|
|
|
2854
3110
|
printJsonOrText(
|
|
2855
|
-
{ ok: true, action: 'mission_run', mission, ran_ticks: ranTicks, tick_count: ticks.length, ticks, pause_reason: pauseReason, session_id: sessionId, summary_receipt: finalReceipt, worktree: summaryWorktree, codex_goal_state: codexGoalState, continuation_goal: continuationGoal },
|
|
3111
|
+
{ ok: true, action: 'mission_run', mission, ran_ticks: ranTicks, tick_count: ticks.length, ticks, pause_reason: pauseReason, session_id: sessionId, summary_receipt: finalReceipt, worktree: summaryWorktree, atris_goal_state: atrisGoalState, codex_goal_state: codexGoalState, continuation_goal: continuationGoal },
|
|
2856
3112
|
[
|
|
2857
3113
|
`Ran mission ${mission.id}`,
|
|
2858
3114
|
` objective: ${mission.objective}`,
|
|
@@ -2902,6 +3158,7 @@ function tickMission(args) {
|
|
|
2902
3158
|
printJsonOrText({ ok: true, action: 'tick_skipped', mission: saved }, [`Skipped ${mission.id}: ${mission.status}`], asJson);
|
|
2903
3159
|
return;
|
|
2904
3160
|
}
|
|
3161
|
+
if (returnIfCodexNativeGoalNotStarted(mission, asJson)) return;
|
|
2905
3162
|
|
|
2906
3163
|
// Per the /mission skill design, the calling Claude session IS the per-tick LLM.
|
|
2907
3164
|
// This CLI subcommand records the tick: writes a structured receipt (matching the
|
|
@@ -2994,9 +3251,10 @@ function tickMission(args) {
|
|
|
2994
3251
|
? seedMissionRunContinuation(saved, cwd, receiptPath)
|
|
2995
3252
|
: null;
|
|
2996
3253
|
const outputMission = continuationGoal?.parent || saved;
|
|
3254
|
+
const atrisGoalState = refreshAtrisGoalController(process.cwd(), { missionId: outputMission.id });
|
|
2997
3255
|
const codexGoalState = refreshCodexGoalController(process.cwd());
|
|
2998
3256
|
printJsonOrText(
|
|
2999
|
-
{ ok: true, action: 'mission_tick', mission: outputMission, tick: tickRecord, verifier_result: verifierResult, receipt_path: receiptPath, log_path: logPath, codex_goal_state: codexGoalState, continuation_goal: continuationGoal },
|
|
3257
|
+
{ ok: true, action: 'mission_tick', mission: outputMission, tick: tickRecord, verifier_result: verifierResult, receipt_path: receiptPath, log_path: logPath, atris_goal_state: atrisGoalState, codex_goal_state: codexGoalState, continuation_goal: continuationGoal },
|
|
3000
3258
|
[
|
|
3001
3259
|
`Ticked mission: ${outputMission.objective}`,
|
|
3002
3260
|
`State: ${outputMission.status}`,
|
|
@@ -3088,6 +3346,7 @@ function completeMission(args) {
|
|
|
3088
3346
|
const continuationGoal = seedMissionRunContinuation(saved, process.cwd(), proof);
|
|
3089
3347
|
const outputMission = continuationGoal?.parent || saved;
|
|
3090
3348
|
const logPath = appendMemberLog(outputMission.owner, 'Mission completed', { mission: outputMission.objective, proof });
|
|
3349
|
+
const atrisGoalState = refreshAtrisGoalController(process.cwd(), { missionId: continuationGoal?.mission?.id || outputMission.id });
|
|
3091
3350
|
const codexGoalState = refreshCodexGoalController(process.cwd());
|
|
3092
3351
|
printJsonOrText(
|
|
3093
3352
|
{
|
|
@@ -3097,6 +3356,7 @@ function completeMission(args) {
|
|
|
3097
3356
|
landing: completion.landing,
|
|
3098
3357
|
result: completion.result,
|
|
3099
3358
|
log_path: logPath,
|
|
3359
|
+
atris_goal_state: atrisGoalState,
|
|
3100
3360
|
codex_goal_state: codexGoalState,
|
|
3101
3361
|
xp_next_command: xpNextCommand,
|
|
3102
3362
|
continuation_goal: continuationGoal,
|
|
@@ -3162,6 +3422,29 @@ function stopMission(args) {
|
|
|
3162
3422
|
|
|
3163
3423
|
function goalMission(args) {
|
|
3164
3424
|
const asJson = wantsJson(args);
|
|
3425
|
+
if (args[0] === 'ack') {
|
|
3426
|
+
return ackMissionGoal(args.slice(1));
|
|
3427
|
+
}
|
|
3428
|
+
const runtime = String(readFlag(args, '--runtime', 'codex') || 'codex').trim().toLowerCase();
|
|
3429
|
+
if (runtime === 'atris' || runtime === 'atris2' || runtime === 'ax') {
|
|
3430
|
+
const heartbeatMode = hasFlag(args, '--heartbeat');
|
|
3431
|
+
const payload = refreshAtrisGoalController(process.cwd(), { heartbeat: heartbeatMode });
|
|
3432
|
+
if (!payload.goal) {
|
|
3433
|
+
printJsonOrText(payload, ['No active mission found for Atris goal.'], asJson);
|
|
3434
|
+
return;
|
|
3435
|
+
}
|
|
3436
|
+
printJsonOrText(
|
|
3437
|
+
payload,
|
|
3438
|
+
[
|
|
3439
|
+
`Atris goal: ${payload.goal.objective}`,
|
|
3440
|
+
`Runner: ${payload.goal.runner}`,
|
|
3441
|
+
`Next: ${payload.goal.next_command}`,
|
|
3442
|
+
payload.goal.replace_after,
|
|
3443
|
+
],
|
|
3444
|
+
asJson,
|
|
3445
|
+
);
|
|
3446
|
+
return;
|
|
3447
|
+
}
|
|
3165
3448
|
const heartbeatMode = hasFlag(args, '--heartbeat');
|
|
3166
3449
|
const payload = refreshCodexGoalController(process.cwd(), { heartbeat: heartbeatMode });
|
|
3167
3450
|
if (!payload.goal) {
|
|
@@ -3185,6 +3468,59 @@ function goalMission(args) {
|
|
|
3185
3468
|
);
|
|
3186
3469
|
}
|
|
3187
3470
|
|
|
3471
|
+
function ackMissionGoal(args) {
|
|
3472
|
+
const asJson = wantsJson(args);
|
|
3473
|
+
const ref = stripKnownFlags(args, ['--runtime', '--status', '--objective'], ['--json'])[0] || '';
|
|
3474
|
+
if (!ref) {
|
|
3475
|
+
exitMissionError('Usage: atris mission goal ack <mission-id> --runtime codex --status active --objective "<objective>" --json', 1, asJson);
|
|
3476
|
+
}
|
|
3477
|
+
const runtime = String(readFlag(args, '--runtime', 'codex') || 'codex').trim().toLowerCase();
|
|
3478
|
+
const status = String(readFlag(args, '--status', 'active') || 'active').trim().toLowerCase();
|
|
3479
|
+
const mission = resolveMission(ref);
|
|
3480
|
+
if (!mission) {
|
|
3481
|
+
exitMissionError(`Mission "${ref}" not found.`, 1, asJson);
|
|
3482
|
+
}
|
|
3483
|
+
if (!isCodexGoalMission(mission)) {
|
|
3484
|
+
exitMissionError(`Mission "${ref}" uses runner=${mission.runner || 'manual'}; native Codex goal ack is only for runner=codex_goal.`, 2, asJson);
|
|
3485
|
+
}
|
|
3486
|
+
if (runtime !== 'codex' || status !== 'active') {
|
|
3487
|
+
exitMissionError('Native goal ack requires --runtime codex --status active.', 2, asJson);
|
|
3488
|
+
}
|
|
3489
|
+
const expectedObjective = codexGoalObjective(mission);
|
|
3490
|
+
const objective = readFlag(args, '--objective', expectedObjective);
|
|
3491
|
+
if (objective !== expectedObjective) {
|
|
3492
|
+
exitMissionError(`Native goal objective mismatch. Expected: ${expectedObjective}`, 2, asJson);
|
|
3493
|
+
}
|
|
3494
|
+
const ack = {
|
|
3495
|
+
runtime: 'codex',
|
|
3496
|
+
status: 'active',
|
|
3497
|
+
objective,
|
|
3498
|
+
acknowledged_at: stampIso(),
|
|
3499
|
+
};
|
|
3500
|
+
const nextMission = {
|
|
3501
|
+
...mission,
|
|
3502
|
+
native_goal_ack: ack,
|
|
3503
|
+
next_action: codexGoalNextCommand({ ...mission, native_goal_ack: ack }),
|
|
3504
|
+
};
|
|
3505
|
+
const { mission: saved } = saveMission(nextMission, process.cwd(), 'mission_native_goal_ack', { ack });
|
|
3506
|
+
const payload = refreshCodexGoalController(process.cwd());
|
|
3507
|
+
printJsonOrText(
|
|
3508
|
+
{
|
|
3509
|
+
ok: true,
|
|
3510
|
+
action: 'native_goal_acknowledged',
|
|
3511
|
+
mission: saved,
|
|
3512
|
+
native_goal_ack: ack,
|
|
3513
|
+
codex_goal_state: payload,
|
|
3514
|
+
next_command: payload.goal?.next_command || `atris mission tick ${saved.id} --summary "<what changed>"`,
|
|
3515
|
+
},
|
|
3516
|
+
[
|
|
3517
|
+
`Native goal active: ${saved.objective}`,
|
|
3518
|
+
`Next: ${payload.goal?.next_command || saved.next_action}`,
|
|
3519
|
+
],
|
|
3520
|
+
asJson,
|
|
3521
|
+
);
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3188
3524
|
async function goalLoopMission(args) {
|
|
3189
3525
|
const asJson = wantsJson(args);
|
|
3190
3526
|
const noClaude = hasFlag(args, '--no-claude');
|
|
@@ -3281,7 +3617,8 @@ atris mission - durable goal + loop + owner + proof state
|
|
|
3281
3617
|
atris mission watch [id] [--interval <s>] [--idle-every <s>] Live heartbeat: prints a line per tick as it lands
|
|
3282
3618
|
atris mission layers [--mission <id-substr>] [--since <date>] [--json] Per-layer growth curve across tick receipts
|
|
3283
3619
|
(rolls up sibling git-worktree missions; --local scopes to this checkout)
|
|
3284
|
-
atris mission goal [--heartbeat] [--json]
|
|
3620
|
+
atris mission goal [--runtime codex|atris] [--heartbeat] [--json]
|
|
3621
|
+
atris mission goal ack <id> --runtime codex --status active --objective "<objective>" --json
|
|
3285
3622
|
atris mission goal-loop [--max-wall 28800] [--max-iterations 32] [--no-claude] [--json]
|
|
3286
3623
|
atris mission tick <id> [--verify] [--complete-on-pass] [--summary "..."] [--json]
|
|
3287
3624
|
atris mission "<objective>" [--owner <member>] Shortcut for: atris mission run "<objective>"
|
|
@@ -3296,7 +3633,7 @@ Autonomy recipe:
|
|
|
3296
3633
|
1. Pick an owner member: atris member create <member> (if missing)
|
|
3297
3634
|
2. Start a current-agent mission with a verifier:
|
|
3298
3635
|
atris mission start "ship one proof" --owner <member> --runner codex_goal --lane code --verify "npm test" --stop "verifier passes" --xp-task
|
|
3299
|
-
3. Codex sessions: atris mission goal --json, then
|
|
3636
|
+
3. Codex sessions: atris mission goal --json, create the native goal, then run atris mission goal ack <id> --runtime codex --status active --objective "<objective>" --json
|
|
3300
3637
|
Overnight controller: atris mission goal --heartbeat --json
|
|
3301
3638
|
Bounded overnight runner: atris mission goal-loop --max-wall 28800 --no-claude --json
|
|
3302
3639
|
4. Do one bounded step, then record it:
|