atris 3.32.0 → 3.33.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/README.md +43 -1
- package/ax +364 -11
- package/bin/atris.js +53 -4
- package/commands/autoland.js +8 -2
- package/commands/engine.js +299 -0
- package/commands/integrations.js +147 -0
- package/commands/member.js +63 -10
- package/commands/mission.js +119 -18
- package/commands/task.js +173 -14
- package/commands/truth.js +70 -10
- package/lib/fleet.js +354 -0
- package/lib/inspect-fields.js +174 -0
- package/lib/runner-command.js +24 -0
- package/lib/task-db.js +38 -0
- package/package.json +2 -1
- package/scripts/agent_worktree.py +72 -0
package/commands/member.js
CHANGED
|
@@ -611,16 +611,48 @@ function startMemberRunMission(name, missionText, args = []) {
|
|
|
611
611
|
}
|
|
612
612
|
}
|
|
613
613
|
|
|
614
|
-
// atris member ping <name> "<message>" — talk to
|
|
615
|
-
//
|
|
616
|
-
// missions in sibling checkouts)
|
|
614
|
+
// atris member ping <name> "<message>" — talk to a busy member mid-run.
|
|
615
|
+
// Two delivery lanes: the member's most recently touched live mission
|
|
616
|
+
// (including --worktree missions in sibling checkouts), whose next tick
|
|
617
|
+
// consumes the note, and the member's claimed task dialogue, which task-loop
|
|
618
|
+
// agents re-read every step without ever ticking a mission. The ping lands on
|
|
619
|
+
// every lane that exists and errors only when neither does.
|
|
620
|
+
function pingClaimedTaskDialogue(name, text, from) {
|
|
621
|
+
let taskDb;
|
|
622
|
+
try {
|
|
623
|
+
taskDb = require('../lib/task-db');
|
|
624
|
+
} catch {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
const db = taskDb.open();
|
|
629
|
+
const local = taskDb.listTasks(db, { workspaceRoot: taskDb.workspaceRoot(), status: 'claimed', claimedBy: name });
|
|
630
|
+
const pool = local.length ? local : taskDb.listTasks(db, { status: 'claimed', claimedBy: name });
|
|
631
|
+
if (!pool.length) return null;
|
|
632
|
+
const task = [...pool].sort((a, b) => Number(b.updated_at || 0) - Number(a.updated_at || 0))[0];
|
|
633
|
+
const result = taskDb.noteTask(db, { id: task.id, actor: from, content: text });
|
|
634
|
+
if (!result.noted) return null;
|
|
635
|
+
return { task_id: task.id, title: task.title };
|
|
636
|
+
} catch {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
617
641
|
function memberPing(name, ...args) {
|
|
618
642
|
if (!name || name === '--help' || name === '-h') {
|
|
619
|
-
console.log('Usage: atris member ping <name> "<message>" [--json]');
|
|
620
|
-
console.log('Leaves
|
|
643
|
+
console.log('Usage: atris member ping <name> "<message>" [--from <who>] [--json]');
|
|
644
|
+
console.log('Leaves the note on the member\'s active mission and claimed task dialogue; whichever loop the member runs reads it next.');
|
|
621
645
|
return;
|
|
622
646
|
}
|
|
623
|
-
const
|
|
647
|
+
const asJson = args.includes('--json');
|
|
648
|
+
const rest = args.filter((a) => a !== '--json');
|
|
649
|
+
let from = process.env.USER || 'operator';
|
|
650
|
+
const fromIdx = rest.indexOf('--from');
|
|
651
|
+
if (fromIdx !== -1) {
|
|
652
|
+
from = rest[fromIdx + 1] || from;
|
|
653
|
+
rest.splice(fromIdx, 2);
|
|
654
|
+
}
|
|
655
|
+
const text = rest.filter((a) => !a.startsWith('--')).join(' ').trim();
|
|
624
656
|
if (!text) {
|
|
625
657
|
console.error('atris member ping: message required');
|
|
626
658
|
process.exit(2);
|
|
@@ -633,12 +665,33 @@ function memberPing(name, ...args) {
|
|
|
633
665
|
]
|
|
634
666
|
.filter((m) => m && m.owner === name && !terminal.has(m.status))
|
|
635
667
|
.sort((a, b) => String(b.updated_at || b.created_at || '').localeCompare(String(a.updated_at || a.created_at || '')));
|
|
636
|
-
|
|
637
|
-
|
|
668
|
+
|
|
669
|
+
const taskNote = pingClaimedTaskDialogue(name, text, from);
|
|
670
|
+
|
|
671
|
+
if (!candidates.length && !taskNote) {
|
|
672
|
+
console.error(`atris member ping: no live mission or claimed task for "${name}". Start one: atris member run ${name} "<objective>"`);
|
|
638
673
|
process.exit(1);
|
|
639
674
|
}
|
|
640
|
-
|
|
641
|
-
|
|
675
|
+
|
|
676
|
+
const mission = candidates.length
|
|
677
|
+
? missionMod.pingMission([candidates[0].id, text, '--from', from], { silent: true })
|
|
678
|
+
: null;
|
|
679
|
+
const pendingPings = mission ? (mission.pings || []).filter((p) => p && !p.consumed_at).length : null;
|
|
680
|
+
|
|
681
|
+
if (asJson) {
|
|
682
|
+
console.log(JSON.stringify({
|
|
683
|
+
ok: true,
|
|
684
|
+
action: 'member_ping',
|
|
685
|
+
member: name,
|
|
686
|
+
mission_id: mission ? mission.id : null,
|
|
687
|
+
pending_pings: pendingPings,
|
|
688
|
+
task_id: taskNote ? taskNote.task_id : null,
|
|
689
|
+
}));
|
|
690
|
+
} else {
|
|
691
|
+
if (mission) console.log(`pinged ${mission.id} — the next tick reads it (${pendingPings} unread).`);
|
|
692
|
+
if (taskNote) console.log(`pinged task ${taskNote.task_id} dialogue — ${name} reads it on the next step.`);
|
|
693
|
+
}
|
|
694
|
+
return { mission, task_note: taskNote };
|
|
642
695
|
}
|
|
643
696
|
|
|
644
697
|
function memberRun(name, ...args) {
|
package/commands/mission.js
CHANGED
|
@@ -30,6 +30,15 @@ const {
|
|
|
30
30
|
formatBytes,
|
|
31
31
|
} = require('../lib/runs-prune');
|
|
32
32
|
const { operatorReady, hasAgentJargon } = require('./autoland');
|
|
33
|
+
const {
|
|
34
|
+
MISSION_INSPECT_FIELDS,
|
|
35
|
+
readFieldsFlag,
|
|
36
|
+
stripInspectArgs,
|
|
37
|
+
validateFields,
|
|
38
|
+
missionInspectFieldValues,
|
|
39
|
+
inspectTextLines,
|
|
40
|
+
buildInspectPayload,
|
|
41
|
+
} = require('../lib/inspect-fields');
|
|
33
42
|
|
|
34
43
|
const VALID_STATUSES = new Set(['planning', 'running', 'ready', 'paused', 'blocked', 'stopped', 'complete']);
|
|
35
44
|
const TERMINAL_STATUSES = new Set(['stopped', 'complete']);
|
|
@@ -194,6 +203,7 @@ function printJsonOrText(payload, lines, asJson) {
|
|
|
194
203
|
}
|
|
195
204
|
|
|
196
205
|
const MISSION_RUN_VALUE_FLAGS = [
|
|
206
|
+
'--slots',
|
|
197
207
|
'--max-ticks',
|
|
198
208
|
'--max-wall',
|
|
199
209
|
'--minutes',
|
|
@@ -213,6 +223,8 @@ const MISSION_RUN_VALUE_FLAGS = [
|
|
|
213
223
|
const MISSION_RUN_BOOLEAN_FLAGS = [
|
|
214
224
|
'--json',
|
|
215
225
|
'--due',
|
|
226
|
+
'--fleet',
|
|
227
|
+
'--dry-run',
|
|
216
228
|
'--no-claude',
|
|
217
229
|
'--no-verify',
|
|
218
230
|
'--complete-on-pass',
|
|
@@ -227,6 +239,7 @@ const MISSION_RUN_BOOLEAN_FLAGS = [
|
|
|
227
239
|
'--no-room-auto-run',
|
|
228
240
|
'--allow-native-goal-supersede',
|
|
229
241
|
'--supersede-paused-native-goal',
|
|
242
|
+
'--take-goal-slot',
|
|
230
243
|
'--always-on',
|
|
231
244
|
'--xp-task',
|
|
232
245
|
'--agent-xp',
|
|
@@ -1619,7 +1632,7 @@ function missionFromArgs(args) {
|
|
|
1619
1632
|
'--native-goal-objective',
|
|
1620
1633
|
'--visible-goal-status',
|
|
1621
1634
|
'--visible-goal-objective',
|
|
1622
|
-
], ['--json', '--always-on', '--xp-task', '--agent-xp', '--worktree', '--spend-full-budget', '--use-whole-budget', '--stop-when-done', '--allow-native-goal-supersede', '--supersede-paused-native-goal']).join(' ').trim();
|
|
1635
|
+
], ['--json', '--always-on', '--xp-task', '--agent-xp', '--worktree', '--spend-full-budget', '--use-whole-budget', '--stop-when-done', '--allow-native-goal-supersede', '--supersede-paused-native-goal', '--take-goal-slot']).join(' ').trim();
|
|
1623
1636
|
if (!objective) {
|
|
1624
1637
|
exitMissionError('Usage: atris mission start "<objective>" --owner <member> [--verify "..."] [--cadence manual] [--worktree]', 1, wantsJson(args));
|
|
1625
1638
|
}
|
|
@@ -2795,6 +2808,9 @@ function startMission(args) {
|
|
|
2795
2808
|
const warnings = [missingVerifierWarning(mission)].filter(Boolean);
|
|
2796
2809
|
ensureMemberMissionFile(mission.owner, process.cwd(), mission.objective);
|
|
2797
2810
|
const { mission: saved } = saveMission(mission, process.cwd(), 'mission_started', { objective: mission.objective });
|
|
2811
|
+
const goalSlotHandoff = hasFlag(args, '--take-goal-slot') && isCodexGoalMission(saved)
|
|
2812
|
+
? takeCodexGoalSlotForMission(saved, process.cwd())
|
|
2813
|
+
: null;
|
|
2798
2814
|
const memberState = renderMemberMissionState(saved.owner);
|
|
2799
2815
|
const logPath = appendMemberLog(saved.owner, 'Mission started', {
|
|
2800
2816
|
mission: saved.objective,
|
|
@@ -2805,7 +2821,7 @@ function startMission(args) {
|
|
|
2805
2821
|
});
|
|
2806
2822
|
const worktreeBaseline = captureMissionWorktreeBaseline(saved, process.cwd());
|
|
2807
2823
|
printJsonOrText(
|
|
2808
|
-
{ ok: true, action: 'mission_started', mission: saved, warnings, state_path: statePaths().missionsJsonl, member_state: memberState, log_path: logPath, worktree_baseline: worktreeBaseline ? { path: path.relative(process.cwd(), missionBaselinePath(saved.id)), dirty_count: worktreeBaseline.dirty_count, dirty_hash: worktreeBaseline.dirty_hash } : null },
|
|
2824
|
+
{ ok: true, action: 'mission_started', mission: saved, warnings, goal_slot_handoff: goalSlotHandoff, state_path: statePaths().missionsJsonl, member_state: memberState, log_path: logPath, worktree_baseline: worktreeBaseline ? { path: path.relative(process.cwd(), missionBaselinePath(saved.id)), dirty_count: worktreeBaseline.dirty_count, dirty_hash: worktreeBaseline.dirty_hash } : null },
|
|
2809
2825
|
[
|
|
2810
2826
|
`Started mission: ${saved.objective}`,
|
|
2811
2827
|
`Owner: ${saved.owner}`,
|
|
@@ -2920,6 +2936,9 @@ function startMissionFromRunObjective(objective, args) {
|
|
|
2920
2936
|
...(nativeGoalObjective ? { nativeGoalObjective } : {}),
|
|
2921
2937
|
...(hasFlag(args, '--allow-native-goal-supersede') || hasFlag(args, '--supersede-paused-native-goal') ? { allowNativeGoalSupersede: true } : {}),
|
|
2922
2938
|
};
|
|
2939
|
+
const goalSlotHandoff = hasFlag(args, '--take-goal-slot') && isCodexGoalMission(saved)
|
|
2940
|
+
? takeCodexGoalSlotForMission(saved, process.cwd(), nativeGoalOptions)
|
|
2941
|
+
: null;
|
|
2923
2942
|
const atrisGoalState = refreshAtrisGoalController(process.cwd(), { missionId: saved.id });
|
|
2924
2943
|
const codexGoalState = runnerUsesCallerSession(saved.runner)
|
|
2925
2944
|
? refreshCodexGoalController(process.cwd(), { missionId: saved.id, ...nativeGoalOptions })
|
|
@@ -2944,6 +2963,7 @@ function startMissionFromRunObjective(objective, args) {
|
|
|
2944
2963
|
} : null,
|
|
2945
2964
|
completed_continuation_goal: completedContinuationGoal,
|
|
2946
2965
|
direct_goal_request: directGoalRequest,
|
|
2966
|
+
goal_slot_handoff: goalSlotHandoff,
|
|
2947
2967
|
atris_goal_state: atrisGoalState,
|
|
2948
2968
|
codex_goal_state: codexGoalState,
|
|
2949
2969
|
requires_native_goal_start: codexGoalState?.requires_native_goal_start === true || codexGoalState?.goal?.requires_native_goal_start === true,
|
|
@@ -4611,11 +4631,11 @@ function codexGoalObjective(mission) {
|
|
|
4611
4631
|
return mission.objective;
|
|
4612
4632
|
}
|
|
4613
4633
|
|
|
4614
|
-
function codexNativeGoalAck(mission
|
|
4634
|
+
function codexNativeGoalAck(mission) {
|
|
4615
4635
|
const ack = mission?.native_goal_ack || null;
|
|
4616
4636
|
if (!ack || String(ack.runtime || '').toLowerCase() !== 'codex') return null;
|
|
4617
4637
|
if (ack.status !== 'active') return null;
|
|
4618
|
-
if (String(ack.
|
|
4638
|
+
if (ack.mission_id && String(ack.mission_id) !== String(mission?.id || '')) return null;
|
|
4619
4639
|
return ack;
|
|
4620
4640
|
}
|
|
4621
4641
|
|
|
@@ -4821,6 +4841,33 @@ function activeCodexVisibleGoalOwner(root = process.cwd(), excludeId = '', now =
|
|
|
4821
4841
|
return candidates[0] || null;
|
|
4822
4842
|
}
|
|
4823
4843
|
|
|
4844
|
+
function pauseMissionRecord(mission, reason, root = process.cwd()) {
|
|
4845
|
+
const next = {
|
|
4846
|
+
...mission,
|
|
4847
|
+
status: 'paused',
|
|
4848
|
+
paused_at: stampIso(),
|
|
4849
|
+
stop_reason: reason,
|
|
4850
|
+
next_action: `resume with: atris mission tick ${mission.id}`,
|
|
4851
|
+
};
|
|
4852
|
+
const { mission: saved } = saveMission(next, root, 'mission_paused', { reason, source: 'goal_slot_handoff' });
|
|
4853
|
+
appendMemberLog(saved.owner, 'Mission paused', { mission: saved.objective, reason });
|
|
4854
|
+
return saved;
|
|
4855
|
+
}
|
|
4856
|
+
|
|
4857
|
+
function takeCodexGoalSlotForMission(newMission, root = process.cwd(), options = {}) {
|
|
4858
|
+
if (!newMission || !isCodexGoalMission(newMission)) return null;
|
|
4859
|
+
const runtimeGoalState = codexRuntimeGoalStateFromOptions(options);
|
|
4860
|
+
const activeOwner = activeCodexVisibleGoalOwner(root, newMission.id, new Date(), runtimeGoalState);
|
|
4861
|
+
if (!activeOwner) return null;
|
|
4862
|
+
const reason = `visible goal replaced by ${newMission.id}`;
|
|
4863
|
+
const paused = pauseMissionRecord(activeOwner, reason, root);
|
|
4864
|
+
return {
|
|
4865
|
+
paused_mission_id: paused.id,
|
|
4866
|
+
paused_mission: missionStatusView(paused),
|
|
4867
|
+
reason,
|
|
4868
|
+
};
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4824
4871
|
function codexGoalActiveConflictPayload(newMission, activeMission, request = null, heartbeatMode = false, runtimeGoalState = null) {
|
|
4825
4872
|
const newObjective = codexGoalObjective(newMission);
|
|
4826
4873
|
const pausedConflict = runtimeGoalState?.status === 'paused';
|
|
@@ -5119,7 +5166,7 @@ function buildCodexGoalPayload(root = process.cwd(), options = {}) {
|
|
|
5119
5166
|
const taskSpine = missionTaskSpine(mission);
|
|
5120
5167
|
const missionView = missionStatusView(mission);
|
|
5121
5168
|
const objective = codexGoalObjective(mission);
|
|
5122
|
-
const ack = codexNativeGoalAck(mission
|
|
5169
|
+
const ack = codexNativeGoalAck(mission);
|
|
5123
5170
|
const runtimeNeedsReplace = !ack && codexRuntimeGoalNeedsReplace(runtimeGoalState, objective);
|
|
5124
5171
|
const nativeGoalAction = ack
|
|
5125
5172
|
? null
|
|
@@ -5777,6 +5824,21 @@ async function runMission(args) {
|
|
|
5777
5824
|
help();
|
|
5778
5825
|
return;
|
|
5779
5826
|
}
|
|
5827
|
+
// --fleet: staff every idle capable engine on the board's claimable
|
|
5828
|
+
// safe-lane tasks, build in parallel worktrees, land serially. Humble flag,
|
|
5829
|
+
// full loop — see lib/fleet.js. --dry-run previews the staffing only.
|
|
5830
|
+
if (hasFlag(args, '--fleet')) {
|
|
5831
|
+
const { runFleetFlight } = require('../lib/fleet');
|
|
5832
|
+
const slots = Math.max(1, Number(readFlag(args, '--slots', '')) || 3);
|
|
5833
|
+
const flight = await runFleetFlight({
|
|
5834
|
+
slots,
|
|
5835
|
+
dryRun: hasFlag(args, '--dry-run'),
|
|
5836
|
+
log: asJson ? () => {} : console.log,
|
|
5837
|
+
});
|
|
5838
|
+
if (asJson) console.log(JSON.stringify(flight, null, 2));
|
|
5839
|
+
process.exitCode = flight.paused && flight.paused.length > 0 ? 1 : 0;
|
|
5840
|
+
return;
|
|
5841
|
+
}
|
|
5780
5842
|
const dueMode = hasFlag(args, '--due');
|
|
5781
5843
|
const skipClaude = hasFlag(args, '--no-claude');
|
|
5782
5844
|
const verifyEach = !hasFlag(args, '--no-verify');
|
|
@@ -6723,16 +6785,15 @@ function ackMissionGoal(args) {
|
|
|
6723
6785
|
releaseMissionLock(lock);
|
|
6724
6786
|
exitMissionError(`Mission "${ref}" uses runner=${mission.runner || 'manual'}; native Codex goal ack is only for runner=codex_goal.`, 2, asJson);
|
|
6725
6787
|
}
|
|
6726
|
-
const
|
|
6727
|
-
const
|
|
6728
|
-
|
|
6729
|
-
releaseMissionLock(lock);
|
|
6730
|
-
exitMissionError(`Native goal objective mismatch. Expected: ${expectedObjective}`, 2, asJson);
|
|
6731
|
-
}
|
|
6788
|
+
const canonicalObjective = codexGoalObjective(mission);
|
|
6789
|
+
const reportedObjective = readFlag(args, '--objective', canonicalObjective);
|
|
6790
|
+
const objective = canonicalObjective;
|
|
6732
6791
|
const ack = {
|
|
6733
6792
|
runtime: 'codex',
|
|
6734
6793
|
status: 'active',
|
|
6794
|
+
mission_id: mission.id,
|
|
6735
6795
|
objective,
|
|
6796
|
+
...(reportedObjective && reportedObjective !== objective ? { reported_objective: reportedObjective } : {}),
|
|
6736
6797
|
acknowledged_at: stampIso(),
|
|
6737
6798
|
};
|
|
6738
6799
|
const nextMission = {
|
|
@@ -6848,13 +6909,15 @@ function help() {
|
|
|
6848
6909
|
console.log(`
|
|
6849
6910
|
atris mission - durable goal + loop + owner + proof state
|
|
6850
6911
|
|
|
6851
|
-
atris mission start "<objective>" --owner <member> [--verify "..."] [--always-on] [--xp-task] [--worktree]
|
|
6912
|
+
atris mission start "<objective>" --owner <member> [--verify "..."] [--always-on] [--xp-task] [--worktree] [--take-goal-slot]
|
|
6852
6913
|
[--runner manual|claude|atris2|codex_goal] [--model <id>]
|
|
6853
6914
|
(runner claude spawns local claude -p per tick, --model passes through;
|
|
6854
6915
|
runner atris2 runs each tick as one /atris2/turn on the AtrisOS backend,
|
|
6855
6916
|
default model atris:fast; runner codex_goal publishes the goal for a live
|
|
6856
6917
|
Codex session to pull via atris mission goal)
|
|
6857
6918
|
atris mission status [id] [--status <state>] [--limit <n>] [--local] [--json]
|
|
6919
|
+
atris mission inspect <id> --fields status,runner,ack,pings [--json]
|
|
6920
|
+
Field-selectable mission state (status, runner, native goal ack, ping counts)
|
|
6858
6921
|
atris mission doctor [--local] [--json] Flag no-verifier missions, help missions, stale ready receipts, and blocked always-on loops
|
|
6859
6922
|
atris mission attach-task <id> [--json] Create the missing task spine for an existing active mission
|
|
6860
6923
|
atris mission report [id] [--limit <n>] [--local] [--json] Plain outcome, worker receipt, verifier receipt, and next move
|
|
@@ -6869,8 +6932,9 @@ atris mission - durable goal + loop + owner + proof state
|
|
|
6869
6932
|
atris mission goal-loop [--max-wall 28800] [--max-iterations 32] [--no-claude] [--json]
|
|
6870
6933
|
atris mission tick <id> [--verify ["cmd"]] [--complete-on-pass] [--summary "..."] [--json]
|
|
6871
6934
|
atris mission "<objective>" [--owner <member>] Shortcut for: atris mission run "<objective>"
|
|
6935
|
+
atris mission run --fleet [--slots 3] [--dry-run] [--json] Staff every idle capable engine on claimable safe-lane tasks: parallel worktree builds, serial rebase-before-ship landings, receipt in atris/runs/
|
|
6872
6936
|
atris mission run ["objective"|<member> ["objective"]|id|--due] [--owner <member>] [--max-ticks 4] [--max-wall 3600] [--cadence "15m"]
|
|
6873
|
-
[--native-goal-status active|paused] [--native-goal-objective "..."] [--allow-native-goal-supersede]
|
|
6937
|
+
[--native-goal-status active|paused] [--native-goal-objective "..."] [--allow-native-goal-supersede] [--take-goal-slot]
|
|
6874
6938
|
[--spend-full-budget|--use-whole-budget|--stop-when-done] [--room-preflight|--no-room-preflight]
|
|
6875
6939
|
[--room-auto-run|--no-room-auto-run]
|
|
6876
6940
|
[--no-claude] [--no-verify] [--complete-on-pass] [--no-drain] [--create-next] [--json]
|
|
@@ -7149,7 +7213,39 @@ function findMissionAcrossWorktrees(ref, root = process.cwd()) {
|
|
|
7149
7213
|
// atris mission ping <id> "<message>" — leave a note the mission's next tick
|
|
7150
7214
|
// reads (and consumes) as operator direction. This is how you talk to an
|
|
7151
7215
|
// always-on member mid-run without stopping it.
|
|
7152
|
-
function
|
|
7216
|
+
function inspectMission(args) {
|
|
7217
|
+
const asJson = wantsJson(args);
|
|
7218
|
+
const parsed = readFieldsFlag(args, '--fields');
|
|
7219
|
+
if (!parsed || parsed.error) {
|
|
7220
|
+
exitMissionError(
|
|
7221
|
+
parsed?.error || 'Usage: atris mission inspect <id> --fields status,runner,ack,pings [--json]',
|
|
7222
|
+
2,
|
|
7223
|
+
asJson,
|
|
7224
|
+
);
|
|
7225
|
+
}
|
|
7226
|
+
const fieldError = validateFields(parsed.fields, MISSION_INSPECT_FIELDS, 'mission');
|
|
7227
|
+
if (fieldError) exitMissionError(fieldError, 2, asJson);
|
|
7228
|
+
const ref = stripInspectArgs(args)[0] || '';
|
|
7229
|
+
if (!ref) {
|
|
7230
|
+
exitMissionError('Usage: atris mission inspect <id> --fields status,runner,ack,pings [--json]', 2, asJson);
|
|
7231
|
+
}
|
|
7232
|
+
const found = findMissionAcrossWorktrees(ref);
|
|
7233
|
+
if (!found) exitMissingMission(ref, 1, asJson);
|
|
7234
|
+
const { mission } = found;
|
|
7235
|
+
const values = missionInspectFieldValues(mission, parsed.fields);
|
|
7236
|
+
const payload = buildInspectPayload({
|
|
7237
|
+
action: 'mission_inspect',
|
|
7238
|
+
idKey: 'mission_id',
|
|
7239
|
+
idValue: mission.id,
|
|
7240
|
+
fields: parsed.fields,
|
|
7241
|
+
values,
|
|
7242
|
+
});
|
|
7243
|
+
printJsonOrText(payload, inspectTextLines(parsed.fields, values), asJson);
|
|
7244
|
+
}
|
|
7245
|
+
|
|
7246
|
+
// always-on member mid-run without stopping it. opts.silent skips console
|
|
7247
|
+
// output so callers (member ping) can compose their own multi-lane report.
|
|
7248
|
+
function pingMission(args, opts = {}) {
|
|
7153
7249
|
const asJson = args.includes('--json');
|
|
7154
7250
|
const rest = args.filter((a) => a !== '--json');
|
|
7155
7251
|
let from = process.env.USER || 'operator';
|
|
@@ -7181,10 +7277,12 @@ function pingMission(args) {
|
|
|
7181
7277
|
{ from, text: text.slice(0, 200) },
|
|
7182
7278
|
).mission;
|
|
7183
7279
|
const pending = (saved.pings || []).filter((p) => p && !p.consumed_at).length;
|
|
7184
|
-
if (
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7280
|
+
if (!opts.silent) {
|
|
7281
|
+
if (asJson) {
|
|
7282
|
+
console.log(JSON.stringify({ ok: true, action: 'mission_ping', mission_id: saved.id, pending_pings: pending, ping }));
|
|
7283
|
+
} else {
|
|
7284
|
+
console.log(`pinged ${saved.id} — the next tick reads it (${pending} unread).`);
|
|
7285
|
+
}
|
|
7188
7286
|
}
|
|
7189
7287
|
return saved;
|
|
7190
7288
|
}
|
|
@@ -7235,6 +7333,8 @@ function missionCommand(args) {
|
|
|
7235
7333
|
return goalLoopMission(rest);
|
|
7236
7334
|
case 'ping':
|
|
7237
7335
|
return pingMission(rest);
|
|
7336
|
+
case 'inspect':
|
|
7337
|
+
return inspectMission(rest);
|
|
7238
7338
|
case 'tick':
|
|
7239
7339
|
return tickMission(rest);
|
|
7240
7340
|
case 'run':
|
|
@@ -7257,6 +7357,7 @@ function missionCommand(args) {
|
|
|
7257
7357
|
|
|
7258
7358
|
module.exports = {
|
|
7259
7359
|
missionCommand,
|
|
7360
|
+
inspectMission,
|
|
7260
7361
|
missionHeartbeatLines,
|
|
7261
7362
|
listMissions,
|
|
7262
7363
|
listWorktreeRollupMissions,
|