atris 3.30.1 → 3.30.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/AGENTS.md +6 -0
- package/atris/AGENTS.md +11 -0
- package/atris/CLAUDE.md +5 -0
- package/atris/atris.md +19 -4
- package/atris/policies/atris-design.md +71 -0
- package/atris/policies/design-seed.md +187 -0
- package/atris/skills/atris/SKILL.md +26 -3
- package/atris/skills/design/SKILL.md +3 -2
- package/atris/skills/loop/SKILL.md +5 -3
- package/atris/team/_template/MEMBER.md +19 -0
- package/atris.md +63 -23
- package/ax +1434 -1698
- package/bin/atris.js +5 -1
- package/commands/agent-spawn.js +2 -2
- package/commands/brain.js +92 -7
- package/commands/brainstorm.js +62 -22
- package/commands/business-sync.js +92 -8
- package/commands/business.js +13 -7
- package/commands/chat-scan.js +102 -0
- package/commands/computer.js +758 -15
- package/commands/deck.js +505 -105
- package/commands/init.js +6 -1
- package/commands/launchpad.js +638 -0
- package/commands/log-sync.js +44 -13
- package/commands/loop-front.js +290 -0
- package/commands/member.js +124 -3
- package/commands/mission.js +717 -32
- package/commands/pull.js +37 -28
- package/commands/pulse.js +11 -8
- package/commands/run.js +79 -39
- package/commands/sync.js +36 -17
- package/commands/task.js +342 -66
- package/commands/xp.js +3 -0
- package/commands/youtube.js +221 -7
- package/decks/README.md +89 -0
- package/decks/archetype-catalog.json +180 -0
- package/decks/atris-antislop-pitch.json +48 -0
- package/decks/atris-archetypes-v2.json +83 -0
- package/decks/atris-one-loop-pitch.json +80 -0
- package/decks/atris-seed-pitch-v3.json +118 -0
- package/decks/atris-seed-pitch-v4-skeleton.json +106 -0
- package/decks/atris-seed-pitch-v5.json +109 -0
- package/decks/atris-seed-pitch-v6.json +137 -0
- package/decks/atris-seed-pitch-v7.json +133 -0
- package/decks/atris-single-shot-proof.json +74 -0
- package/decks/mark-pincus-narrative.json +102 -0
- package/decks/mark-pincus-sourcery.json +94 -0
- package/decks/yash-applied-compute-detailed.json +150 -0
- package/decks/yash-applied-compute-generalist.json +82 -0
- package/decks/yash-applied-compute-narrative.json +54 -0
- package/lib/ax-prefs.js +5 -12
- package/lib/chat-log-scan.js +377 -0
- package/lib/context-gatherer.js +35 -1
- package/lib/deck-compose.js +145 -0
- package/lib/deck-history.js +64 -0
- package/lib/deck-layout.js +169 -0
- package/lib/deck-review.js +431 -0
- package/lib/deck-schema.js +154 -0
- package/lib/file-ops.js +2 -2
- package/lib/functional-owner.js +189 -0
- package/lib/slides-deck.js +512 -58
- package/lib/task-db.js +109 -2
- package/package.json +2 -1
- package/templates/business-starter/team/START_HERE.md +12 -8
- package/utils/auth.js +4 -0
- package/utils/config.js +4 -0
- package/atris/atrisDev.md +0 -717
package/commands/mission.js
CHANGED
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const crypto = require('crypto');
|
|
6
|
+
const readline = require('readline');
|
|
6
7
|
const { spawn, spawnSync } = require('child_process');
|
|
7
8
|
const {
|
|
8
9
|
resolveClaudeRunnerModel,
|
|
9
10
|
resolveClaudeRunnerBin,
|
|
10
11
|
} = require('../lib/runner-command');
|
|
12
|
+
const {
|
|
13
|
+
normalizeOwnerSlug,
|
|
14
|
+
resolveFunctionalOwner,
|
|
15
|
+
} = require('../lib/functional-owner');
|
|
11
16
|
|
|
12
17
|
const VALID_STATUSES = new Set(['planning', 'running', 'ready', 'paused', 'blocked', 'stopped', 'complete']);
|
|
13
18
|
const TERMINAL_STATUSES = new Set(['stopped', 'complete']);
|
|
@@ -137,6 +142,58 @@ function printJsonOrText(payload, lines, asJson) {
|
|
|
137
142
|
for (const line of lines) console.log(line);
|
|
138
143
|
}
|
|
139
144
|
|
|
145
|
+
function missionRunInputRequired(asJson = false) {
|
|
146
|
+
const payload = {
|
|
147
|
+
ok: false,
|
|
148
|
+
action: 'mission_input_required',
|
|
149
|
+
prompt: 'What mission should Atris run?',
|
|
150
|
+
owner_prompt: 'Which team member should own it?',
|
|
151
|
+
example: 'atris mission run "make onboarding magical" --owner mission-lead',
|
|
152
|
+
};
|
|
153
|
+
if (asJson) {
|
|
154
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
155
|
+
} else {
|
|
156
|
+
console.error('What mission should Atris run?');
|
|
157
|
+
console.error('Try: atris mission run "make onboarding magical" --owner mission-lead');
|
|
158
|
+
}
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function askLine(rl, question) {
|
|
163
|
+
return new Promise((resolve) => rl.question(question, (answer) => resolve(String(answer || '').trim())));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function removeValueFlag(args, name) {
|
|
167
|
+
const out = [];
|
|
168
|
+
const prefix = `${name}=`;
|
|
169
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
170
|
+
const arg = String(args[i]);
|
|
171
|
+
if (arg === name) {
|
|
172
|
+
if (args[i + 1] && !String(args[i + 1]).startsWith('--')) i += 1;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (arg.startsWith(prefix)) continue;
|
|
176
|
+
out.push(args[i]);
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function promptMissionRunInput(args) {
|
|
182
|
+
const defaultOwner = readFlag(args, '--owner', process.env.ATRIS_AGENT_ID || 'mission-lead');
|
|
183
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
184
|
+
try {
|
|
185
|
+
const objective = await askLine(rl, 'Mission: ');
|
|
186
|
+
if (!objective) missionRunInputRequired(false);
|
|
187
|
+
const owner = await askLine(rl, `Team member [${defaultOwner}]: `);
|
|
188
|
+
return {
|
|
189
|
+
objective,
|
|
190
|
+
args: [...removeValueFlag(args, '--owner'), '--owner', owner || defaultOwner],
|
|
191
|
+
};
|
|
192
|
+
} finally {
|
|
193
|
+
rl.close();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
140
197
|
function loadTaskDb(asJson = false) {
|
|
141
198
|
try {
|
|
142
199
|
return require('../lib/task-db');
|
|
@@ -166,27 +223,33 @@ function createMissionXpTask(mission, root = process.cwd(), asJson = false) {
|
|
|
166
223
|
const db = taskDb.open();
|
|
167
224
|
const workspaceRoot = taskDb.workspaceRoot(root);
|
|
168
225
|
const title = `Mission XP: ${mission.objective}`;
|
|
226
|
+
const ownerResolution = resolveMissionOwner(mission, workspaceRoot);
|
|
227
|
+
const owner = ownerResolution.owner;
|
|
169
228
|
const metadata = {
|
|
170
|
-
assigned_to:
|
|
229
|
+
assigned_to: owner,
|
|
171
230
|
delegate_via: 'mission_goal_loop',
|
|
172
231
|
created_for_day: todayName(),
|
|
173
232
|
goal_id: mission.id,
|
|
174
233
|
goal_objective: mission.objective,
|
|
175
234
|
mission_id: mission.id,
|
|
176
235
|
mission_objective: mission.objective,
|
|
177
|
-
mission_owner:
|
|
236
|
+
mission_owner: owner,
|
|
237
|
+
owner_resolution: ownerResolution.reason,
|
|
178
238
|
mission_lane: mission.lane,
|
|
179
239
|
mission_runner: mission.runner,
|
|
180
240
|
verify: mission.verifier || null,
|
|
181
241
|
stop_condition: mission.stop_condition || null,
|
|
182
242
|
};
|
|
243
|
+
if (ownerResolution.requested_owner && ownerResolution.requested_owner !== owner) metadata.requested_owner = ownerResolution.requested_owner;
|
|
244
|
+
if (ownerResolution.executed_by) metadata.executed_by = normalizeOwnerSlug(ownerResolution.executed_by);
|
|
245
|
+
if (ownerResolution.proposed_member) metadata.proposed_member = ownerResolution.proposed_member;
|
|
183
246
|
const result = taskDb.addTask(db, {
|
|
184
247
|
title,
|
|
185
248
|
tag: 'agent-xp',
|
|
186
249
|
workspaceRoot,
|
|
187
250
|
sourceKey: `mission-xp:${mission.id}`,
|
|
188
251
|
status: 'claimed',
|
|
189
|
-
claimedBy:
|
|
252
|
+
claimedBy: owner,
|
|
190
253
|
metadata,
|
|
191
254
|
});
|
|
192
255
|
const rows = taskDb.withTaskDisplayRefs(taskDb.listTasks(db, { workspaceRoot }));
|
|
@@ -194,7 +257,7 @@ function createMissionXpTask(mission, root = process.cwd(), asJson = false) {
|
|
|
194
257
|
if (task) {
|
|
195
258
|
taskDb.noteTask(db, {
|
|
196
259
|
id: task.id,
|
|
197
|
-
actor: process.env.ATRIS_AGENT_ID ||
|
|
260
|
+
actor: process.env.ATRIS_AGENT_ID || owner,
|
|
198
261
|
content: `Mission goal loop XP bridge for ${mission.id}. Proof goes through task current-step; AgentXP lands only after human accept.`,
|
|
199
262
|
});
|
|
200
263
|
}
|
|
@@ -204,7 +267,9 @@ function createMissionXpTask(mission, root = process.cwd(), asJson = false) {
|
|
|
204
267
|
ref: missionTaskRef(task) || result.id,
|
|
205
268
|
title,
|
|
206
269
|
status: task?.status || 'claimed',
|
|
207
|
-
assigned_to:
|
|
270
|
+
assigned_to: owner,
|
|
271
|
+
owner_resolution: ownerResolution.reason,
|
|
272
|
+
executed_by: ownerResolution.executed_by ? normalizeOwnerSlug(ownerResolution.executed_by) : null,
|
|
208
273
|
inserted: result.inserted !== false,
|
|
209
274
|
projection_path: outPath,
|
|
210
275
|
};
|
|
@@ -223,6 +288,24 @@ function statePaths(root = process.cwd()) {
|
|
|
223
288
|
};
|
|
224
289
|
}
|
|
225
290
|
|
|
291
|
+
function readBusinessBinding(root = process.cwd()) {
|
|
292
|
+
const file = path.join(root, '.atris', 'business.json');
|
|
293
|
+
try {
|
|
294
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
295
|
+
return {
|
|
296
|
+
business_id: parsed.business_id || '',
|
|
297
|
+
workspace_id: parsed.workspace_id || '',
|
|
298
|
+
slug: parsed.slug || '',
|
|
299
|
+
};
|
|
300
|
+
} catch {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function businessIdForAtris2Mission(mission, cwd = process.cwd()) {
|
|
306
|
+
return mission?.business_id || readBusinessBinding(cwd)?.business_id || null;
|
|
307
|
+
}
|
|
308
|
+
|
|
226
309
|
function readJsonLines(file) {
|
|
227
310
|
if (!fs.existsSync(file)) return [];
|
|
228
311
|
return fs.readFileSync(file, 'utf8')
|
|
@@ -424,6 +507,7 @@ function renderMemberNowMarkdown(owner, missions) {
|
|
|
424
507
|
return lines.join('\n');
|
|
425
508
|
}
|
|
426
509
|
for (const mission of missions) {
|
|
510
|
+
const taskSpine = missionTaskSpine(mission);
|
|
427
511
|
lines.push(`## ${mission.objective}`);
|
|
428
512
|
lines.push('');
|
|
429
513
|
lines.push(`- id: ${mission.id}`);
|
|
@@ -431,6 +515,9 @@ function renderMemberNowMarkdown(owner, missions) {
|
|
|
431
515
|
lines.push(`- cadence: ${mission.cadence}`);
|
|
432
516
|
lines.push(`- runner: ${mission.runner}${mission.model ? ` (${mission.model})` : ''}`);
|
|
433
517
|
lines.push(`- lane: ${mission.lane}`);
|
|
518
|
+
if (taskSpine.task_ref) lines.push(`- task: ${taskSpine.task_ref}`);
|
|
519
|
+
if (taskSpine.current_step_command) lines.push(`- task next: ${taskSpine.current_step_command}`);
|
|
520
|
+
if (!taskSpine.has_task && taskSpine.ensure_task_command) lines.push(`- task setup: ${taskSpine.ensure_task_command}`);
|
|
434
521
|
if (mission.xp_task?.ref) lines.push(`- AgentXP task: ${mission.xp_task.ref}`);
|
|
435
522
|
if (mission.verifier) lines.push(`- verifier: ${mission.verifier}`);
|
|
436
523
|
if (mission.stop_condition) lines.push(`- stop: ${mission.stop_condition}`);
|
|
@@ -463,6 +550,52 @@ function completionGateLabel(gate) {
|
|
|
463
550
|
return gate.forced ? `forced override (${gate.source})` : gate.source;
|
|
464
551
|
}
|
|
465
552
|
|
|
553
|
+
function missionCompletionReceipt(mission, proof, xpNextCommand = null) {
|
|
554
|
+
const gate = mission.completion_gate || {};
|
|
555
|
+
const gateLabel = completionGateLabel(gate) || 'completion gate';
|
|
556
|
+
const checked = gate.source === 'receipt' && gate.receipt_path
|
|
557
|
+
? `I checked the passing verifier receipt ${gate.receipt_path}.`
|
|
558
|
+
: gate.source === 'mission_state'
|
|
559
|
+
? 'I checked mission state showing the verifier passed.'
|
|
560
|
+
: gate.source === 'no_verifier'
|
|
561
|
+
? 'No verifier was configured, so completion used the no-verifier gate.'
|
|
562
|
+
: `I checked the ${gateLabel} completion gate.`;
|
|
563
|
+
const tested = mission.verifier_result?.passed
|
|
564
|
+
? `Verifier passed: ${mission.verifier_result.command || mission.verifier || 'configured verifier'}.`
|
|
565
|
+
: mission.verifier
|
|
566
|
+
? `Completion proof is attached for verifier: ${mission.verifier}.`
|
|
567
|
+
: 'No verifier command was recorded for this mission.';
|
|
568
|
+
const landing = {
|
|
569
|
+
happened: `Mission completed: ${mission.objective}.`,
|
|
570
|
+
checked,
|
|
571
|
+
tested,
|
|
572
|
+
saved: `Saved complete mission ${mission.id}${proof ? ` with proof ${proof}` : ''}.`,
|
|
573
|
+
decision: xpNextCommand
|
|
574
|
+
? `Queue AgentXP next: ${xpNextCommand}`
|
|
575
|
+
: 'Mission is complete; start or resume the next mission if more work remains.',
|
|
576
|
+
};
|
|
577
|
+
const result = {
|
|
578
|
+
changed: landing.happened,
|
|
579
|
+
checked: landing.checked,
|
|
580
|
+
tested: landing.tested,
|
|
581
|
+
saved: landing.saved,
|
|
582
|
+
accept: landing.decision,
|
|
583
|
+
};
|
|
584
|
+
return { landing, result };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function missionResultLines(completion) {
|
|
588
|
+
const landing = completion?.landing || {};
|
|
589
|
+
const result = completion?.result || {};
|
|
590
|
+
const lines = ['Result:'];
|
|
591
|
+
if (landing.happened) lines.push(` What happened: ${landing.happened}`);
|
|
592
|
+
if (landing.checked) lines.push(` How I checked: ${landing.checked}`);
|
|
593
|
+
if (landing.tested) lines.push(` What I tested: ${landing.tested}`);
|
|
594
|
+
if (result.saved) lines.push(` Saved: ${result.saved}`);
|
|
595
|
+
if (landing.decision) lines.push(` Decision: ${landing.decision}`);
|
|
596
|
+
return lines;
|
|
597
|
+
}
|
|
598
|
+
|
|
466
599
|
function renderMissionStatus(root = process.cwd()) {
|
|
467
600
|
const paths = statePaths(root);
|
|
468
601
|
const missions = listMissions(root);
|
|
@@ -478,10 +611,14 @@ function renderMissionStatus(root = process.cwd()) {
|
|
|
478
611
|
lines.push('No missions yet.', '');
|
|
479
612
|
} else {
|
|
480
613
|
for (const mission of missions.slice(0, 12)) {
|
|
614
|
+
const taskSpine = missionTaskSpine(mission);
|
|
481
615
|
lines.push(`- **${mission.id}** ${mission.objective}`);
|
|
482
616
|
lines.push(` - owner: ${mission.owner}`);
|
|
483
617
|
lines.push(` - state: ${mission.status}`);
|
|
484
618
|
lines.push(` - next: ${mission.next_action || 'tick or verify'}`);
|
|
619
|
+
if (taskSpine?.task_ref) lines.push(` - task: ${taskSpine.task_ref}`);
|
|
620
|
+
if (taskSpine?.current_step_command) lines.push(` - task next: ${taskSpine.current_step_command}`);
|
|
621
|
+
if (taskSpine && !taskSpine.has_task && taskSpine.ensure_task_command) lines.push(` - task setup: ${taskSpine.ensure_task_command}`);
|
|
485
622
|
if (mission.xp_task?.ref) lines.push(` - AgentXP task: ${mission.xp_task.ref}`);
|
|
486
623
|
if (mission.receipt_path) lines.push(` - proof: ${mission.receipt_path}`);
|
|
487
624
|
const gateLabel = completionGateLabel(mission.completion_gate);
|
|
@@ -501,13 +638,110 @@ function missionXpTaskRefFromMission(mission) {
|
|
|
501
638
|
return '';
|
|
502
639
|
}
|
|
503
640
|
|
|
641
|
+
function resolveMissionOwner(mission, root = process.cwd()) {
|
|
642
|
+
const requestedOwner = mission?.owner || process.env.ATRIS_AGENT_ID || 'mission-lead';
|
|
643
|
+
const resolved = resolveFunctionalOwner({
|
|
644
|
+
requestedOwner,
|
|
645
|
+
title: mission?.objective || '',
|
|
646
|
+
tag: mission?.lane || 'mission',
|
|
647
|
+
note: mission?.next_action || '',
|
|
648
|
+
goal: mission?.objective || '',
|
|
649
|
+
root,
|
|
650
|
+
fallbackOwners: ['mission-lead', 'task-planner', 'architect', 'validator'],
|
|
651
|
+
});
|
|
652
|
+
const requested = mission?.requested_owner || (
|
|
653
|
+
resolved.requested_owner && resolved.requested_owner !== resolved.owner ? resolved.requested_owner : null
|
|
654
|
+
);
|
|
655
|
+
return {
|
|
656
|
+
...resolved,
|
|
657
|
+
reason: mission?.owner_resolution || resolved.reason,
|
|
658
|
+
requested_owner: requested,
|
|
659
|
+
executed_by: mission?.executed_by || resolved.executed_by || null,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function applyMissionOwnerResolution(mission, root = process.cwd()) {
|
|
664
|
+
const ownerResolution = resolveMissionOwner(mission, root);
|
|
665
|
+
const next = {
|
|
666
|
+
...mission,
|
|
667
|
+
owner: ownerResolution.owner,
|
|
668
|
+
owner_resolution: ownerResolution.reason,
|
|
669
|
+
};
|
|
670
|
+
if (ownerResolution.requested_owner && ownerResolution.requested_owner !== ownerResolution.owner) {
|
|
671
|
+
next.requested_owner = ownerResolution.requested_owner;
|
|
672
|
+
}
|
|
673
|
+
if (ownerResolution.executed_by) {
|
|
674
|
+
next.executed_by = normalizeOwnerSlug(ownerResolution.executed_by);
|
|
675
|
+
}
|
|
676
|
+
if (ownerResolution.proposed_member) {
|
|
677
|
+
next.proposed_member = ownerResolution.proposed_member;
|
|
678
|
+
}
|
|
679
|
+
return { mission: next, ownerResolution };
|
|
680
|
+
}
|
|
681
|
+
|
|
504
682
|
function missionXpReadyAction(mission, receiptPath) {
|
|
505
683
|
const ref = missionXpTaskRefFromMission(mission);
|
|
506
684
|
if (!ref || !receiptPath) return null;
|
|
507
|
-
const owner = mission.owner
|
|
685
|
+
const owner = resolveMissionOwner(mission).owner;
|
|
508
686
|
return `queue AgentXP review: atris task current-step --goal-id ${mission.id} --as ${owner} --proof "${receiptPath}" --json`;
|
|
509
687
|
}
|
|
510
688
|
|
|
689
|
+
function missionTaskSpine(mission) {
|
|
690
|
+
if (!mission || !mission.id) return null;
|
|
691
|
+
const ownerResolution = resolveMissionOwner(mission);
|
|
692
|
+
const taskIds = Array.isArray(mission.task_ids) ? mission.task_ids.filter(Boolean) : [];
|
|
693
|
+
const taskId = mission.xp_task?.task_id
|
|
694
|
+
|| mission.current_task_id
|
|
695
|
+
|| mission.task_id
|
|
696
|
+
|| taskIds[0]
|
|
697
|
+
|| null;
|
|
698
|
+
const taskRef = mission.xp_task?.ref
|
|
699
|
+
|| mission.task_ref
|
|
700
|
+
|| (taskId ? String(taskId) : null);
|
|
701
|
+
const owner = ownerResolution.owner;
|
|
702
|
+
return {
|
|
703
|
+
schema: 'atris.mission_task_spine.v1',
|
|
704
|
+
goal_id: mission.id,
|
|
705
|
+
owner,
|
|
706
|
+
requested_owner: ownerResolution.requested_owner || null,
|
|
707
|
+
owner_resolution: ownerResolution.reason,
|
|
708
|
+
executed_by: ownerResolution.executed_by ? normalizeOwnerSlug(ownerResolution.executed_by) : null,
|
|
709
|
+
lane: mission.lane || 'workspace',
|
|
710
|
+
runner: mission.runner || 'manual',
|
|
711
|
+
task_id: taskId,
|
|
712
|
+
current_task_id: taskId,
|
|
713
|
+
task_ref: taskRef,
|
|
714
|
+
has_task: Boolean(taskId || taskRef),
|
|
715
|
+
current_step_command: taskId || taskRef
|
|
716
|
+
? `atris task current-step --goal-id ${mission.id} --as ${owner} --proof "<proof>" --json`
|
|
717
|
+
: null,
|
|
718
|
+
ensure_task_command: taskId || taskRef
|
|
719
|
+
? null
|
|
720
|
+
: `atris mission attach-task ${mission.id} --json`,
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function missionStatusView(mission) {
|
|
725
|
+
const taskSpine = missionTaskSpine(mission);
|
|
726
|
+
if (!taskSpine) return mission;
|
|
727
|
+
const requestedOwner = taskSpine.requested_owner
|
|
728
|
+
|| mission.requested_owner
|
|
729
|
+
|| (mission.owner && mission.owner !== taskSpine.owner ? mission.owner : null);
|
|
730
|
+
return {
|
|
731
|
+
...mission,
|
|
732
|
+
owner: taskSpine.owner,
|
|
733
|
+
functional_owner: taskSpine.owner,
|
|
734
|
+
requested_owner: requestedOwner,
|
|
735
|
+
owner_resolution: taskSpine.owner_resolution,
|
|
736
|
+
executed_by: taskSpine.executed_by || mission.executed_by || null,
|
|
737
|
+
goal_id: taskSpine.goal_id,
|
|
738
|
+
task_id: taskSpine.task_id,
|
|
739
|
+
current_task_id: taskSpine.current_task_id,
|
|
740
|
+
task_ref: taskSpine.task_ref,
|
|
741
|
+
task_spine: taskSpine,
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
511
745
|
function missionFromArgs(args) {
|
|
512
746
|
const objective = stripKnownFlags(args, [
|
|
513
747
|
'--owner',
|
|
@@ -524,11 +758,20 @@ function missionFromArgs(args) {
|
|
|
524
758
|
if (!objective) {
|
|
525
759
|
exitMissionError('Usage: atris mission start "<objective>" --owner <member> [--verify "..."] [--cadence manual] [--worktree]', 1, wantsJson(args));
|
|
526
760
|
}
|
|
527
|
-
const
|
|
761
|
+
const requestedOwner = readFlag(args, '--owner', process.env.ATRIS_AGENT_ID || 'mission-lead');
|
|
528
762
|
const cadence = readFlag(args, '--cadence', readFlag(args, '--loop', 'manual')) || 'manual';
|
|
529
763
|
const runner = readFlag(args, '--runner', 'manual');
|
|
530
764
|
const model = readFlag(args, '--model', '') || (String(runner).toLowerCase() === 'atris2' ? 'atris:fast' : '');
|
|
531
765
|
const lane = readFlag(args, '--lane', 'workspace');
|
|
766
|
+
const ownerResolution = resolveFunctionalOwner({
|
|
767
|
+
requestedOwner,
|
|
768
|
+
title: objective,
|
|
769
|
+
tag: lane,
|
|
770
|
+
goal: objective,
|
|
771
|
+
root: process.cwd(),
|
|
772
|
+
fallbackOwners: ['mission-lead', 'task-planner', 'architect', 'validator'],
|
|
773
|
+
});
|
|
774
|
+
const owner = ownerResolution.owner;
|
|
532
775
|
const verifier = readFlag(args, '--verify', '');
|
|
533
776
|
assertMissionVerifier(verifier, wantsJson(args));
|
|
534
777
|
const stopCondition = readFlag(args, '--stop', verifier ? 'verifier passes and no human asks remain' : 'human marks complete with proof');
|
|
@@ -536,6 +779,7 @@ function missionFromArgs(args) {
|
|
|
536
779
|
const humanAsks = readRepeatedFlag(args, '--ask');
|
|
537
780
|
const alwaysOn = hasFlag(args, '--always-on');
|
|
538
781
|
const xpTaskEnabled = hasFlag(args, '--xp-task') || hasFlag(args, '--agent-xp');
|
|
782
|
+
const businessBinding = readBusinessBinding(process.cwd());
|
|
539
783
|
const id = missionId(objective);
|
|
540
784
|
const mission = {
|
|
541
785
|
schema: 'atris.mission.v1',
|
|
@@ -543,10 +787,16 @@ function missionFromArgs(args) {
|
|
|
543
787
|
slug: slugify(objective),
|
|
544
788
|
objective,
|
|
545
789
|
owner,
|
|
790
|
+
owner_resolution: ownerResolution.reason,
|
|
791
|
+
...(ownerResolution.requested_owner && ownerResolution.requested_owner !== owner ? { requested_owner: ownerResolution.requested_owner } : {}),
|
|
792
|
+
...(ownerResolution.executed_by ? { executed_by: normalizeOwnerSlug(ownerResolution.executed_by) } : {}),
|
|
793
|
+
...(ownerResolution.proposed_member ? { proposed_member: ownerResolution.proposed_member } : {}),
|
|
546
794
|
status: 'planning',
|
|
547
795
|
cadence,
|
|
548
796
|
runner,
|
|
549
797
|
...(model ? { model } : {}),
|
|
798
|
+
...(businessBinding?.business_id ? { business_id: businessBinding.business_id } : {}),
|
|
799
|
+
...(businessBinding?.workspace_id ? { workspace_id: businessBinding.workspace_id } : {}),
|
|
550
800
|
lane,
|
|
551
801
|
verifier,
|
|
552
802
|
always_on: alwaysOn,
|
|
@@ -571,6 +821,36 @@ function missingVerifierWarning(mission) {
|
|
|
571
821
|
};
|
|
572
822
|
}
|
|
573
823
|
|
|
824
|
+
function missionRunSmokeVerifier() {
|
|
825
|
+
const script = [
|
|
826
|
+
"const fs=require('fs')",
|
|
827
|
+
"const os=require('os')",
|
|
828
|
+
"const path=require('path')",
|
|
829
|
+
"const {spawnSync}=require('child_process')",
|
|
830
|
+
"const root=fs.mkdtempSync(path.join(os.tmpdir(),'atris-mission-run-verifier-'))",
|
|
831
|
+
"process.on('exit',()=>fs.rmSync(root,{recursive:true,force:true}))",
|
|
832
|
+
"fs.mkdirSync(path.join(root,'atris'),{recursive:true})",
|
|
833
|
+
"const r=spawnSync('atris',['mission','run','verifier smoke objective','--json'],{cwd:root,encoding:'utf8',env:{...process.env,ATRIS_SKIP_UPDATE_CHECK:'1'}})",
|
|
834
|
+
"if(r.status!==0){process.stderr.write(r.stderr||r.stdout);process.exit(1)}",
|
|
835
|
+
"const p=JSON.parse(r.stdout)",
|
|
836
|
+
"if(p.action!=='mission_run_started')process.exit(2)",
|
|
837
|
+
"if(p.mission?.runner!=='codex_goal')process.exit(3)",
|
|
838
|
+
"if(p.codex_goal_state?.goal?.visible_goal?.schema!=='atris.visible_chat_goal_bridge.v1')process.exit(4)",
|
|
839
|
+
].join(';');
|
|
840
|
+
return `node -e ${JSON.stringify(script)}`;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function inferRunObjectiveVerifier(objective, root = process.cwd()) {
|
|
844
|
+
const text = String(objective || '').toLowerCase();
|
|
845
|
+
if (!/\bmission\s+run\b/.test(text)) return '';
|
|
846
|
+
const sourceTest = path.join(root, 'test', 'mission-status.test.js');
|
|
847
|
+
const sourceCommand = path.join(root, 'commands', 'mission.js');
|
|
848
|
+
if (fs.existsSync(sourceTest) && fs.existsSync(sourceCommand)) {
|
|
849
|
+
return 'node --test test/mission-status.test.js';
|
|
850
|
+
}
|
|
851
|
+
return missionRunSmokeVerifier();
|
|
852
|
+
}
|
|
853
|
+
|
|
574
854
|
function startMission(args) {
|
|
575
855
|
const asJson = wantsJson(args);
|
|
576
856
|
const mission = missionFromArgs(args);
|
|
@@ -624,6 +904,137 @@ function startMission(args) {
|
|
|
624
904
|
);
|
|
625
905
|
}
|
|
626
906
|
|
|
907
|
+
function startMissionFromRunObjective(objective, args) {
|
|
908
|
+
const asJson = wantsJson(args);
|
|
909
|
+
const verifier = readFlag(args, '--verify', inferRunObjectiveVerifier(objective));
|
|
910
|
+
const stopCondition = readFlag(
|
|
911
|
+
args,
|
|
912
|
+
'--stop',
|
|
913
|
+
verifier ? 'verifier passes and visible goal lands' : 'visible goal lands and proof is ready',
|
|
914
|
+
);
|
|
915
|
+
const startArgs = [
|
|
916
|
+
objective,
|
|
917
|
+
'--owner',
|
|
918
|
+
readFlag(args, '--owner', process.env.ATRIS_AGENT_ID || 'mission-lead'),
|
|
919
|
+
'--runner',
|
|
920
|
+
readFlag(args, '--runner', 'codex_goal'),
|
|
921
|
+
'--lane',
|
|
922
|
+
readFlag(args, '--lane', 'workspace'),
|
|
923
|
+
'--cadence',
|
|
924
|
+
readFlag(args, '--cadence', 'manual'),
|
|
925
|
+
'--stop',
|
|
926
|
+
stopCondition,
|
|
927
|
+
];
|
|
928
|
+
if (verifier) startArgs.push('--verify', verifier);
|
|
929
|
+
const model = readFlag(args, '--model', '');
|
|
930
|
+
if (model) startArgs.push('--model', model);
|
|
931
|
+
if (hasFlag(args, '--always-on')) startArgs.push('--always-on');
|
|
932
|
+
if (hasFlag(args, '--xp-task') || hasFlag(args, '--agent-xp')) startArgs.push('--xp-task');
|
|
933
|
+
|
|
934
|
+
const mission = missionFromArgs(startArgs);
|
|
935
|
+
const warnings = [missingVerifierWarning(mission)].filter(Boolean);
|
|
936
|
+
ensureMemberMissionFile(mission.owner, process.cwd(), mission.objective);
|
|
937
|
+
const { mission: saved } = saveMission(mission, process.cwd(), 'mission_started', { objective: mission.objective, source: 'mission_run_objective' });
|
|
938
|
+
const memberState = renderMemberMissionState(saved.owner);
|
|
939
|
+
const logPath = appendMemberLog(saved.owner, 'Mission started from run', {
|
|
940
|
+
mission: saved.objective,
|
|
941
|
+
cadence: saved.cadence,
|
|
942
|
+
runner: saved.runner,
|
|
943
|
+
lane: saved.lane,
|
|
944
|
+
verifier: saved.verifier,
|
|
945
|
+
});
|
|
946
|
+
const worktreeBaseline = captureMissionWorktreeBaseline(saved, process.cwd());
|
|
947
|
+
const codexGoalState = refreshCodexGoalController(process.cwd());
|
|
948
|
+
printJsonOrText(
|
|
949
|
+
{
|
|
950
|
+
ok: true,
|
|
951
|
+
action: 'mission_run_started',
|
|
952
|
+
mission: saved,
|
|
953
|
+
warnings,
|
|
954
|
+
state_path: statePaths().missionsJsonl,
|
|
955
|
+
member_state: memberState,
|
|
956
|
+
log_path: logPath,
|
|
957
|
+
worktree_baseline: worktreeBaseline ? {
|
|
958
|
+
path: path.relative(process.cwd(), missionBaselinePath(saved.id)),
|
|
959
|
+
dirty_count: worktreeBaseline.dirty_count,
|
|
960
|
+
dirty_hash: worktreeBaseline.dirty_hash,
|
|
961
|
+
} : null,
|
|
962
|
+
codex_goal_state: codexGoalState,
|
|
963
|
+
next_command: codexGoalState.goal?.next_command || `atris mission tick ${saved.id} --verify`,
|
|
964
|
+
},
|
|
965
|
+
[
|
|
966
|
+
`Started mission: ${saved.objective}`,
|
|
967
|
+
`Owner: ${saved.owner}`,
|
|
968
|
+
`Runner: ${saved.runner}`,
|
|
969
|
+
...(codexGoalState.goal ? [`Visible goal: ${codexGoalState.goal.objective}`] : []),
|
|
970
|
+
...warnings.map((warning) => `Warning: ${warning.message}`),
|
|
971
|
+
],
|
|
972
|
+
asJson,
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function attachMissionTask(args) {
|
|
977
|
+
const asJson = wantsJson(args);
|
|
978
|
+
const ref = stripKnownFlags(args, [], ['--json'])[0] || '';
|
|
979
|
+
if (!ref) {
|
|
980
|
+
exitMissionError('Usage: atris mission attach-task <id> [--json]', 1, asJson);
|
|
981
|
+
}
|
|
982
|
+
const mission = resolveMission(ref);
|
|
983
|
+
if (!mission) {
|
|
984
|
+
exitMissionError(`Mission "${ref}" not found.`, 1, asJson);
|
|
985
|
+
}
|
|
986
|
+
if (TERMINAL_STATUSES.has(mission.status)) {
|
|
987
|
+
exitMissionError(`Mission "${ref}" is ${mission.status}; task spines attach only to active missions.`, 2, asJson);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
const existingSpine = missionTaskSpine(mission);
|
|
991
|
+
if (existingSpine?.has_task) {
|
|
992
|
+
const view = missionStatusView(mission);
|
|
993
|
+
printJsonOrText(
|
|
994
|
+
{ ok: true, action: 'mission_task_spine_exists', mission: view, task_spine: view.task_spine },
|
|
995
|
+
[
|
|
996
|
+
`Mission task spine already exists: ${mission.objective}`,
|
|
997
|
+
`Task: ${view.task_spine.task_ref}`,
|
|
998
|
+
`Next: ${view.task_spine.current_step_command}`,
|
|
999
|
+
],
|
|
1000
|
+
asJson,
|
|
1001
|
+
);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
const ownership = applyMissionOwnerResolution(mission, process.cwd());
|
|
1006
|
+
const baseMission = ownership.mission;
|
|
1007
|
+
const xpTask = createMissionXpTask(baseMission, process.cwd(), asJson);
|
|
1008
|
+
const nextMission = {
|
|
1009
|
+
...baseMission,
|
|
1010
|
+
xp_task_enabled: true,
|
|
1011
|
+
xp_task: xpTask,
|
|
1012
|
+
task_ids: Array.from(new Set([...(baseMission.task_ids || []), xpTask.task_id])),
|
|
1013
|
+
};
|
|
1014
|
+
if (nextMission.status === 'ready' && nextMission.receipt_path) {
|
|
1015
|
+
nextMission.next_action = missionXpReadyAction(nextMission, nextMission.receipt_path) || nextMission.next_action;
|
|
1016
|
+
} else if (!nextMission.verifier && !nextMission.always_on) {
|
|
1017
|
+
nextMission.next_action = `work task then run: atris task current-step --goal-id ${nextMission.id} --as ${nextMission.owner} --proof "<proof>" --json`;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const { mission: saved } = saveMission(nextMission, process.cwd(), 'mission_task_spine_attached', { task_id: xpTask.task_id, task_ref: xpTask.ref });
|
|
1021
|
+
const memberState = renderMemberMissionState(saved.owner);
|
|
1022
|
+
const logPath = appendMemberLog(saved.owner, 'Mission task spine attached', {
|
|
1023
|
+
mission: saved.objective,
|
|
1024
|
+
task: xpTask.ref,
|
|
1025
|
+
});
|
|
1026
|
+
const view = missionStatusView(saved);
|
|
1027
|
+
printJsonOrText(
|
|
1028
|
+
{ ok: true, action: 'mission_task_spine_attached', mission: view, task: xpTask, task_spine: view.task_spine, member_state: memberState, log_path: logPath },
|
|
1029
|
+
[
|
|
1030
|
+
`Attached task spine: ${saved.objective}`,
|
|
1031
|
+
`Task: ${xpTask.ref}`,
|
|
1032
|
+
`Next: ${view.task_spine.current_step_command}`,
|
|
1033
|
+
],
|
|
1034
|
+
asJson,
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
627
1038
|
function statusMission(args) {
|
|
628
1039
|
const asJson = wantsJson(args);
|
|
629
1040
|
const localOnly = hasFlag(args, '--local');
|
|
@@ -648,14 +1059,15 @@ function statusMission(args) {
|
|
|
648
1059
|
if (ref && !missions.length) {
|
|
649
1060
|
exitMissionError(`Mission "${ref}" not found.`, 1, asJson);
|
|
650
1061
|
}
|
|
1062
|
+
const missionViews = missions.map(missionStatusView);
|
|
651
1063
|
// Member state renders are cwd-local writes; rolled-up missions stay read-only.
|
|
652
|
-
for (const owner of new Set(
|
|
1064
|
+
for (const owner of new Set(missionViews.filter((mission) => !mission.worktree_root).map((mission) => mission.owner).filter(Boolean))) {
|
|
653
1065
|
renderMemberMissionState(owner);
|
|
654
1066
|
}
|
|
655
1067
|
const payload = {
|
|
656
1068
|
ok: true,
|
|
657
1069
|
action: 'mission_status',
|
|
658
|
-
missions,
|
|
1070
|
+
missions: missionViews,
|
|
659
1071
|
state_path: statePaths().missionsJsonl,
|
|
660
1072
|
events_path: statePaths().eventsJsonl,
|
|
661
1073
|
status_path: renderMissionStatus(),
|
|
@@ -663,14 +1075,18 @@ function statusMission(args) {
|
|
|
663
1075
|
printJsonOrText(
|
|
664
1076
|
payload,
|
|
665
1077
|
missions.length
|
|
666
|
-
?
|
|
1078
|
+
? missionViews.flatMap((mission) => [
|
|
667
1079
|
`Mission: ${mission.objective}`,
|
|
668
1080
|
` id: ${mission.id}`,
|
|
669
1081
|
` owner: ${mission.owner}`,
|
|
1082
|
+
...(mission.executed_by ? [` executed_by: ${mission.executed_by}`] : []),
|
|
670
1083
|
` state: ${mission.status}`,
|
|
671
1084
|
...missionHeartbeatLines(mission),
|
|
672
1085
|
...(mission.worktree_root ? [` worktree: ${mission.worktree_root}`] : []),
|
|
673
1086
|
` next: ${mission.next_action || 'tick or verify'}`,
|
|
1087
|
+
...(mission.task_spine?.task_ref ? [` task: ${mission.task_spine.task_ref}`] : []),
|
|
1088
|
+
...(mission.task_spine?.current_step_command ? [` task next: ${mission.task_spine.current_step_command}`] : []),
|
|
1089
|
+
...(!mission.task_spine?.has_task && mission.task_spine?.ensure_task_command ? [` task setup: ${mission.task_spine.ensure_task_command}`] : []),
|
|
674
1090
|
...(mission.receipt_path ? [` proof: ${mission.receipt_path}`] : []),
|
|
675
1091
|
...(completionGateLabel(mission.completion_gate) ? [` gate: ${completionGateLabel(mission.completion_gate)}`] : []),
|
|
676
1092
|
])
|
|
@@ -679,6 +1095,108 @@ function statusMission(args) {
|
|
|
679
1095
|
);
|
|
680
1096
|
}
|
|
681
1097
|
|
|
1098
|
+
function readMissionReceipt(receiptPath, root = process.cwd()) {
|
|
1099
|
+
if (!receiptPath) return null;
|
|
1100
|
+
const file = path.isAbsolute(receiptPath) ? receiptPath : path.join(root, receiptPath);
|
|
1101
|
+
try {
|
|
1102
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
1103
|
+
} catch {
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function firstUsefulLine(text, fallback = '') {
|
|
1109
|
+
return String(text || '')
|
|
1110
|
+
.split(/\r?\n/)
|
|
1111
|
+
.map((line) => line.replace(/^[-*\s#]+/, '').trim())
|
|
1112
|
+
.filter(Boolean)
|
|
1113
|
+
.find((line) => !/^(receipt|summary|final|result)$/i.test(line))
|
|
1114
|
+
|| fallback;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function missionWorkerLabel(mission) {
|
|
1118
|
+
const runner = String(mission && mission.runner || 'manual').toLowerCase();
|
|
1119
|
+
if (runner === 'atris2') return `Remote Atris2 computer${mission.model ? ` using ${mission.model}` : ''}`;
|
|
1120
|
+
if (runner === 'claude') return `Claude worker${mission.model ? ` using ${mission.model}` : ''}`;
|
|
1121
|
+
if (runner === 'codex_goal') return 'Codex goal handoff';
|
|
1122
|
+
return 'Local mission tick';
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
function missionWorkerSummary(mission, receipt) {
|
|
1126
|
+
if (mission && mission.worker_summary) return mission.worker_summary;
|
|
1127
|
+
const tick = receipt && receipt.result && (receipt.result.tick || (Array.isArray(receipt.result.ticks) ? receipt.result.ticks[receipt.result.ticks.length - 1] : null));
|
|
1128
|
+
if (!tick) return mission && mission.last_tick_reason ? `Last tick: ${mission.last_tick_reason}` : 'No worker receipt yet.';
|
|
1129
|
+
if (tick.atris2) {
|
|
1130
|
+
return firstUsefulLine(tick.atris2.receipt_text, tick.atris2.ok ? 'Remote worker ran and returned a response.' : 'Remote worker failed.');
|
|
1131
|
+
}
|
|
1132
|
+
if (tick.claude) {
|
|
1133
|
+
if (tick.claude.skipped) {
|
|
1134
|
+
return tick.summary || `Worker step skipped: ${tick.claude.reason || tick.reason || 'not needed'}.`;
|
|
1135
|
+
}
|
|
1136
|
+
return tick.claude.summary || firstUsefulLine(tick.claude.receipt_text, tick.claude.ok ? 'Worker ran and returned a response.' : 'Worker failed.');
|
|
1137
|
+
}
|
|
1138
|
+
if (tick.summary) return tick.summary;
|
|
1139
|
+
return tick.reason ? `Worker tick: ${tick.reason}` : 'Worker tick recorded.';
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function missionReportFor(mission, root = process.cwd()) {
|
|
1143
|
+
const verifierReceiptPath = mission.receipt_path || null;
|
|
1144
|
+
const receipt = readMissionReceipt(verifierReceiptPath, root);
|
|
1145
|
+
const workerReceiptPath = mission.worker_receipt_path || (receipt && verifierReceiptPath) || null;
|
|
1146
|
+
const verifierPassed = mission.verifier_result && mission.verifier_result.passed === true;
|
|
1147
|
+
const operatorOutcome = mission.operator_outcome
|
|
1148
|
+
|| (verifierPassed ? 'Verifier passed.' : mission.status === 'complete' ? 'Mission is complete.' : mission.status === 'blocked' ? 'Mission is blocked.' : 'Mission is still in progress.');
|
|
1149
|
+
return {
|
|
1150
|
+
id: mission.id,
|
|
1151
|
+
objective: mission.objective,
|
|
1152
|
+
status: mission.status,
|
|
1153
|
+
operator_outcome: operatorOutcome,
|
|
1154
|
+
worker: mission.worker || missionWorkerLabel(mission),
|
|
1155
|
+
worker_summary: missionWorkerSummary(mission, receipt),
|
|
1156
|
+
worker_receipt_path: workerReceiptPath,
|
|
1157
|
+
verifier_receipt_path: verifierReceiptPath,
|
|
1158
|
+
operator_next: mission.operator_next || mission.next_action || 'Review the mission state.',
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function reportMission(args) {
|
|
1163
|
+
const asJson = wantsJson(args);
|
|
1164
|
+
const localOnly = hasFlag(args, '--local');
|
|
1165
|
+
const ref = stripKnownFlags(args, ['--limit'], ['--json', '--local'])[0] || '';
|
|
1166
|
+
const limit = readPositiveIntegerFlag(args, '--limit', ref ? 1 : 3, { json: asJson });
|
|
1167
|
+
let missions = ref ? [resolveMission(ref)].filter(Boolean) : listMissions();
|
|
1168
|
+
if (!ref && !localOnly) {
|
|
1169
|
+
const seen = new Set(missions.map((mission) => mission.id));
|
|
1170
|
+
for (const rolled of listWorktreeRollupMissions()) {
|
|
1171
|
+
if (seen.has(rolled.id)) continue;
|
|
1172
|
+
seen.add(rolled.id);
|
|
1173
|
+
missions.push(rolled);
|
|
1174
|
+
}
|
|
1175
|
+
missions.sort((a, b) => String(b.updated_at || b.created_at || '').localeCompare(String(a.updated_at || a.created_at || '')));
|
|
1176
|
+
}
|
|
1177
|
+
if (ref && !missions.length) {
|
|
1178
|
+
exitMissionError(`Mission "${ref}" not found.`, 1, asJson);
|
|
1179
|
+
}
|
|
1180
|
+
missions = missions.slice(0, limit);
|
|
1181
|
+
const reports = missions.map((mission) => missionReportFor(mission, mission.worktree_root || process.cwd()));
|
|
1182
|
+
printJsonOrText(
|
|
1183
|
+
{ ok: true, action: 'mission_report', reports },
|
|
1184
|
+
reports.length
|
|
1185
|
+
? reports.flatMap((report) => [
|
|
1186
|
+
`Mission: ${report.objective}`,
|
|
1187
|
+
` state: ${report.status}`,
|
|
1188
|
+
` What happened: ${report.operator_outcome}`,
|
|
1189
|
+
` Worker: ${report.worker}`,
|
|
1190
|
+
` Worker summary: ${report.worker_summary}`,
|
|
1191
|
+
...(report.worker_receipt_path ? [` Worker receipt: ${report.worker_receipt_path}`] : []),
|
|
1192
|
+
...(report.verifier_receipt_path ? [` Verifier receipt: ${report.verifier_receipt_path}`] : []),
|
|
1193
|
+
` Next: ${report.operator_next}`,
|
|
1194
|
+
])
|
|
1195
|
+
: ['No missions yet. Run: atris mission start "..." --owner <member>'],
|
|
1196
|
+
asJson,
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
682
1200
|
// `atris mission watch [id]` — read-only live heartbeat. Prints a line per tick as it
|
|
683
1201
|
// lands so a human (or any terminal) can see the loop is alive without rerunning status.
|
|
684
1202
|
function watchMission(args) {
|
|
@@ -1125,10 +1643,14 @@ function selectCodexGoalMission(root = process.cwd(), now = new Date()) {
|
|
|
1125
1643
|
}
|
|
1126
1644
|
|
|
1127
1645
|
function codexGoalObjective(mission) {
|
|
1128
|
-
return
|
|
1646
|
+
return mission.objective;
|
|
1129
1647
|
}
|
|
1130
1648
|
|
|
1131
1649
|
function codexGoalNextCommand(mission) {
|
|
1650
|
+
const taskSpine = missionTaskSpine(mission);
|
|
1651
|
+
if (taskSpine && !taskSpine.has_task && taskSpine.ensure_task_command) {
|
|
1652
|
+
return taskSpine.ensure_task_command;
|
|
1653
|
+
}
|
|
1132
1654
|
if (mission.status === 'ready') {
|
|
1133
1655
|
const xpAction = missionXpReadyAction(mission, mission.receipt_path);
|
|
1134
1656
|
if (xpAction) return xpAction.replace(/^queue AgentXP review: /, '');
|
|
@@ -1143,14 +1665,40 @@ function codexGoalNextCommand(mission) {
|
|
|
1143
1665
|
return `atris mission tick ${mission.id} --summary "<what changed>"`;
|
|
1144
1666
|
}
|
|
1145
1667
|
|
|
1668
|
+
function codexVisibleGoalBridge(mission, goalObjective) {
|
|
1669
|
+
return {
|
|
1670
|
+
schema: 'atris.visible_chat_goal_bridge.v1',
|
|
1671
|
+
runtime: 'codex',
|
|
1672
|
+
source: 'atris_mission',
|
|
1673
|
+
mission_id: mission.id,
|
|
1674
|
+
desired_objective: goalObjective,
|
|
1675
|
+
status: 'needs_runtime_write',
|
|
1676
|
+
state_file: '.atris/state/codex_goal.json',
|
|
1677
|
+
status_file: 'atris/status/codex-goal.md',
|
|
1678
|
+
operations: {
|
|
1679
|
+
read_current_goal: 'get_goal',
|
|
1680
|
+
keep_if_matching: 'if current goal objective equals goal.objective, continue the mission',
|
|
1681
|
+
create_when_empty_or_completed: 'create_goal({ objective: goal.objective })',
|
|
1682
|
+
complete_after_proof: 'update_goal({ status: "complete" })',
|
|
1683
|
+
refresh_next_candidate: 'atris mission goal --json',
|
|
1684
|
+
},
|
|
1685
|
+
guardrails: [
|
|
1686
|
+
'Do not complete a human-set active goal unless it matches this mission goal or the mission receipt proves handoff.',
|
|
1687
|
+
'If create_goal fails because another goal is active, keep this bridge waiting for the visible goal slot.',
|
|
1688
|
+
],
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1146
1692
|
function codexGoalToolContract(mission) {
|
|
1147
1693
|
return {
|
|
1148
1694
|
current_policy: 'keep one visible Codex /goal active for the selected Atris mission',
|
|
1149
1695
|
read_current_goal: 'get_goal',
|
|
1150
1696
|
complete_current_goal: 'update_goal({ status: "complete" })',
|
|
1151
1697
|
select_next_goal: 'atris mission goal --json',
|
|
1152
|
-
set_next_goal: '
|
|
1153
|
-
|
|
1698
|
+
set_next_goal: 'use goal.visible_goal: create_goal({ objective: goal.objective }) when no active goal blocks the slot',
|
|
1699
|
+
visible_goal_bridge: 'goal.visible_goal',
|
|
1700
|
+
platform_requirement: 'Codex runtime must expose replace_goal/set_goal, or allow update_goal({ status: "complete" }) followed by create_goal({ objective }).',
|
|
1701
|
+
runtime_tool_sequence: 'get_goal -> update_goal({ status: "complete" }) after proof -> atris mission goal --json -> create_goal({ objective: goal.objective })',
|
|
1154
1702
|
blocked_without_platform_goal_write: true,
|
|
1155
1703
|
mission_id: mission.id,
|
|
1156
1704
|
};
|
|
@@ -1191,6 +1739,12 @@ function writeCodexGoalState(payload, root = process.cwd()) {
|
|
|
1191
1739
|
lines.push(`- reason: ${state.goal.reason}`);
|
|
1192
1740
|
lines.push(`- objective: ${state.goal.objective}`);
|
|
1193
1741
|
lines.push(`- next: ${state.goal.next_command}`);
|
|
1742
|
+
if (state.goal.visible_goal) {
|
|
1743
|
+
lines.push(`- visible goal: ${state.goal.visible_goal.status}`);
|
|
1744
|
+
lines.push(`- visible goal desired: ${state.goal.visible_goal.desired_objective}`);
|
|
1745
|
+
lines.push(`- visible goal create: ${state.goal.visible_goal.operations.create_when_empty_or_completed}`);
|
|
1746
|
+
lines.push(`- visible goal complete: ${state.goal.visible_goal.operations.complete_after_proof}`);
|
|
1747
|
+
}
|
|
1194
1748
|
lines.push(`- platform write blocked: ${state.goal.codex_tool_contract.blocked_without_platform_goal_write}`);
|
|
1195
1749
|
} else {
|
|
1196
1750
|
lines.push('- mission: none');
|
|
@@ -1219,14 +1773,21 @@ function buildCodexGoalPayload(root = process.cwd(), options = {}) {
|
|
|
1219
1773
|
}
|
|
1220
1774
|
|
|
1221
1775
|
const { mission, reason } = selected;
|
|
1776
|
+
const taskSpine = missionTaskSpine(mission);
|
|
1777
|
+
const missionView = missionStatusView(mission);
|
|
1778
|
+
const objective = codexGoalObjective(mission);
|
|
1222
1779
|
const goal = {
|
|
1223
|
-
objective
|
|
1780
|
+
objective,
|
|
1224
1781
|
mission_id: mission.id,
|
|
1225
1782
|
mission_objective: mission.objective,
|
|
1226
1783
|
mission_status: mission.status,
|
|
1784
|
+
owner: taskSpine?.owner || mission.owner,
|
|
1785
|
+
executed_by: taskSpine?.executed_by || mission.executed_by || null,
|
|
1786
|
+
task_spine: taskSpine,
|
|
1227
1787
|
reason,
|
|
1228
1788
|
next_command: codexGoalNextCommand(mission),
|
|
1229
1789
|
replace_after: 'After proof or verifier pass, run atris mission goal --json again and replace the Codex /goal with the returned objective.',
|
|
1790
|
+
visible_goal: codexVisibleGoalBridge(mission, objective),
|
|
1230
1791
|
codex_tool_contract: codexGoalToolContract(mission),
|
|
1231
1792
|
};
|
|
1232
1793
|
const heartbeat = heartbeatMode ? codexGoalHeartbeat(goal, mission) : undefined;
|
|
@@ -1234,7 +1795,7 @@ function buildCodexGoalPayload(root = process.cwd(), options = {}) {
|
|
|
1234
1795
|
ok: true,
|
|
1235
1796
|
action: heartbeatMode ? 'codex_goal_heartbeat' : 'codex_goal_candidate',
|
|
1236
1797
|
goal,
|
|
1237
|
-
mission,
|
|
1798
|
+
mission: missionView,
|
|
1238
1799
|
heartbeat,
|
|
1239
1800
|
};
|
|
1240
1801
|
}
|
|
@@ -1249,10 +1810,8 @@ function refreshCodexGoalController(root = process.cwd(), options = {}) {
|
|
|
1249
1810
|
};
|
|
1250
1811
|
}
|
|
1251
1812
|
|
|
1252
|
-
function
|
|
1813
|
+
function runAtrisMissionJsonCommand(root, args, options = {}) {
|
|
1253
1814
|
const cliPath = path.join(__dirname, '..', 'bin', 'atris.js');
|
|
1254
|
-
const args = ['mission', 'run', '--due', '--max-ticks', '1', '--complete-on-pass', '--json'];
|
|
1255
|
-
if (options.noClaude) args.push('--no-claude');
|
|
1256
1815
|
const result = spawnSync(process.execPath, [cliPath, ...args], {
|
|
1257
1816
|
cwd: root,
|
|
1258
1817
|
encoding: 'utf8',
|
|
@@ -1264,15 +1823,79 @@ function runMissionRunDueOnce(root = process.cwd(), options = {}) {
|
|
|
1264
1823
|
payload = JSON.parse(result.stdout || '{}');
|
|
1265
1824
|
} catch {}
|
|
1266
1825
|
return {
|
|
1826
|
+
action: options.action || null,
|
|
1267
1827
|
command: `atris ${args.join(' ')}`,
|
|
1268
1828
|
status: result.status,
|
|
1269
1829
|
ok: result.status === 0,
|
|
1830
|
+
heavy_work: options.heavyWork === true,
|
|
1831
|
+
setup_work: options.setupWork === true,
|
|
1270
1832
|
stdout: String(result.stdout || '').slice(-4000),
|
|
1271
1833
|
stderr: String(result.stderr || '').slice(-4000),
|
|
1272
1834
|
payload,
|
|
1273
1835
|
};
|
|
1274
1836
|
}
|
|
1275
1837
|
|
|
1838
|
+
function runMissionRunDueOnce(root = process.cwd(), options = {}) {
|
|
1839
|
+
const args = ['mission', 'run', '--due', '--max-ticks', '1', '--complete-on-pass', '--json'];
|
|
1840
|
+
if (options.noClaude) args.push('--no-claude');
|
|
1841
|
+
return runAtrisMissionJsonCommand(root, args, {
|
|
1842
|
+
action: 'mission_run_due',
|
|
1843
|
+
heavyWork: true,
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function goalLoopNextCommandPlan(goal) {
|
|
1848
|
+
const command = String(goal?.next_command || '').trim();
|
|
1849
|
+
const attach = command.match(/^atris\s+mission\s+(attach-task|ensure-task|task-spine)\s+([A-Za-z0-9_.:-]+)\s+--json$/);
|
|
1850
|
+
if (attach) {
|
|
1851
|
+
return {
|
|
1852
|
+
action: 'mission_attach_task',
|
|
1853
|
+
command,
|
|
1854
|
+
args: ['mission', 'attach-task', attach[2], '--json'],
|
|
1855
|
+
heavy_work: false,
|
|
1856
|
+
setup_work: true,
|
|
1857
|
+
run_when_due_only: false,
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
const dueRun = command.match(/^atris\s+mission\s+run\s+--due\s+--max-ticks\s+([0-9]+)(\s+--complete-on-pass)?(?:\s+--json)?$/);
|
|
1862
|
+
if (dueRun) {
|
|
1863
|
+
const args = ['mission', 'run', '--due', '--max-ticks', dueRun[1]];
|
|
1864
|
+
if (dueRun[2]) args.push('--complete-on-pass');
|
|
1865
|
+
args.push('--json');
|
|
1866
|
+
return {
|
|
1867
|
+
action: 'mission_run_due',
|
|
1868
|
+
command: `atris ${args.join(' ')}`,
|
|
1869
|
+
args,
|
|
1870
|
+
heavy_work: true,
|
|
1871
|
+
setup_work: false,
|
|
1872
|
+
run_when_due_only: true,
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
return null;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
function shouldRunGoalLoopCommand(heartbeat, plan) {
|
|
1880
|
+
if (!heartbeat?.goal) return false;
|
|
1881
|
+
if (plan && plan.run_when_due_only === false) return true;
|
|
1882
|
+
return heartbeat.heartbeat?.due === true;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
function runMissionGoalNextCommand(root = process.cwd(), heartbeat, options = {}) {
|
|
1886
|
+
const plan = goalLoopNextCommandPlan(heartbeat?.goal);
|
|
1887
|
+
if (plan) {
|
|
1888
|
+
const args = [...plan.args];
|
|
1889
|
+
if (options.noClaude && plan.action === 'mission_run_due') args.push('--no-claude');
|
|
1890
|
+
return runAtrisMissionJsonCommand(root, args, {
|
|
1891
|
+
action: plan.action,
|
|
1892
|
+
heavyWork: plan.heavy_work,
|
|
1893
|
+
setupWork: plan.setup_work,
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
return runMissionRunDueOnce(root, options);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1276
1899
|
function sleep(ms, signal) {
|
|
1277
1900
|
return new Promise((resolve, reject) => {
|
|
1278
1901
|
if (ms <= 0) return resolve();
|
|
@@ -1600,7 +2223,20 @@ async function runMission(args) {
|
|
|
1600
2223
|
const maxTicks = Math.max(1, Number(maxTicksFlag) || MISSION_RUN_DEFAULTS.maxTicks);
|
|
1601
2224
|
const maxWallSeconds = Math.max(60, Number(readFlag(args, '--max-wall', '')) || MISSION_RUN_DEFAULTS.maxWallSeconds);
|
|
1602
2225
|
const cadenceOverride = readFlag(args, '--cadence', '');
|
|
1603
|
-
const ref = stripKnownFlags(
|
|
2226
|
+
const ref = stripKnownFlags(
|
|
2227
|
+
args,
|
|
2228
|
+
['--max-ticks', '--max-wall', '--cadence', '--owner', '--runner', '--lane', '--verify', '--stop', '--model'],
|
|
2229
|
+
['--json', '--due', '--no-claude', '--no-verify', '--complete-on-pass', '--no-drain', '--always-on', '--xp-task', '--agent-xp'],
|
|
2230
|
+
).join(' ').trim();
|
|
2231
|
+
|
|
2232
|
+
if (!dueMode && !ref) {
|
|
2233
|
+
if (asJson || !process.stdin.isTTY || !process.stderr.isTTY) {
|
|
2234
|
+
missionRunInputRequired(asJson);
|
|
2235
|
+
}
|
|
2236
|
+
const prompted = await promptMissionRunInput(args);
|
|
2237
|
+
startMissionFromRunObjective(prompted.objective, prompted.args);
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
1604
2240
|
|
|
1605
2241
|
let mission = dueMode && !ref ? selectDueMission() : resolveMission(ref);
|
|
1606
2242
|
if (!mission && dueMode && !ref) {
|
|
@@ -1611,8 +2247,12 @@ async function runMission(args) {
|
|
|
1611
2247
|
);
|
|
1612
2248
|
return;
|
|
1613
2249
|
}
|
|
2250
|
+
if (!mission && ref && /\s/.test(ref) && !String(ref).startsWith('mission-')) {
|
|
2251
|
+
startMissionFromRunObjective(ref, args);
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
1614
2254
|
if (!mission) {
|
|
1615
|
-
exitMissionError(ref ? `Mission "${ref}" not found.` : 'Usage: atris mission run <id> [--max-ticks 4] [--max-wall 3600]', 1, asJson);
|
|
2255
|
+
exitMissionError(ref ? `Mission "${ref}" not found.` : 'Usage: atris mission run <id|objective> [--max-ticks 4] [--max-wall 3600]', 1, asJson);
|
|
1616
2256
|
}
|
|
1617
2257
|
if (['complete', 'stopped'].includes(mission.status)) {
|
|
1618
2258
|
if (asJson) {
|
|
@@ -1752,9 +2392,11 @@ async function runMission(args) {
|
|
|
1752
2392
|
} else if (atris2Runner) {
|
|
1753
2393
|
const prompt = buildTickPrompt(mission, tickIdx, effectiveMaxTicks, frozen);
|
|
1754
2394
|
const { runAtris2Turn } = require('./probe');
|
|
2395
|
+
const businessId = businessIdForAtris2Mission(mission, cwd);
|
|
1755
2396
|
const turn = await runAtris2Turn({
|
|
1756
2397
|
prompt,
|
|
1757
2398
|
model: mission.model || 'atris:fast',
|
|
2399
|
+
business: businessId,
|
|
1758
2400
|
maxTurns: 16,
|
|
1759
2401
|
signal: controller.signal,
|
|
1760
2402
|
});
|
|
@@ -2201,22 +2843,43 @@ function completeMission(args) {
|
|
|
2201
2843
|
exitMissionError(`[mission complete] ${gate.reason}. Run: atris mission tick ${mission.id} --verify (or override as operator with --force)`, 2, asJson);
|
|
2202
2844
|
}
|
|
2203
2845
|
const baselineSummary = pruneMissionWorktreeBaseline(mission, process.cwd());
|
|
2204
|
-
const
|
|
2846
|
+
const completionGate = { ...gate, forced: force && !gate.ok };
|
|
2847
|
+
const baseNext = {
|
|
2205
2848
|
...mission,
|
|
2206
2849
|
status: 'complete',
|
|
2207
2850
|
completed_at: stampIso(),
|
|
2208
2851
|
proof,
|
|
2209
|
-
completion_gate:
|
|
2852
|
+
completion_gate: completionGate,
|
|
2210
2853
|
worktree_baseline: baselineSummary || mission.worktree_baseline || null,
|
|
2211
2854
|
next_action: 'mission complete',
|
|
2212
2855
|
};
|
|
2856
|
+
const xpNextCommand = missionXpReadyAction(baseNext, proof);
|
|
2857
|
+
const completion = missionCompletionReceipt(baseNext, proof, xpNextCommand);
|
|
2858
|
+
const next = {
|
|
2859
|
+
...baseNext,
|
|
2860
|
+
landing: completion.landing,
|
|
2861
|
+
result: completion.result,
|
|
2862
|
+
};
|
|
2213
2863
|
const { mission: saved } = saveMission(next, process.cwd(), 'mission_completed', { proof, completion_gate: next.completion_gate });
|
|
2214
2864
|
const logPath = appendMemberLog(saved.owner, 'Mission completed', { mission: saved.objective, proof });
|
|
2215
2865
|
const codexGoalState = refreshCodexGoalController(process.cwd());
|
|
2216
|
-
const xpNextCommand = missionXpReadyAction(saved, proof);
|
|
2217
2866
|
printJsonOrText(
|
|
2218
|
-
{
|
|
2219
|
-
|
|
2867
|
+
{
|
|
2868
|
+
ok: true,
|
|
2869
|
+
action: 'mission_completed',
|
|
2870
|
+
mission: saved,
|
|
2871
|
+
landing: completion.landing,
|
|
2872
|
+
result: completion.result,
|
|
2873
|
+
log_path: logPath,
|
|
2874
|
+
codex_goal_state: codexGoalState,
|
|
2875
|
+
xp_next_command: xpNextCommand,
|
|
2876
|
+
},
|
|
2877
|
+
[
|
|
2878
|
+
`Completed mission: ${saved.objective}`,
|
|
2879
|
+
...missionResultLines(completion),
|
|
2880
|
+
`Proof: ${proof}`,
|
|
2881
|
+
...(xpNextCommand ? [`AgentXP: ${xpNextCommand}`] : []),
|
|
2882
|
+
],
|
|
2220
2883
|
asJson,
|
|
2221
2884
|
);
|
|
2222
2885
|
}
|
|
@@ -2311,16 +2974,27 @@ async function goalLoopMission(args) {
|
|
|
2311
2974
|
iteration: index + 1,
|
|
2312
2975
|
heartbeat,
|
|
2313
2976
|
ran_heavy_work: false,
|
|
2977
|
+
ran_setup_work: false,
|
|
2314
2978
|
dry_run: dryRun,
|
|
2315
2979
|
};
|
|
2316
2980
|
|
|
2317
|
-
|
|
2981
|
+
const commandPlan = goalLoopNextCommandPlan(heartbeat.goal);
|
|
2982
|
+
if (shouldRunGoalLoopCommand(heartbeat, commandPlan)) {
|
|
2318
2983
|
if (dryRun) {
|
|
2319
2984
|
event.ran_heavy_work = false;
|
|
2320
|
-
event.
|
|
2985
|
+
event.ran_setup_work = false;
|
|
2986
|
+
event.run = {
|
|
2987
|
+
skipped: true,
|
|
2988
|
+
reason: 'dry-run',
|
|
2989
|
+
action: commandPlan?.action || 'mission_run_due',
|
|
2990
|
+
command: commandPlan?.command || 'atris mission run --due --max-ticks 1 --complete-on-pass --json',
|
|
2991
|
+
heavy_work: commandPlan?.heavy_work === true,
|
|
2992
|
+
setup_work: commandPlan?.setup_work === true,
|
|
2993
|
+
};
|
|
2321
2994
|
} else {
|
|
2322
|
-
event.run =
|
|
2323
|
-
event.ran_heavy_work = event.run.ok === true;
|
|
2995
|
+
event.run = runMissionGoalNextCommand(root, heartbeat, { noClaude });
|
|
2996
|
+
event.ran_heavy_work = event.run.ok === true && event.run.heavy_work === true;
|
|
2997
|
+
event.ran_setup_work = event.run.ok === true && event.run.setup_work === true;
|
|
2324
2998
|
event.after_run = refreshCodexGoalController(root, { heartbeat: true });
|
|
2325
2999
|
}
|
|
2326
3000
|
}
|
|
@@ -2347,6 +3021,7 @@ async function goalLoopMission(args) {
|
|
|
2347
3021
|
max_iterations: maxIterations,
|
|
2348
3022
|
max_wall_seconds: maxWallSeconds,
|
|
2349
3023
|
heavy_runs: events.filter((event) => event.ran_heavy_work).length,
|
|
3024
|
+
setup_runs: events.filter((event) => event.ran_setup_work).length,
|
|
2350
3025
|
events,
|
|
2351
3026
|
final_state: finalState,
|
|
2352
3027
|
};
|
|
@@ -2373,15 +3048,17 @@ atris mission - durable goal + loop + owner + proof state
|
|
|
2373
3048
|
default model atris:fast; runner codex_goal publishes the goal for a live
|
|
2374
3049
|
Codex session to pull via atris mission goal)
|
|
2375
3050
|
atris mission status [id] [--status <state>] [--limit <n>] [--local] [--json]
|
|
3051
|
+
atris mission attach-task <id> [--json] Create the missing task spine for an existing active mission
|
|
3052
|
+
atris mission report [id] [--limit <n>] [--local] [--json] Plain outcome, worker receipt, verifier receipt, and next move
|
|
2376
3053
|
atris mission watch [id] [--interval <s>] [--idle-every <s>] Live heartbeat: prints a line per tick as it lands
|
|
2377
3054
|
atris mission layers [--mission <id-substr>] [--since <date>] [--json] Per-layer growth curve across tick receipts
|
|
2378
3055
|
(rolls up sibling git-worktree missions; --local scopes to this checkout)
|
|
2379
3056
|
atris mission goal [--heartbeat] [--json]
|
|
2380
3057
|
atris mission goal-loop [--max-wall 28800] [--max-iterations 32] [--no-claude] [--json]
|
|
2381
3058
|
atris mission tick <id> [--verify] [--complete-on-pass] [--summary "..."] [--json]
|
|
2382
|
-
atris mission run
|
|
3059
|
+
atris mission run ["objective"|id|--due] [--owner <member>] [--max-ticks 4] [--max-wall 3600] [--cadence "15m"]
|
|
2383
3060
|
[--no-claude] [--no-verify] [--complete-on-pass] [--no-drain] [--json]
|
|
2384
|
-
(
|
|
3061
|
+
(bare run prompts for mission + owner; --due runs the saved queue)
|
|
2385
3062
|
atris mission complete <id> --proof "..."
|
|
2386
3063
|
atris mission stop <id> [--pause] [--reason "..."]
|
|
2387
3064
|
|
|
@@ -2389,7 +3066,7 @@ Autonomy recipe:
|
|
|
2389
3066
|
1. Pick an owner member: atris member create <member> (if missing)
|
|
2390
3067
|
2. Start a current-agent mission with a verifier:
|
|
2391
3068
|
atris mission start "ship one proof" --owner <member> --runner codex_goal --lane code --verify "npm test" --stop "verifier passes" --xp-task
|
|
2392
|
-
3. Codex sessions: atris mission goal --json, then
|
|
3069
|
+
3. Codex sessions: atris mission goal --json, then mirror goal.visible_goal into the native chat goal
|
|
2393
3070
|
Overnight controller: atris mission goal --heartbeat --json
|
|
2394
3071
|
Bounded overnight runner: atris mission goal-loop --max-wall 28800 --no-claude --json
|
|
2395
3072
|
4. Do one bounded step, then record it:
|
|
@@ -2582,6 +3259,13 @@ function missionCommand(args) {
|
|
|
2582
3259
|
case 'list':
|
|
2583
3260
|
case 'ls':
|
|
2584
3261
|
return statusMission(rest);
|
|
3262
|
+
case 'attach-task':
|
|
3263
|
+
case 'ensure-task':
|
|
3264
|
+
case 'task-spine':
|
|
3265
|
+
return attachMissionTask(rest);
|
|
3266
|
+
case 'report':
|
|
3267
|
+
case 'debrief':
|
|
3268
|
+
return reportMission(rest);
|
|
2585
3269
|
case 'watch':
|
|
2586
3270
|
return watchMission(rest);
|
|
2587
3271
|
case 'layers':
|
|
@@ -2625,6 +3309,7 @@ module.exports = {
|
|
|
2625
3309
|
classifyPathsByLayer,
|
|
2626
3310
|
resolveClaudeRunnerModel,
|
|
2627
3311
|
resolveClaudeRunnerBin,
|
|
3312
|
+
businessIdForAtris2Mission,
|
|
2628
3313
|
detectUnavailableModel,
|
|
2629
3314
|
missionPauseNextAction,
|
|
2630
3315
|
consecutiveSameReasonErrors,
|