atris 3.15.31 → 3.15.37

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.
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { loadCredentials } = require('../utils/auth');
4
4
  const { apiRequestJson } = require('../utils/api');
5
+ const { loadBusinesses, saveBusinesses, businessMatchesSlug } = require('./business');
5
6
 
6
7
  /**
7
8
  * Resolve business slug from CLI arg or local .atris/business.json.
@@ -22,6 +23,69 @@ function resolveSlug() {
22
23
  return slug;
23
24
  }
24
25
 
26
+ async function resolveBusiness(token, slug) {
27
+ const businesses = loadBusinesses();
28
+
29
+ const listResult = await apiRequestJson('/business/', { method: 'GET', token });
30
+ if (listResult.ok && Array.isArray(listResult.data)) {
31
+ const match = listResult.data.find((business) =>
32
+ businessMatchesSlug(business, slug, { includeName: true })
33
+ );
34
+ if (match && match.id) {
35
+ businesses[slug] = {
36
+ business_id: match.id,
37
+ workspace_id: match.workspace_id,
38
+ name: match.name || slug,
39
+ slug: match.slug || slug,
40
+ canonical_slug: match.slug || slug,
41
+ added_at: businesses[slug]?.added_at || new Date().toISOString(),
42
+ updated_at: new Date().toISOString(),
43
+ };
44
+ saveBusinesses(businesses);
45
+ return businesses[slug];
46
+ }
47
+ }
48
+
49
+ const wanted = String(slug || '').toLowerCase();
50
+ return businesses[slug] || Object.values(businesses).find((entry) =>
51
+ entry
52
+ && (
53
+ String(entry.slug || '').toLowerCase() === wanted
54
+ || String(entry.canonical_slug || '').toLowerCase() === wanted
55
+ || String(entry.name || '').toLowerCase() === wanted
56
+ )
57
+ ) || null;
58
+ }
59
+
60
+ async function activateBusinessWorkspace(token, business) {
61
+ if (!business?.business_id || !business?.workspace_id) return;
62
+ const result = await apiRequestJson(`/business/${business.business_id}/workspaces/${business.workspace_id}/activate`, {
63
+ method: 'POST',
64
+ token,
65
+ body: {},
66
+ });
67
+ if (!result.ok && result.status !== 409) {
68
+ throw new Error(`Failed to activate business workspace: ${result.errorMessage || result.error || result.status}`);
69
+ }
70
+ }
71
+
72
+ async function waitForBusinessComputer(token, business, maxWait = 90000) {
73
+ const start = Date.now();
74
+ while (Date.now() - start < maxWait) {
75
+ await new Promise((r) => setTimeout(r, 3000));
76
+
77
+ const status = await apiRequestJson(`/business/${business.business_id}/ai-computer/status`, {
78
+ method: 'GET',
79
+ token,
80
+ });
81
+
82
+ if (status.ok && status.data && status.data.status === 'running' && status.data.endpoint) {
83
+ return status.data;
84
+ }
85
+ }
86
+ return null;
87
+ }
88
+
25
89
  /**
26
90
  * Pause a workspace to save compute (storage only mode).
27
91
  * @returns {Promise<void>}
@@ -39,6 +103,23 @@ async function sleepAtris() {
39
103
  const creds = loadCredentials();
40
104
  if (!creds || !creds.token) { console.error('Not logged in. Run: atris login'); process.exit(1); }
41
105
 
106
+ const business = await resolveBusiness(creds.token, slug);
107
+ if (business?.business_id) {
108
+ const result = await apiRequestJson(`/business/${business.business_id}/ai-computer/sleep`, {
109
+ method: 'POST',
110
+ token: creds.token,
111
+ body: {},
112
+ });
113
+
114
+ if (!result.ok) {
115
+ console.error(`Failed to sleep business computer: ${result.error || result.errorMessage || result.status}`);
116
+ process.exit(1);
117
+ }
118
+
119
+ console.log(`Business computer '${business.name || slug}' is now sleeping. Files persist. Wake it with: atris wake ${slug}`);
120
+ return;
121
+ }
122
+
42
123
  const result = await apiRequestJson(`/workspace/${slug}/sleep`, {
43
124
  method: 'POST',
44
125
  token: creds.token,
@@ -70,6 +151,40 @@ async function wakeAtris() {
70
151
  const creds = loadCredentials();
71
152
  if (!creds || !creds.token) { console.error('Not logged in. Run: atris login'); process.exit(1); }
72
153
 
154
+ const business = await resolveBusiness(creds.token, slug);
155
+ if (business?.business_id) {
156
+ if (business.workspace_id) {
157
+ await activateBusinessWorkspace(creds.token, business);
158
+ }
159
+
160
+ const result = await apiRequestJson(`/business/${business.business_id}/ai-computer/wake`, {
161
+ method: 'POST',
162
+ token: creds.token,
163
+ body: {},
164
+ });
165
+
166
+ if (!result.ok) {
167
+ console.error(`Failed to wake business computer: ${result.error || result.errorMessage || result.status}`);
168
+ process.exit(1);
169
+ }
170
+
171
+ console.log(`Waking business computer '${business.name || slug}'...`);
172
+
173
+ if (result.data && result.data.status === 'running' && result.data.endpoint) {
174
+ console.log(`Business computer '${business.name || slug}' is alive. Agents resuming.`);
175
+ return;
176
+ }
177
+
178
+ const running = await waitForBusinessComputer(creds.token, business);
179
+ if (running) {
180
+ console.log(`Business computer '${business.name || slug}' is alive. Agents resuming.`);
181
+ return;
182
+ }
183
+
184
+ console.log('Still starting up. Check with: atris computer status --business ' + slug);
185
+ return;
186
+ }
187
+
73
188
  const result = await apiRequestJson(`/workspace/${slug}/wake`, {
74
189
  method: 'POST',
75
190
  token: creds.token,
@@ -707,7 +707,8 @@ function worktreeReceipt(before, after, { verifier = '' } = {}) {
707
707
  // ---------------------------------------------------------------------------
708
708
  // `atris mission run <id>` — bounded local headless loop. v0.1.
709
709
  // Spawns `claude -p --resume <session>` per tick. Honors cadence, active-hours,
710
- // rate-limit info, and a flock per mission. Only consumes max-ticks on `ran`.
710
+ // rate-limit info, and a flock per mission. `max-ticks` bounds total attempts;
711
+ // `ran_ticks` separately reports ticks that actually made progress.
711
712
  // ---------------------------------------------------------------------------
712
713
 
713
714
  const MISSION_RUN_DEFAULTS = {
@@ -1284,7 +1285,7 @@ async function runMission(args) {
1284
1285
  const sessionLabel = skipWorker ? 'caller-session' : (sessionId || `pending=${pendingSessionId}`);
1285
1286
  console.error(`[mission run] ${mission.id}\n objective: ${mission.objective}\n lane: ${frozen.lane}\n cadence: ${cadence} (${cadenceSeconds}s)\n max_ticks: ${effectiveMaxTicks}, max_wall: ${maxWallSeconds}s\n session: ${sessionLabel}`);
1286
1287
 
1287
- while (ranTicks < effectiveMaxTicks) {
1288
+ while (ticks.length < effectiveMaxTicks) {
1288
1289
  const elapsedSec = (Date.now() - startedAt) / 1000;
1289
1290
  const remainingWall = maxWallSeconds - elapsedSec;
1290
1291
  if (remainingWall <= 0) { pauseReason = 'max-wall-reached'; break; }
@@ -1462,12 +1463,17 @@ async function runMission(args) {
1462
1463
  }
1463
1464
  const remainingMs = remainingWall * 1000 - 1;
1464
1465
  sleepMs = Math.min(Math.max(0, sleepMs), Math.max(0, remainingMs));
1465
- if (sleepMs > 0 && ranTicks < effectiveMaxTicks) {
1466
+ if (sleepMs > 0 && ticks.length < effectiveMaxTicks) {
1466
1467
  try { await sleep(sleepMs, controller.signal); }
1467
1468
  catch (e) { if (e.code === 'ABORTED') { pauseReason = 'aborted'; break; } throw e; }
1468
1469
  }
1469
1470
  }
1470
1471
 
1472
+ if (!pauseReason && ticks.length >= effectiveMaxTicks) {
1473
+ const lastTick = ticks[ticks.length - 1];
1474
+ if (lastTick && lastTick.status !== 'ran') pauseReason = 'max-ticks-reached';
1475
+ }
1476
+
1471
1477
  if (pauseReason && !['complete', 'ready', 'max-wall-reached'].includes(pauseReason)) {
1472
1478
  mission = saveMission({
1473
1479
  ...mission,
package/commands/play.js CHANGED
@@ -7,7 +7,7 @@ const { spawnSync } = require('child_process');
7
7
  const { getSessionProfile, loadCredentials } = require('../utils/auth');
8
8
 
9
9
  const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
10
- const AGENTXP_GLOBAL_SYNC_RULE = 'Use the owner-provided sync token first; fallback is atris login before sync.';
10
+ const AGENTXP_GLOBAL_SYNC_RULE = 'Run atris login, then sync. Owner-provided sync tokens are guided-demo fallback only.';
11
11
 
12
12
  function showHelp() {
13
13
  console.log('');
@@ -257,9 +257,9 @@ function starterMissionPrompt(player) {
257
257
 
258
258
  function globalSyncCommands(player) {
259
259
  return [
260
- `atris xp sync --local --as ${player} --token <owner-provided-token>`,
261
260
  'atris login',
262
- `atris xp sync --local --as ${player}`,
261
+ 'atris xp sync --local',
262
+ 'atris xp sync --local --token <owner-provided-token>',
263
263
  ];
264
264
  }
265
265
 
@@ -431,7 +431,7 @@ function render(state) {
431
431
  console.log('');
432
432
  console.log('Win condition: real artifact + verifier + human accept.');
433
433
  console.log('XP rule: no proof, no AgentXP; accept/revise stays human-gated.');
434
- console.log('Global sync: use owner token first; fallback to atris login before hosted leaderboard sync.');
434
+ console.log('Global sync: run atris login, then sync; owner tokens are guided-demo fallback only.');
435
435
  console.log(`Leaderboard: ${state.leaderboard_url}`);
436
436
  console.log('');
437
437
  console.log('Next commands:');
package/commands/pull.js CHANGED
@@ -933,12 +933,12 @@ async function pullBusiness(slug) {
933
933
  const relativePath = path.relative(path.dirname(symlinkPath), fullPath);
934
934
 
935
935
  // Business skills override init skills (remove existing symlink if present)
936
- if (fs.existsSync(symlinkPath)) {
937
- try {
938
- const stat = fs.lstatSync(symlinkPath);
939
- if (stat.isSymbolicLink()) fs.unlinkSync(symlinkPath);
940
- else continue; // Don't overwrite real directories
941
- } catch { continue; }
936
+ try {
937
+ const stat = fs.lstatSync(symlinkPath);
938
+ if (stat.isSymbolicLink()) fs.unlinkSync(symlinkPath);
939
+ else continue; // Don't overwrite real directories
940
+ } catch (err) {
941
+ if (err && err.code !== 'ENOENT') continue;
942
942
  }
943
943
  try {
944
944
  fs.symlinkSync(relativePath, symlinkPath);
@@ -959,7 +959,11 @@ async function pullBusiness(slug) {
959
959
  // Count wired skills
960
960
  const wiredSkills = fs.readdirSync(claudeSkillsDir).filter(f => {
961
961
  const p = path.join(claudeSkillsDir, f);
962
- return fs.statSync(p).isDirectory();
962
+ try {
963
+ return fs.statSync(p).isDirectory();
964
+ } catch {
965
+ return false;
966
+ }
963
967
  });
964
968
  if (wiredSkills.length > 0) {
965
969
  console.log(` Wired ${wiredSkills.length} skills → .claude/skills/`);