atris 3.30.2 → 3.30.4

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.
Files changed (2) hide show
  1. package/commands/mission.js +177 -11
  2. package/package.json +1 -1
@@ -3,6 +3,7 @@
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,
@@ -141,6 +142,58 @@ function printJsonOrText(payload, lines, asJson) {
141
142
  for (const line of lines) console.log(line);
142
143
  }
143
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
+
144
197
  function loadTaskDb(asJson = false) {
145
198
  try {
146
199
  return require('../lib/task-db');
@@ -798,6 +851,94 @@ function inferRunObjectiveVerifier(objective, root = process.cwd()) {
798
851
  return missionRunSmokeVerifier();
799
852
  }
800
853
 
854
+ function markMissionRunContinuation(mission) {
855
+ return {
856
+ ...mission,
857
+ started_from: 'mission_run_objective',
858
+ continue_on_complete: true,
859
+ continuation_policy: 'decide_and_start_next_useful_mission',
860
+ };
861
+ }
862
+
863
+ function continuationObjective(parent) {
864
+ return `Decide and start the next useful mission after: ${parent.objective}`;
865
+ }
866
+
867
+ function findActiveContinuationMission(parent, root = process.cwd()) {
868
+ return listMissions(root).find((mission) => (
869
+ mission.parent_mission_id === parent.id
870
+ && mission.started_from === 'mission_run_continuation'
871
+ && !TERMINAL_STATUSES.has(mission.status)
872
+ )) || null;
873
+ }
874
+
875
+ function seedMissionRunContinuation(parent, root = process.cwd(), proof = '') {
876
+ if (!parent || parent.status !== 'complete') return null;
877
+ if (parent.continue_on_complete !== true) return null;
878
+ if (parent.continuation_seeded_mission_id) return {
879
+ inserted: false,
880
+ reason: 'already_seeded',
881
+ mission_id: parent.continuation_seeded_mission_id,
882
+ };
883
+
884
+ const existing = findActiveContinuationMission(parent, root);
885
+ if (existing) return { inserted: false, reason: 'active_continuation_exists', mission: existing };
886
+
887
+ const owner = parent.owner || process.env.ATRIS_AGENT_ID || 'mission-lead';
888
+ const objective = continuationObjective(parent);
889
+ const mission = missionFromArgs([
890
+ objective,
891
+ '--owner',
892
+ owner,
893
+ '--runner',
894
+ 'codex_goal',
895
+ '--lane',
896
+ parent.lane || 'workspace',
897
+ '--cadence',
898
+ 'manual',
899
+ '--stop',
900
+ 'next useful mission is started, or no useful next mission remains',
901
+ ]);
902
+ const nextMission = {
903
+ ...mission,
904
+ started_from: 'mission_run_continuation',
905
+ parent_mission_id: parent.id,
906
+ parent_objective: parent.objective,
907
+ continue_on_complete: false,
908
+ continuation_policy: 'choose_next_mission',
909
+ parent_proof: proof || parent.receipt_path || null,
910
+ next_action: `decide next mission, then run: atris mission run "<next useful mission>" --owner ${owner}`,
911
+ };
912
+
913
+ ensureMemberMissionFile(nextMission.owner, root, nextMission.objective);
914
+ const { mission: saved } = saveMission(nextMission, root, 'mission_continuation_started', {
915
+ parent_mission_id: parent.id,
916
+ parent_objective: parent.objective,
917
+ proof: proof || null,
918
+ });
919
+ const worktreeBaseline = captureMissionWorktreeBaseline(saved, root);
920
+ const seededAt = stampIso();
921
+ const { mission: updatedParent } = saveMission({
922
+ ...parent,
923
+ continuation_seeded_mission_id: saved.id,
924
+ continuation_seeded_at: seededAt,
925
+ continuation_seeded_objective: saved.objective,
926
+ }, root, 'mission_continuation_seeded', {
927
+ continuation_mission_id: saved.id,
928
+ continuation_objective: saved.objective,
929
+ });
930
+ return {
931
+ inserted: true,
932
+ mission: saved,
933
+ parent: updatedParent,
934
+ worktree_baseline: worktreeBaseline ? {
935
+ path: path.relative(root, missionBaselinePath(saved.id, root)),
936
+ dirty_count: worktreeBaseline.dirty_count,
937
+ dirty_hash: worktreeBaseline.dirty_hash,
938
+ } : null,
939
+ };
940
+ }
941
+
801
942
  function startMission(args) {
802
943
  const asJson = wantsJson(args);
803
944
  const mission = missionFromArgs(args);
@@ -878,7 +1019,7 @@ function startMissionFromRunObjective(objective, args) {
878
1019
  if (hasFlag(args, '--always-on')) startArgs.push('--always-on');
879
1020
  if (hasFlag(args, '--xp-task') || hasFlag(args, '--agent-xp')) startArgs.push('--xp-task');
880
1021
 
881
- const mission = missionFromArgs(startArgs);
1022
+ const mission = markMissionRunContinuation(missionFromArgs(startArgs));
882
1023
  const warnings = [missingVerifierWarning(mission)].filter(Boolean);
883
1024
  ensureMemberMissionFile(mission.owner, process.cwd(), mission.objective);
884
1025
  const { mission: saved } = saveMission(mission, process.cwd(), 'mission_started', { objective: mission.objective, source: 'mission_run_objective' });
@@ -2176,6 +2317,15 @@ async function runMission(args) {
2176
2317
  ['--json', '--due', '--no-claude', '--no-verify', '--complete-on-pass', '--no-drain', '--always-on', '--xp-task', '--agent-xp'],
2177
2318
  ).join(' ').trim();
2178
2319
 
2320
+ if (!dueMode && !ref) {
2321
+ if (asJson || !process.stdin.isTTY || !process.stderr.isTTY) {
2322
+ missionRunInputRequired(asJson);
2323
+ }
2324
+ const prompted = await promptMissionRunInput(args);
2325
+ startMissionFromRunObjective(prompted.objective, prompted.args);
2326
+ return;
2327
+ }
2328
+
2179
2329
  let mission = dueMode && !ref ? selectDueMission() : resolveMission(ref);
2180
2330
  if (!mission && dueMode && !ref) {
2181
2331
  printJsonOrText(
@@ -2283,6 +2433,7 @@ async function runMission(args) {
2283
2433
  const startedAt = Date.now();
2284
2434
  let backoffAttempt = 0;
2285
2435
  let lastRateLimit = null;
2436
+ let continuationGoal = null;
2286
2437
 
2287
2438
  const sessionLabel = skipWorker
2288
2439
  ? 'caller-session'
@@ -2495,6 +2646,10 @@ async function runMission(args) {
2495
2646
  verifier: verifierResult ? (verifierResult.passed ? 'passed' : 'failed') : 'not_run',
2496
2647
  receipt: receiptPath,
2497
2648
  });
2649
+ if (newStatus === 'complete') {
2650
+ continuationGoal = seedMissionRunContinuation(mission, cwd, receiptPath);
2651
+ if (continuationGoal?.parent) mission = continuationGoal.parent;
2652
+ }
2498
2653
  refreshCodexGoalController(cwd);
2499
2654
 
2500
2655
  console.error(`[tick ${tickIdx}] status=${result.status} reason=${result.reason} verifier=${verifierResult ? (verifierResult.passed ? 'pass' : 'fail') : 'skip'} -> ${receiptPath || '-'}`);
@@ -2572,7 +2727,7 @@ async function runMission(args) {
2572
2727
  const codexGoalState = refreshCodexGoalController(cwd);
2573
2728
 
2574
2729
  printJsonOrText(
2575
- { 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 },
2730
+ { 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 },
2576
2731
  [
2577
2732
  `Ran mission ${mission.id}`,
2578
2733
  ` objective: ${mission.objective}`,
@@ -2581,6 +2736,7 @@ async function runMission(args) {
2581
2736
  pauseReason ? ` pause: ${pauseReason}` : null,
2582
2737
  ` session: ${sessionId || '(none)'}`,
2583
2738
  ` summary receipt: ${finalReceipt}`,
2739
+ continuationGoal?.mission ? ` next goal: ${continuationGoal.mission.objective}` : null,
2584
2740
  ].filter(Boolean),
2585
2741
  asJson,
2586
2742
  );
@@ -2709,15 +2865,20 @@ function tickMission(args) {
2709
2865
  receipt: receiptPath,
2710
2866
  summary: summary || undefined,
2711
2867
  });
2868
+ const continuationGoal = saved.status === 'complete'
2869
+ ? seedMissionRunContinuation(saved, cwd, receiptPath)
2870
+ : null;
2871
+ const outputMission = continuationGoal?.parent || saved;
2712
2872
  const codexGoalState = refreshCodexGoalController(process.cwd());
2713
2873
  printJsonOrText(
2714
- { ok: true, action: 'mission_tick', mission: saved, tick: tickRecord, verifier_result: verifierResult, receipt_path: receiptPath, log_path: logPath, codex_goal_state: codexGoalState },
2874
+ { 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 },
2715
2875
  [
2716
- `Ticked mission: ${saved.objective}`,
2717
- `State: ${saved.status}`,
2876
+ `Ticked mission: ${outputMission.objective}`,
2877
+ `State: ${outputMission.status}`,
2718
2878
  `Tick: ${tickIdx}`,
2719
- `Next: ${saved.next_action}`,
2879
+ `Next: ${outputMission.next_action}`,
2720
2880
  ...(receiptPath ? [`Receipt: ${receiptPath}`] : []),
2881
+ ...(continuationGoal?.mission ? [`Next goal: ${continuationGoal.mission.objective}`] : []),
2721
2882
  ],
2722
2883
  asJson,
2723
2884
  );
@@ -2799,24 +2960,28 @@ function completeMission(args) {
2799
2960
  result: completion.result,
2800
2961
  };
2801
2962
  const { mission: saved } = saveMission(next, process.cwd(), 'mission_completed', { proof, completion_gate: next.completion_gate });
2802
- const logPath = appendMemberLog(saved.owner, 'Mission completed', { mission: saved.objective, proof });
2963
+ const continuationGoal = seedMissionRunContinuation(saved, process.cwd(), proof);
2964
+ const outputMission = continuationGoal?.parent || saved;
2965
+ const logPath = appendMemberLog(outputMission.owner, 'Mission completed', { mission: outputMission.objective, proof });
2803
2966
  const codexGoalState = refreshCodexGoalController(process.cwd());
2804
2967
  printJsonOrText(
2805
2968
  {
2806
2969
  ok: true,
2807
2970
  action: 'mission_completed',
2808
- mission: saved,
2971
+ mission: outputMission,
2809
2972
  landing: completion.landing,
2810
2973
  result: completion.result,
2811
2974
  log_path: logPath,
2812
2975
  codex_goal_state: codexGoalState,
2813
2976
  xp_next_command: xpNextCommand,
2977
+ continuation_goal: continuationGoal,
2814
2978
  },
2815
2979
  [
2816
- `Completed mission: ${saved.objective}`,
2980
+ `Completed mission: ${outputMission.objective}`,
2817
2981
  ...missionResultLines(completion),
2818
2982
  `Proof: ${proof}`,
2819
2983
  ...(xpNextCommand ? [`AgentXP: ${xpNextCommand}`] : []),
2984
+ ...(continuationGoal?.mission ? [`Next goal: ${continuationGoal.mission.objective}`] : []),
2820
2985
  ],
2821
2986
  asJson,
2822
2987
  );
@@ -2994,9 +3159,10 @@ atris mission - durable goal + loop + owner + proof state
2994
3159
  atris mission goal [--heartbeat] [--json]
2995
3160
  atris mission goal-loop [--max-wall 28800] [--max-iterations 32] [--no-claude] [--json]
2996
3161
  atris mission tick <id> [--verify] [--complete-on-pass] [--summary "..."] [--json]
2997
- atris mission run <id|--due> [--max-ticks 4] [--max-wall 3600] [--cadence "15m"]
3162
+ atris mission run ["objective"|id|--due] [--owner <member>] [--max-ticks 4] [--max-wall 3600] [--cadence "15m"]
2998
3163
  [--no-claude] [--no-verify] [--complete-on-pass] [--no-drain] [--json]
2999
- (always-on runs drain the review lane each tick; --no-drain skips)
3164
+ (bare run prompts for mission + owner; --due runs the saved queue)
3165
+ (mission-run completions seed the next visible goal: decide and start the next useful mission)
3000
3166
  atris mission complete <id> --proof "..."
3001
3167
  atris mission stop <id> [--pause] [--reason "..."]
3002
3168
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.30.2",
3
+ "version": "3.30.4",
4
4
  "main": "bin/atris.js",
5
5
  "bin": {
6
6
  "atris": "bin/atris.js",