atris 3.30.7 → 3.30.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/atris.js CHANGED
@@ -65,6 +65,7 @@ const {
65
65
  apiRequestJson, streamProChat, spawnClaudeCodeSession,
66
66
  DEFAULT_CLIENT_ID, DEFAULT_USER_AGENT,
67
67
  } = require('../utils/api');
68
+ const missionRuntime = require('../lib/mission-runtime-loop');
68
69
 
69
70
  // Bind DI wrappers (utils/auth uses dependency injection for apiRequestJson)
70
71
  const validateAccessToken = (token) => _validateAccessToken(token, apiRequestJson);
@@ -200,10 +201,14 @@ function loadActiveMissions(workspaceDir) {
200
201
  owner: m.owner || '?',
201
202
  objective: m.objective || '',
202
203
  status,
204
+ created_at: m.created_at || null,
205
+ updated_at: m.updated_at || null,
206
+ completed_at: m.completed_at || null,
203
207
  verifier: m.verifier || null,
204
208
  verifier_passed: (m.verifier_result && m.verifier_result.passed) === true,
205
209
  next_action: m.next_action || '',
206
210
  lane: m.lane || null,
211
+ runner: m.runner || null,
207
212
  });
208
213
  }
209
214
  // Most recently started first (rough — relies on insertion order from reversed walk)
@@ -213,6 +218,93 @@ function loadActiveMissions(workspaceDir) {
213
218
  }
214
219
  }
215
220
 
