atris 3.15.30 → 3.15.36
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 +1 -1
- package/commands/business.js +21 -2
- package/commands/computer.js +515 -10
- package/commands/gm.js +4 -2
- package/commands/lifecycle.js +115 -0
- package/commands/mission.js +9 -3
- package/commands/play.js +4 -2
- package/commands/xp.js +309 -3
- package/lib/runtime-bootstrap.js +107 -0
- package/package.json +1 -1
package/commands/gm.js
CHANGED
|
@@ -5,6 +5,7 @@ const os = require('os');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
7
|
const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
|
|
8
|
+
const AGENTXP_GLOBAL_SYNC_RULE = 'Run atris login, then sync. Owner-provided sync tokens are guided-demo fallback only.';
|
|
8
9
|
|
|
9
10
|
const ROLE_PLAYERS_TO_IGNORE = new Set([
|
|
10
11
|
'game-manager',
|
|
@@ -267,6 +268,7 @@ function globalSyncCommands(player) {
|
|
|
267
268
|
return [
|
|
268
269
|
'atris login',
|
|
269
270
|
`atris xp sync --local --as ${player}`,
|
|
271
|
+
`atris xp sync --local --as ${player} --token <owner-provided-token>`,
|
|
270
272
|
];
|
|
271
273
|
}
|
|
272
274
|
|
|
@@ -330,7 +332,7 @@ function gmState(args = []) {
|
|
|
330
332
|
review_queue: reviewQueue,
|
|
331
333
|
next_commands: commands,
|
|
332
334
|
xp_rule: 'GM can route missions and review proof, but AgentXP still lands only after human accept.',
|
|
333
|
-
global_sync_rule:
|
|
335
|
+
global_sync_rule: AGENTXP_GLOBAL_SYNC_RULE,
|
|
334
336
|
leaderboard_url: AGENTXP_LEADERBOARD_URL,
|
|
335
337
|
};
|
|
336
338
|
}
|
|
@@ -365,7 +367,7 @@ function render(state) {
|
|
|
365
367
|
|
|
366
368
|
console.log('');
|
|
367
369
|
console.log('XP rule: no proof, no AgentXP; accept/revise stays human-gated.');
|
|
368
|
-
console.log('Global sync: run atris login
|
|
370
|
+
console.log('Global sync: run atris login, then sync; owner tokens are guided-demo fallback only.');
|
|
369
371
|
console.log(`Leaderboard: ${state.leaderboard_url}`);
|
|
370
372
|
console.log('');
|
|
371
373
|
console.log('Next commands:');
|
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,6 +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 = 'Run atris login, then sync. Owner-provided sync tokens are guided-demo fallback only.';
|
|
10
11
|
|
|
11
12
|
function showHelp() {
|
|
12
13
|
console.log('');
|
|
@@ -258,6 +259,7 @@ function globalSyncCommands(player) {
|
|
|
258
259
|
return [
|
|
259
260
|
'atris login',
|
|
260
261
|
`atris xp sync --local --as ${player}`,
|
|
262
|
+
`atris xp sync --local --as ${player} --token <owner-provided-token>`,
|
|
261
263
|
];
|
|
262
264
|
}
|
|
263
265
|
|
|
@@ -395,7 +397,7 @@ function modeState(args = []) {
|
|
|
395
397
|
prompt: latestMessage(events),
|
|
396
398
|
} : null,
|
|
397
399
|
xp_rule: 'AgentXP lands only after proof is ready and a human accepts the task.',
|
|
398
|
-
global_sync_rule:
|
|
400
|
+
global_sync_rule: AGENTXP_GLOBAL_SYNC_RULE,
|
|
399
401
|
leaderboard_url: AGENTXP_LEADERBOARD_URL,
|
|
400
402
|
next_commands: commandList,
|
|
401
403
|
};
|
|
@@ -429,7 +431,7 @@ function render(state) {
|
|
|
429
431
|
console.log('');
|
|
430
432
|
console.log('Win condition: real artifact + verifier + human accept.');
|
|
431
433
|
console.log('XP rule: no proof, no AgentXP; accept/revise stays human-gated.');
|
|
432
|
-
console.log('Global sync: run atris login
|
|
434
|
+
console.log('Global sync: run atris login, then sync; owner tokens are guided-demo fallback only.');
|
|
433
435
|
console.log(`Leaderboard: ${state.leaderboard_url}`);
|
|
434
436
|
console.log('');
|
|
435
437
|
console.log('Next commands:');
|
package/commands/xp.js
CHANGED
|
@@ -20,6 +20,50 @@ const AGENT_XP_LABEL = 'AgentXP';
|
|
|
20
20
|
const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
|
|
21
21
|
const LEVEL_XP = 1000;
|
|
22
22
|
const RECEIPT_CHAIN_VERSION = 'atris.career_xp_receipt_chain.v1';
|
|
23
|
+
const VALIDATION_TERMS = ['verify', 'verified', 'verifier', 'passed', 'pytest', 'receipt', 'score', 'smoke', 'git diff --check'];
|
|
24
|
+
const QUALITY_TERMS = ['craft', 'scorecard', 'quality', 'critique', 'review', 'taste'];
|
|
25
|
+
const ATRIS_ACCEPTED_STATUSES = new Set(['accepted', 'approved', 'done', 'review', 'reviewed', 'unknown']);
|
|
26
|
+
const ATRIS_ACCEPTED_EVENTS = new Set(['accepted', 'approved', 'completed', 'done', 'reviewed', 'unknown']);
|
|
27
|
+
const EARNING_MODEL = {
|
|
28
|
+
schema: 'atris.agentxp_earning_model.v1',
|
|
29
|
+
score_name: AGENT_XP_LABEL,
|
|
30
|
+
primary_public_source: 'accepted_task_receipt',
|
|
31
|
+
public_rule: 'No accepted proof-backed task episodes means no AgentXP leaderboard movement.',
|
|
32
|
+
weights: [
|
|
33
|
+
{
|
|
34
|
+
id: 'accepted_task_receipt',
|
|
35
|
+
label: 'Accepted proof-backed task',
|
|
36
|
+
relative_weight: 1,
|
|
37
|
+
public_leaderboard: true,
|
|
38
|
+
rl_value: 'high',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'validated_agent_loop',
|
|
42
|
+
label: 'Verifier or agent loop inside an accepted task',
|
|
43
|
+
relative_weight: 0.35,
|
|
44
|
+
public_leaderboard: true,
|
|
45
|
+
rl_value: 'high',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'atris_action_signal',
|
|
49
|
+
label: 'Atris action signal',
|
|
50
|
+
relative_weight: 0.1,
|
|
51
|
+
public_leaderboard: false,
|
|
52
|
+
rl_value: 'medium',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'local_assistant_activity',
|
|
56
|
+
label: 'Local assistant activity',
|
|
57
|
+
relative_weight: 0.05,
|
|
58
|
+
public_leaderboard: false,
|
|
59
|
+
rl_value: 'medium',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
caps: {
|
|
63
|
+
local_assistant_activity: 'Capped below 10% of accepted-task XP and never unlocks public ranking alone.',
|
|
64
|
+
atris_action_signal: 'Routes RL and next moves, but does not outrank an accepted proof-backed task.',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
23
67
|
const XP_STATE_FILES = new Set([
|
|
24
68
|
path.basename(TASK_EPISODES_FILE),
|
|
25
69
|
path.basename(CAREER_XP_RECEIPTS_FILE),
|
|
@@ -421,6 +465,199 @@ function sqlString(value) {
|
|
|
421
465
|
return `'${String(value).replace(/'/g, "''")}'`;
|
|
422
466
|
}
|
|
423
467
|
|
|
468
|
+
function existingSignal(label, filePath) {
|
|
469
|
+
if (!filePath) return null;
|
|
470
|
+
try {
|
|
471
|
+
const stat = fs.statSync(expandHome(filePath));
|
|
472
|
+
return {
|
|
473
|
+
label,
|
|
474
|
+
kind: stat.isDirectory() ? 'dir' : 'file',
|
|
475
|
+
size_bytes: stat.isFile() ? stat.size : null,
|
|
476
|
+
mtime: stat.mtime.toISOString(),
|
|
477
|
+
};
|
|
478
|
+
} catch (_) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function localAssistantProviderSignals(workspace) {
|
|
484
|
+
const codexStatePath = process.env.CODEX_STATE_DB || CODEX_STATE_FILE;
|
|
485
|
+
const candidates = [
|
|
486
|
+
{
|
|
487
|
+
id: 'codex',
|
|
488
|
+
label: 'Codex',
|
|
489
|
+
signals: [
|
|
490
|
+
existingSignal('codex_state', codexStatePath),
|
|
491
|
+
existingSignal('codex_history', path.join(os.homedir(), '.codex', 'history.jsonl')),
|
|
492
|
+
existingSignal('codex_sessions', path.join(os.homedir(), '.codex', 'sessions')),
|
|
493
|
+
],
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
id: 'claude',
|
|
497
|
+
label: 'Claude',
|
|
498
|
+
signals: [
|
|
499
|
+
existingSignal('claude_home', path.join(os.homedir(), '.claude')),
|
|
500
|
+
existingSignal('claude_config', path.join(os.homedir(), '.claude.json')),
|
|
501
|
+
existingSignal('workspace_claude', path.join(workspace, '.claude')),
|
|
502
|
+
],
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
id: 'cursor',
|
|
506
|
+
label: 'Cursor',
|
|
507
|
+
signals: [
|
|
508
|
+
existingSignal('workspace_cursor', path.join(workspace, '.cursor')),
|
|
509
|
+
existingSignal('cursor_home', path.join(os.homedir(), '.cursor')),
|
|
510
|
+
existingSignal('cursor_app_support', path.join(os.homedir(), 'Library', 'Application Support', 'Cursor')),
|
|
511
|
+
],
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
id: 'devin',
|
|
515
|
+
label: 'Devin',
|
|
516
|
+
signals: [
|
|
517
|
+
existingSignal('workspace_devin', path.join(workspace, '.devin')),
|
|
518
|
+
existingSignal('devin_home', path.join(os.homedir(), '.devin')),
|
|
519
|
+
],
|
|
520
|
+
},
|
|
521
|
+
];
|
|
522
|
+
|
|
523
|
+
return candidates.map((provider) => {
|
|
524
|
+
const signals = provider.signals.filter(Boolean);
|
|
525
|
+
return {
|
|
526
|
+
id: provider.id,
|
|
527
|
+
label: provider.label,
|
|
528
|
+
detected: signals.length > 0,
|
|
529
|
+
signal_count: signals.length,
|
|
530
|
+
signals,
|
|
531
|
+
};
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function buildLocalAssistantActivity(workspace, acceptedTaskXp = 0) {
|
|
536
|
+
const providers = localAssistantProviderSignals(workspace);
|
|
537
|
+
const detected = providers.filter(provider => provider.detected);
|
|
538
|
+
const rawSignalCount = detected.reduce((sum, provider) => sum + provider.signal_count, 0);
|
|
539
|
+
const weight = EARNING_MODEL.weights.find(item => item.id === 'local_assistant_activity')?.relative_weight || 0.05;
|
|
540
|
+
const weightedContext = Math.round(rawSignalCount * weight * 100) / 100;
|
|
541
|
+
const acceptedXp = asNumber(acceptedTaskXp);
|
|
542
|
+
const contextCap = Math.round(acceptedXp * 0.1 * 100) / 100;
|
|
543
|
+
const contextAgentXp = acceptedXp > 0 ? Math.min(weightedContext, contextCap) : 0;
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
schema: 'atris.agentxp_local_assistant_activity.v1',
|
|
547
|
+
public_leaderboard: false,
|
|
548
|
+
source_type: 'local_assistant_activity',
|
|
549
|
+
weight,
|
|
550
|
+
context_weight: weight,
|
|
551
|
+
cap: {
|
|
552
|
+
max_public_xp_ratio: 0.1,
|
|
553
|
+
requires_accepted_task_xp: true,
|
|
554
|
+
},
|
|
555
|
+
cap_ratio_to_accepted_task_xp: 0.1,
|
|
556
|
+
cap_agent_xp: contextCap,
|
|
557
|
+
accepted_task_agent_xp: acceptedXp,
|
|
558
|
+
detected_providers: detected.map(provider => provider.id).sort(),
|
|
559
|
+
provider_count: detected.length,
|
|
560
|
+
providers,
|
|
561
|
+
raw_signal_count: rawSignalCount,
|
|
562
|
+
weighted_context_score: weightedContext,
|
|
563
|
+
context_agent_xp: contextAgentXp,
|
|
564
|
+
included_in_total_agent_xp: false,
|
|
565
|
+
role: 'context_only',
|
|
566
|
+
note: 'Local assistant activity helps context and RL; accepted proof-backed tasks are the public AgentXP source.',
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function safeSignalKey(value) {
|
|
571
|
+
const text = String(value || '').trim().toLowerCase().replace(/[\s_]+/g, '-');
|
|
572
|
+
const clean = [...text].filter(char => /[a-z0-9_-]/.test(char)).join('');
|
|
573
|
+
return clean.slice(0, 40) || 'unknown';
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function hasAnyTerm(text, terms) {
|
|
577
|
+
const lower = String(text || '').toLowerCase();
|
|
578
|
+
return terms.some(term => lower.includes(term));
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function incrementCount(counts, key) {
|
|
582
|
+
counts[key] = (counts[key] || 0) + 1;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function buildAtrisActionSignalFromRows(rows = []) {
|
|
586
|
+
const eventTypeCounts = {};
|
|
587
|
+
const statusCounts = {};
|
|
588
|
+
const actors = new Set();
|
|
589
|
+
const claimants = new Set();
|
|
590
|
+
let proofBackedEpisodeCount = 0;
|
|
591
|
+
let acceptedEpisodeCount = 0;
|
|
592
|
+
let validationReceiptCount = 0;
|
|
593
|
+
let qualityReceiptCount = 0;
|
|
594
|
+
let claimedEpisodeCount = 0;
|
|
595
|
+
|
|
596
|
+
for (const row of rows || []) {
|
|
597
|
+
const action = row?.action && typeof row.action === 'object' ? row.action : {};
|
|
598
|
+
const state = row?.state && typeof row.state === 'object' ? row.state : {};
|
|
599
|
+
const eventType = safeSignalKey(action.event_type || action.type || action.name);
|
|
600
|
+
const status = safeSignalKey(state.status);
|
|
601
|
+
incrementCount(eventTypeCounts, eventType);
|
|
602
|
+
incrementCount(statusCounts, status);
|
|
603
|
+
|
|
604
|
+
const actor = slugify(action.actor || '');
|
|
605
|
+
if (actor) actors.add(actor);
|
|
606
|
+
const claimant = slugify(state.claimed_by || state.assigned_to || '');
|
|
607
|
+
if (claimant) {
|
|
608
|
+
claimants.add(claimant);
|
|
609
|
+
claimedEpisodeCount += 1;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const proof = String(row?.proof || '').trim();
|
|
613
|
+
const proofBacked = proof.length >= 20;
|
|
614
|
+
if (proofBacked) proofBackedEpisodeCount += 1;
|
|
615
|
+
if (proofBacked && hasAnyTerm(proof, VALIDATION_TERMS)) validationReceiptCount += 1;
|
|
616
|
+
if (proofBacked && hasAnyTerm(proof, QUALITY_TERMS)) qualityReceiptCount += 1;
|
|
617
|
+
if (
|
|
618
|
+
proofBacked
|
|
619
|
+
&& status !== 'failed'
|
|
620
|
+
&& (ATRIS_ACCEPTED_STATUSES.has(status) || ATRIS_ACCEPTED_EVENTS.has(eventType))
|
|
621
|
+
) {
|
|
622
|
+
acceptedEpisodeCount += 1;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return {
|
|
627
|
+
schema: 'atris.agentxp_atris_action_signal.v1',
|
|
628
|
+
source: '.atris/state/task_episodes.jsonl',
|
|
629
|
+
episode_count: rows.length,
|
|
630
|
+
accepted_episode_count: acceptedEpisodeCount,
|
|
631
|
+
proof_backed_episode_count: proofBackedEpisodeCount,
|
|
632
|
+
validation_receipt_count: validationReceiptCount,
|
|
633
|
+
quality_receipt_count: qualityReceiptCount,
|
|
634
|
+
claimed_episode_count: claimedEpisodeCount,
|
|
635
|
+
reviewed_episode_count: eventTypeCounts.reviewed || 0,
|
|
636
|
+
distinct_actor_count: actors.size,
|
|
637
|
+
distinct_claimant_count: claimants.size,
|
|
638
|
+
event_type_counts: Object.fromEntries(Object.entries(eventTypeCounts).sort()),
|
|
639
|
+
status_counts: Object.fromEntries(Object.entries(statusCounts).sort()),
|
|
640
|
+
included_in_total_agent_xp: false,
|
|
641
|
+
public_leaderboard: false,
|
|
642
|
+
role: 'rl_routing_only',
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function readTaskEpisodesForWorkspace(workspace) {
|
|
647
|
+
if (!workspace) return [];
|
|
648
|
+
return readJsonl(path.join(workspace, TASK_EPISODES_FILE));
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function buildAtrisActionSignal(projection) {
|
|
652
|
+
const workspaceRoots = Array.isArray(projection?.workspaces)
|
|
653
|
+
? projection.workspaces
|
|
654
|
+
.filter(workspace => workspace?.included && workspace.workspace_root)
|
|
655
|
+
.map(workspace => workspace.workspace_root)
|
|
656
|
+
: [projection?.workspace_root].filter(Boolean);
|
|
657
|
+
const rows = workspaceRoots.flatMap(workspace => readTaskEpisodesForWorkspace(workspace));
|
|
658
|
+
return buildAtrisActionSignalFromRows(rows);
|
|
659
|
+
}
|
|
660
|
+
|
|
424
661
|
function runSqliteJsonOptional(dbPath, sql) {
|
|
425
662
|
if (!dbPath || !fs.existsSync(dbPath)) return [];
|
|
426
663
|
const result = spawnSync('sqlite3', ['-readonly', '-json', dbPath, sql], { encoding: 'utf8' });
|
|
@@ -682,6 +919,7 @@ function buildCareerXpProjection(receipts, workspace, integrity = {}) {
|
|
|
682
919
|
percent: Math.round((currentLevelXp / LEVEL_XP) * 1000) / 10,
|
|
683
920
|
};
|
|
684
921
|
const latest = latestReceipt(accepted);
|
|
922
|
+
const localActivity = buildLocalAssistantActivity(workspace, totalXp);
|
|
685
923
|
|
|
686
924
|
return {
|
|
687
925
|
schema: 'atris.career_xp_projection.v1',
|
|
@@ -707,6 +945,8 @@ function buildCareerXpProjection(receipts, workspace, integrity = {}) {
|
|
|
707
945
|
contribution_graph: buildAgentXpContributionGraph(accepted),
|
|
708
946
|
receipts_count: accepted.length,
|
|
709
947
|
sources: countBySource(accepted),
|
|
948
|
+
earning_model: EARNING_MODEL,
|
|
949
|
+
local_activity: localActivity,
|
|
710
950
|
latest_accepted_proof: latest ? {
|
|
711
951
|
label: receiptLabel(latest),
|
|
712
952
|
receipt_id: latest.receipt_id,
|
|
@@ -929,6 +1169,7 @@ function buildAllCareerXpProjection(projections, searchRoots = []) {
|
|
|
929
1169
|
integrity_status: projection?.integrity_status || projection?.integrity?.status || 'unknown',
|
|
930
1170
|
leaderboard_eligible: Boolean(projection?.leaderboard_eligible),
|
|
931
1171
|
latest_accepted_proof: projection?.latest_accepted_proof || null,
|
|
1172
|
+
local_activity: projection?.local_activity || null,
|
|
932
1173
|
ledger: projection?.ledger || null,
|
|
933
1174
|
};
|
|
934
1175
|
}).sort((a, b) => {
|
|
@@ -960,6 +1201,17 @@ function buildAllCareerXpProjection(projections, searchRoots = []) {
|
|
|
960
1201
|
const contributionGraph = combineAgentXpContributionGraphs(
|
|
961
1202
|
verified.map(projection => projection.contribution_graph),
|
|
962
1203
|
);
|
|
1204
|
+
const activityProviders = new Set();
|
|
1205
|
+
let rawActivitySignals = 0;
|
|
1206
|
+
let contextAgentXp = 0;
|
|
1207
|
+
for (const projection of verified) {
|
|
1208
|
+
const activity = projection.local_activity || {};
|
|
1209
|
+
rawActivitySignals += asNumber(activity.raw_signal_count);
|
|
1210
|
+
contextAgentXp += asNumber(activity.context_agent_xp);
|
|
1211
|
+
for (const provider of activity.detected_providers || []) {
|
|
1212
|
+
activityProviders.add(provider);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
963
1215
|
|
|
964
1216
|
return {
|
|
965
1217
|
schema: 'atris.career_xp_profile.v1',
|
|
@@ -985,8 +1237,19 @@ function buildAllCareerXpProjection(projections, searchRoots = []) {
|
|
|
985
1237
|
},
|
|
986
1238
|
contribution_graph: contributionGraph,
|
|
987
1239
|
receipts_count: verified.reduce((sum, projection) => sum + asNumber(projection.receipts_count), 0),
|
|
1240
|
+
earning_model: EARNING_MODEL,
|
|
1241
|
+
local_activity: {
|
|
1242
|
+
schema: 'atris.agentxp_local_activity_summary.v1',
|
|
1243
|
+
public_leaderboard: false,
|
|
1244
|
+
detected_providers: Array.from(activityProviders).sort(),
|
|
1245
|
+
raw_signal_count: rawActivitySignals,
|
|
1246
|
+
context_agent_xp: Math.round(contextAgentXp * 100) / 100,
|
|
1247
|
+
included_in_total_agent_xp: false,
|
|
1248
|
+
note: 'Aggregated local assistant activity is context only; verified receipts define total AgentXP.',
|
|
1249
|
+
},
|
|
988
1250
|
latest_accepted_proof: latest,
|
|
989
1251
|
workspaces,
|
|
1252
|
+
integrity_status: warnings.length ? 'warnings' : 'verified',
|
|
990
1253
|
integrity: {
|
|
991
1254
|
status: warnings.length ? 'warnings' : 'verified',
|
|
992
1255
|
warnings,
|
|
@@ -1408,6 +1671,13 @@ function renderContributionGraph(graph) {
|
|
|
1408
1671
|
console.log('Legend: blank none | . started | : solid | * heavy | # breakout');
|
|
1409
1672
|
}
|
|
1410
1673
|
|
|
1674
|
+
function renderLocalActivity(activity) {
|
|
1675
|
+
if (!activity || !Array.isArray(activity.detected_providers) || !activity.detected_providers.length) return;
|
|
1676
|
+
console.log(
|
|
1677
|
+
`Local activity: ${activity.detected_providers.join(', ')} | context ${formatNumber(activity.context_agent_xp)} | not public ${AGENT_XP_LABEL}`
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1411
1681
|
function loadLocalPayload(args) {
|
|
1412
1682
|
const workspace = path.resolve(readFlag(args, '--workspace', process.cwd()));
|
|
1413
1683
|
const operator = readFlag(
|
|
@@ -1496,6 +1766,7 @@ function buildAgentXpSyncPacket(args = []) {
|
|
|
1496
1766
|
const receiptsCount = asNumber(projection.receipts_count);
|
|
1497
1767
|
const eligible = verifiedProjection(projection) && receiptsCount > 0 && totalXp > 0;
|
|
1498
1768
|
const publicXp = publicAgentXp(totalXp);
|
|
1769
|
+
const workspaceRootHash = sha256(workspaces.map(item => item.workspace_root_hash || item.name).sort().join(':'));
|
|
1499
1770
|
const entry = {
|
|
1500
1771
|
user_id: player,
|
|
1501
1772
|
username: player,
|
|
@@ -1517,7 +1788,7 @@ function buildAgentXpSyncPacket(args = []) {
|
|
|
1517
1788
|
const packet = {
|
|
1518
1789
|
schema: 'atris.agentxp_sync_packet.v1',
|
|
1519
1790
|
generated_at: new Date().toISOString(),
|
|
1520
|
-
workspace_root_hash:
|
|
1791
|
+
workspace_root_hash: workspaceRootHash,
|
|
1521
1792
|
computer: projection.workspace_name || workspaces[0]?.name || 'local',
|
|
1522
1793
|
operator: player,
|
|
1523
1794
|
privacy: {
|
|
@@ -1531,14 +1802,43 @@ function buildAgentXpSyncPacket(args = []) {
|
|
|
1531
1802
|
trust_rule: 'Only verified local ledgers are uploaded; raw proof and paths stay local.',
|
|
1532
1803
|
},
|
|
1533
1804
|
local_evidence: {
|
|
1805
|
+
schema: 'atris.agentxp_local_evidence.v1',
|
|
1806
|
+
workspace_root_hash: workspaceRootHash,
|
|
1534
1807
|
workspaces,
|
|
1535
1808
|
verified_workspace_count: asNumber(projection.verified_workspace_count, verifiedProjection(projection) ? 1 : 0),
|
|
1536
1809
|
receipts_count: receiptsCount,
|
|
1537
1810
|
integrity_status: projection.integrity?.status || projection.integrity_status || 'unknown',
|
|
1538
1811
|
ledger_head_hash: projection.integrity?.head_hash || null,
|
|
1812
|
+
local_activity: {
|
|
1813
|
+
schema: projection.local_activity?.schema || 'atris.agentxp_local_assistant_activity.v1',
|
|
1814
|
+
detected_providers: projection.local_activity?.detected_providers || [],
|
|
1815
|
+
provider_count: asNumber(projection.local_activity?.provider_count, (projection.local_activity?.detected_providers || []).length),
|
|
1816
|
+
raw_signal_count: asNumber(projection.local_activity?.raw_signal_count),
|
|
1817
|
+
context_agent_xp: asNumber(projection.local_activity?.context_agent_xp),
|
|
1818
|
+
context_weight: asNumber(projection.local_activity?.context_weight, 0.05),
|
|
1819
|
+
cap_ratio_to_accepted_task_xp: asNumber(projection.local_activity?.cap_ratio_to_accepted_task_xp, 0.1),
|
|
1820
|
+
cap_agent_xp: asNumber(projection.local_activity?.cap_agent_xp),
|
|
1821
|
+
accepted_task_agent_xp: asNumber(projection.local_activity?.accepted_task_agent_xp, totalXp),
|
|
1822
|
+
included_in_total_agent_xp: false,
|
|
1823
|
+
public_leaderboard: false,
|
|
1824
|
+
role: 'context_only',
|
|
1825
|
+
},
|
|
1826
|
+
atris_actions: buildAtrisActionSignal(projection),
|
|
1827
|
+
},
|
|
1828
|
+
gm_projection: {
|
|
1829
|
+
schema: 'atris.gm_xp_projection.v1',
|
|
1830
|
+
workspace_root_hash: workspaceRootHash,
|
|
1831
|
+
operator: player,
|
|
1832
|
+
player_score: {
|
|
1833
|
+
agent_xp: totalXp,
|
|
1834
|
+
career_xp: totalXp,
|
|
1835
|
+
leaderboard_eligible: eligible,
|
|
1836
|
+
verified_receipts: receiptsCount,
|
|
1837
|
+
},
|
|
1539
1838
|
},
|
|
1540
1839
|
user_leaderboard: {
|
|
1541
1840
|
schema: 'atris.agentxp_user_leaderboard.v1',
|
|
1841
|
+
workspace_root_hash: workspaceRootHash,
|
|
1542
1842
|
score_name: AGENT_XP_LABEL,
|
|
1543
1843
|
entries: [entry],
|
|
1544
1844
|
},
|
|
@@ -1570,7 +1870,10 @@ async function syncAgentXp(args = []) {
|
|
|
1570
1870
|
} else {
|
|
1571
1871
|
const ensured = await ensureValidCredentials(apiRequestJson);
|
|
1572
1872
|
if (ensured.error) {
|
|
1573
|
-
throw new Error(
|
|
1873
|
+
throw new Error(
|
|
1874
|
+
`Missing sync auth. Run atris login, then retry atris xp sync. `
|
|
1875
|
+
+ `Guided demos can pass --token <owner-provided-token>${ensured.detail ? ` (${ensured.detail})` : ''}.`
|
|
1876
|
+
);
|
|
1574
1877
|
}
|
|
1575
1878
|
options.token = ensured.credentials.token;
|
|
1576
1879
|
}
|
|
@@ -1596,7 +1899,8 @@ function renderSync(payload) {
|
|
|
1596
1899
|
console.log(`Player ${payload.player || entry.username || 'player'} | AgentXP ${formatNumber(entry.agent_xp)} | receipts ${formatNumber(entry.verified_receipts)}`);
|
|
1597
1900
|
if (payload.dry_run) {
|
|
1598
1901
|
console.log(`Packet ${payload.packet?.packet_hash || 'unhashed'} ready; no network upload ran.`);
|
|
1599
|
-
console.log(
|
|
1902
|
+
console.log(`Run atris login, then atris xp sync --local --as ${payload.player || entry.username || '<player>'} to publish to the hosted leaderboard.`);
|
|
1903
|
+
console.log('Guided demos can pass --token <owner-provided-token>.');
|
|
1600
1904
|
console.log(`Leaderboard: ${AGENTXP_LEADERBOARD_URL}`);
|
|
1601
1905
|
return;
|
|
1602
1906
|
}
|
|
@@ -1660,6 +1964,7 @@ function render(payload) {
|
|
|
1660
1964
|
} else {
|
|
1661
1965
|
console.log('Latest proof: none accepted yet');
|
|
1662
1966
|
}
|
|
1967
|
+
renderLocalActivity(payload.local_activity);
|
|
1663
1968
|
const integrity = payload.integrity || {};
|
|
1664
1969
|
console.log(`Integrity: ${integrity.status || 'unknown'} (${integrity.local_trust || 'local'})`);
|
|
1665
1970
|
for (const warning of integrity.warnings || []) {
|
|
@@ -1681,6 +1986,7 @@ function render(payload) {
|
|
|
1681
1986
|
} else {
|
|
1682
1987
|
console.log('Latest proof: none accepted yet');
|
|
1683
1988
|
}
|
|
1989
|
+
renderLocalActivity(payload.local_activity);
|
|
1684
1990
|
console.log(`Ledger: ${payload.ledger?.projection_path || CAREER_XP_PROJECTION_FILE}`);
|
|
1685
1991
|
return;
|
|
1686
1992
|
}
|