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.
- package/README.md +3 -3
- package/ax +479 -21
- package/bin/atris.js +1 -1
- package/commands/aeo.js +377 -13
- package/commands/business.js +21 -2
- package/commands/computer.js +346 -16
- package/commands/gm.js +4 -4
- package/commands/lifecycle.js +115 -0
- package/commands/mission.js +9 -3
- package/commands/play.js +4 -4
- package/commands/pull.js +11 -7
- package/commands/xp.js +342 -13
- package/lib/runtime-bootstrap.js +107 -0
- package/package.json +1 -1
package/commands/lifecycle.js
CHANGED
|
@@ -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,
|
package/commands/mission.js
CHANGED
|
@@ -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.
|
|
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 (
|
|
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 &&
|
|
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 = '
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
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
|
-
|
|
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/`);
|