221
+ function readAtrisGoalState(workspaceDir) {
222
+ try {
223
+ const file = path.join(workspaceDir, '.atris', 'state', 'atris_goal.json');
224
+ if (!fs.existsSync(file)) return null;
225
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
226
+ return parsed?.goal || null;
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
231
+
232
+ function currentAtrisGoal(workspaceDir) {
233
+ const activeMissions = loadActiveMissions(workspaceDir);
234
+ const stored = readAtrisGoalState(workspaceDir);
235
+ if (stored && stored.objective) {
236
+ const mission = activeMissions.find((item) => item.id === stored.mission_id) || null;
237
+ return {
238
+ ...stored,
239
+ mission_status: mission?.status || stored.mission_status,
240
+ created_at: stored.created_at || mission?.created_at || null,
241
+ updated_at: stored.updated_at || mission?.updated_at || null,
242
+ completed_at: stored.completed_at || mission?.completed_at || null,
243
+ next_command: stored.next_command || mission?.next_action || null,
244
+ };
245
+ }
246
+ const mission = activeMissions[0] || null;
247
+ if (!mission) return null;
248
+ return {
249
+ objective: mission.objective,
250
+ mission_id: mission.id,
251
+ mission_status: mission.status,
252
+ owner: mission.owner,
253
+ runner: mission.runner || 'mission',
254
+ created_at: mission.created_at,
255
+ updated_at: mission.updated_at,
256
+ completed_at: mission.completed_at,
257
+ next_command: mission.next_action || `atris mission tick ${mission.id} --summary "<what changed>"`,
258
+ };
259
+ }
260
+
261
+ function goalElapsedSeconds(goal) {
262
+ const started = Date.parse(goal?.created_at || '');
263
+ if (!Number.isFinite(started)) return null;
264
+ const ended = Date.parse(goal?.completed_at || '') || Date.now();
265
+ return Math.max(0, Math.floor((ended - started) / 1000));
266
+ }
267
+
268
+ function formatGoalDuration(seconds) {
269
+ if (!Number.isFinite(seconds)) return null;
270
+ const total = Math.max(0, Math.floor(seconds));
271
+ const hours = Math.floor(total / 3600);
272
+ const minutes = Math.floor((total % 3600) / 60);
273
+ const secs = total % 60;
274
+ if (hours > 0) return `${hours}h ${minutes}m`;
275
+ if (minutes > 0) return `${minutes}m ${secs}s`;
276
+ return `${secs}s`;
277
+ }
278
+
279
+ function goalAchieved(goal) {
280
+ return ['complete', 'completed', 'achieved'].includes(String(goal?.mission_status || goal?.status || '').toLowerCase());
281
+ }
282
+
283
+ function printAtrisGoalBanner(workspaceDir = process.cwd(), label = 'Atris goal') {
284
+ if (process.env.ATRIS_SHOW_GOAL_BANNER !== '1' && process.env.AX_SHOW_GOAL_BANNER !== '1') return null;
285
+ const goal = currentAtrisGoal(workspaceDir);
286
+ if (!goal) return null;
287
+ if (String(goal.runner || '').trim().toLowerCase() === 'codex_goal') return null;
288
+ const objective = String(goal.objective || '').length > 92
289
+ ? `${String(goal.objective).slice(0, 89)}...`
290
+ : String(goal.objective || '');
291
+ console.log(`${label}: ${objective}`);
292
+ if (goal.mission_id || goal.mission_status || goal.runner) {
293
+ const elapsed = formatGoalDuration(goalElapsedSeconds(goal));
294
+ const parts = [
295
+ goal.mission_id || '?',
296
+ goal.mission_status || '?',
297
+ goal.runner || 'mission',
298
+ ];
299
+ if (elapsed) parts.push(`elapsed ${elapsed}`);
300
+ parts.push(`achieved ${goalAchieved(goal) ? 'yes' : 'no'}`);
301
+ console.log(`Mission: ${parts.join(' · ')}`);
302
+ }
303
+ if (goal.next_command) console.log(`Next: ${goal.next_command}`);
304
+ console.log('');
305
+ return goal;
306
+ }
307
+
216
308
  function showSearchHelp() {
217
309
  console.log('Usage: atris search <keyword>');
218
310
  console.log('Example: atris search auth');
@@ -1313,7 +1405,7 @@ if (command === 'init') {
1313
1405
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1314
1406
  } else if (command === 'mission') {
1315
1407
  Promise.resolve(require('../commands/mission').missionCommand(process.argv.slice(3)))
1316
- .then(() => process.exit(0))
1408
+ .then(() => process.exit(process.exitCode || 0))
1317
1409
  .catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
1318
1410
  } else if (command === 'pulse') {
1319
1411
  // Pulse: durable overnight self-improvement heartbeat (OS cron) for atris-cli.
@@ -2359,6 +2451,8 @@ async function agentAtris() {
2359
2451
  process.exit(1);
2360
2452
  }
2361
2453
 
2454
+ printAtrisGoalBanner(process.cwd());
2455
+
2362
2456
  // Check if logged in (with token refresh)
2363
2457
  const ensured = await ensureValidCredentials();
2364
2458
  if (ensured.error === 'not_logged_in' || !ensured.credentials?.token) {
@@ -2461,6 +2555,13 @@ async function chatAtris() {
2461
2555
  process.exit(1);
2462
2556
  }
2463
2557
 
2558
+ const missionIntent = missionRunIntentFromFastMessage(message);
2559
+ if (missionIntent) {
2560
+ process.exit(await runLocalFastMission(missionIntent));
2561
+ }
2562
+
2563
+ printAtrisGoalBanner(process.cwd());
2564
+
2464
2565
  // Check agent selected
2465
2566
  const config = loadConfig();
2466
2567
  if (!config.agent_id) {
@@ -2550,6 +2651,15 @@ async function chatInteractive(config, credentials) {
2550
2651
  return;
2551
2652
  }
2552
2653
 
2654
+ const missionIntent = missionRunIntentFromFastMessage(input);
2655
+ if (missionIntent) {
2656
+ console.log('');
2657
+ await runLocalFastMission(missionIntent);
2658
+ console.log('');
2659
+ rl.prompt();
2660
+ return;
2661
+ }
2662
+
2553
2663
  // Send to pro-chat
2554
2664
  console.log('');
2555
2665
  const apiUrl = getApiBaseUrl().replace(/\/api$/, '');
@@ -2598,6 +2708,78 @@ function atrisFastMessageFromArgs() {
2598
2708
  return process.argv.slice(offset).join(' ').trim();
2599
2709
  }
2600
2710
 
2711
+ function stripFastMissionQuotes(value) {
2712
+ const text = String(value || '').trim();
2713
+ if (text.length >= 2) {
2714
+ const first = text[0];
2715
+ const last = text[text.length - 1];
2716
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
2717
+ return text.slice(1, -1).trim();
2718
+ }
2719
+ }
2720
+ return text;
2721
+ }
2722
+
2723
+ function extractFastMissionFlag(text, flagName) {
2724
+ const source = String(text || '');
2725
+ const inline = new RegExp(`(^|\\s)${flagName}=([^\\s]+)(?=\\s|$)`).exec(source);
2726
+ if (inline) {
2727
+ return {
2728
+ value: inline[2],
2729
+ text: `${source.slice(0, inline.index)}${inline[1] || ''}${source.slice(inline.index + inline[0].length)}`.replace(/\s+/g, ' ').trim(),
2730
+ };
2731
+ }
2732
+ const split = new RegExp(`(^|\\s)${flagName}\\s+([^\\s]+)(?=\\s|$)`).exec(source);
2733
+ if (split) {
2734
+ return {
2735
+ value: split[2],
2736
+ text: `${source.slice(0, split.index)}${split[1] || ''}${source.slice(split.index + split[0].length)}`.replace(/\s+/g, ' ').trim(),
2737
+ };
2738
+ }
2739
+ return { value: null, text: source.trim() };
2740
+ }
2741
+
2742
+ function missionRunIntentFromFastMessage(message) {
2743
+ return missionRuntime.missionRunIntentFromMessage(message);
2744
+ }
2745
+
2746
+ function printFastMissionStartReceipt(payload) {
2747
+ const mission = payload?.mission || {};
2748
+ const goal = payload?.atris_goal_state?.goal || {};
2749
+ const receiptGoal = {
2750
+ ...goal,
2751
+ objective: goal.objective || mission.objective,
2752
+ mission_id: goal.mission_id || mission.id,
2753
+ mission_status: goal.mission_status || mission.status,
2754
+ runner: goal.runner || mission.runner,
2755
+ created_at: goal.created_at || mission.created_at,
2756
+ updated_at: goal.updated_at || mission.updated_at,
2757
+ completed_at: goal.completed_at || mission.completed_at,
2758
+ next_command: goal.next_command || payload?.next_command || mission.next_action,
2759
+ };
2760
+ const elapsed = formatGoalDuration(goalElapsedSeconds(receiptGoal)) || '0s';
2761
+
2762
+ console.log('Atris mission started');
2763
+ console.log(`Goal: ${receiptGoal.objective || '?'}`);
2764
+ console.log(`Mission: ${receiptGoal.mission_id || '?'} · ${receiptGoal.mission_status || '?'} · ${receiptGoal.runner || 'atris2'}`);
2765
+ console.log(`Elapsed: ${elapsed}`);
2766
+ console.log(`Achieved: ${goalAchieved(receiptGoal) ? 'yes' : 'no'}`);
2767
+ if (receiptGoal.next_command) console.log(`Next: ${receiptGoal.next_command}`);
2768
+ }
2769
+
2770
+ async function runLocalFastMission(intent) {
2771
+ const result = await missionRuntime.runRuntimeMissionLoop(intent, {
2772
+ cwd: process.cwd(),
2773
+ cliPath: __filename,
2774
+ onProgress: (line) => {
2775
+ if (/^pursuing /.test(line)) console.log('Pursuing goal...');
2776
+ else if (/^\[tick /.test(line)) console.log(line);
2777
+ },
2778
+ });
2779
+ console.log(result.text || 'Mission command failed.');
2780
+ return result.ok ? 0 : (result.status || 1);
2781
+ }
2782
+
2601
2783
  async function atrisFastChat() {
2602
2784
  if (command === 'ax' && process.argv[3] !== 'fast') {
2603
2785
  console.error('Usage: atris ax fast "message"');
@@ -2622,6 +2804,13 @@ async function atrisFastChat() {
2622
2804
  process.exit(1);
2623
2805
  }
2624
2806
 
2807
+ const missionIntent = missionRunIntentFromFastMessage(message);
2808
+ if (missionIntent) {
2809
+ process.exit(await runLocalFastMission(missionIntent));
2810
+ }
2811
+
2812
+ printAtrisGoalBanner(process.cwd());
2813
+
2625
2814
  const ensured = await ensureValidCredentials();
2626
2815
  if (ensured.error === 'not_logged_in' || !ensured.credentials?.token) {
2627
2816
  console.error('✗ Error: Not logged in. Run "atris login" first.');