atris 3.15.22 → 3.15.30
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 +53 -0
- package/ax +1083 -0
- package/commands/aeo.js +377 -13
- package/commands/gm.js +25 -9
- package/commands/mission.js +117 -7
- package/commands/play.js +41 -9
- package/commands/sync.js +9 -4
- package/commands/xp.js +224 -1
- package/package.json +5 -2
package/commands/mission.js
CHANGED
|
@@ -132,6 +132,79 @@ function printJsonOrText(payload, lines, asJson) {
|
|
|
132
132
|
for (const line of lines) console.log(line);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
function loadTaskDb(asJson = false) {
|
|
136
|
+
try {
|
|
137
|
+
return require('../lib/task-db');
|
|
138
|
+
} catch (error) {
|
|
139
|
+
const message = error && error.message ? error.message : String(error);
|
|
140
|
+
if (error?.code === 'ERR_UNKNOWN_BUILTIN_MODULE' || /node:sqlite/.test(message)) {
|
|
141
|
+
exitMissionError('AgentXP mission tasks require Node 22+ with node:sqlite.', 2, asJson);
|
|
142
|
+
}
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function writeMissionTaskProjection(taskDb, db, workspaceRoot) {
|
|
148
|
+
const projection = taskDb.taskProjection(db, { workspaceRoot, limit: 500 });
|
|
149
|
+
const outPath = path.join(workspaceRoot, '.atris', 'state', 'tasks.projection.json');
|
|
150
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
151
|
+
fs.writeFileSync(outPath, JSON.stringify(projection, null, 2) + '\n', 'utf8');
|
|
152
|
+
return { projection, outPath };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function missionTaskRef(task) {
|
|
156
|
+
return task?.display_id || task?.legacy_ref || String(task?.id || '').replace(/[^a-zA-Z0-9]/g, '').toUpperCase().slice(0, 8);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function createMissionXpTask(mission, root = process.cwd(), asJson = false) {
|
|
160
|
+
const taskDb = loadTaskDb(asJson);
|
|
161
|
+
const db = taskDb.open();
|
|
162
|
+
const workspaceRoot = taskDb.workspaceRoot(root);
|
|
163
|
+
const title = `Mission XP: ${mission.objective}`;
|
|
164
|
+
const metadata = {
|
|
165
|
+
assigned_to: mission.owner,
|
|
166
|
+
delegate_via: 'mission_goal_loop',
|
|
167
|
+
created_for_day: todayName(),
|
|
168
|
+
goal_id: mission.id,
|
|
169
|
+
goal_objective: mission.objective,
|
|
170
|
+
mission_id: mission.id,
|
|
171
|
+
mission_objective: mission.objective,
|
|
172
|
+
mission_owner: mission.owner,
|
|
173
|
+
mission_lane: mission.lane,
|
|
174
|
+
mission_runner: mission.runner,
|
|
175
|
+
verify: mission.verifier || null,
|
|
176
|
+
stop_condition: mission.stop_condition || null,
|
|
177
|
+
};
|
|
178
|
+
const result = taskDb.addTask(db, {
|
|
179
|
+
title,
|
|
180
|
+
tag: 'agent-xp',
|
|
181
|
+
workspaceRoot,
|
|
182
|
+
sourceKey: `mission-xp:${mission.id}`,
|
|
183
|
+
status: 'claimed',
|
|
184
|
+
claimedBy: mission.owner,
|
|
185
|
+
metadata,
|
|
186
|
+
});
|
|
187
|
+
const rows = taskDb.withTaskDisplayRefs(taskDb.listTasks(db, { workspaceRoot }));
|
|
188
|
+
const task = rows.find(row => row.id === result.id);
|
|
189
|
+
if (task) {
|
|
190
|
+
taskDb.noteTask(db, {
|
|
191
|
+
id: task.id,
|
|
192
|
+
actor: process.env.ATRIS_AGENT_ID || mission.owner || 'mission-lead',
|
|
193
|
+
content: `Mission goal loop XP bridge for ${mission.id}. Proof goes through task ready; AgentXP lands only after human accept.`,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const { outPath } = writeMissionTaskProjection(taskDb, db, workspaceRoot);
|
|
197
|
+
return {
|
|
198
|
+
task_id: result.id,
|
|
199
|
+
ref: missionTaskRef(task) || result.id,
|
|
200
|
+
title,
|
|
201
|
+
status: task?.status || 'claimed',
|
|
202
|
+
assigned_to: mission.owner,
|
|
203
|
+
inserted: result.inserted !== false,
|
|
204
|
+
projection_path: outPath,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
135
208
|
function statePaths(root = process.cwd()) {
|
|
136
209
|
const stateDir = path.join(root, '.atris', 'state');
|
|
137
210
|
return {
|
|
@@ -323,6 +396,7 @@ function renderMemberNowMarkdown(owner, missions) {
|
|
|
323
396
|
lines.push(`- cadence: ${mission.cadence}`);
|
|
324
397
|
lines.push(`- runner: ${mission.runner}`);
|
|
325
398
|
lines.push(`- lane: ${mission.lane}`);
|
|
399
|
+
if (mission.xp_task?.ref) lines.push(`- AgentXP task: ${mission.xp_task.ref}`);
|
|
326
400
|
if (mission.verifier) lines.push(`- verifier: ${mission.verifier}`);
|
|
327
401
|
if (mission.stop_condition) lines.push(`- stop: ${mission.stop_condition}`);
|
|
328
402
|
if (mission.next_action) lines.push(`- next: ${mission.next_action}`);
|
|
@@ -366,6 +440,7 @@ function renderMissionStatus(root = process.cwd()) {
|
|
|
366
440
|
lines.push(` - owner: ${mission.owner}`);
|
|
367
441
|
lines.push(` - state: ${mission.status}`);
|
|
368
442
|
lines.push(` - next: ${mission.next_action || 'tick or verify'}`);
|
|
443
|
+
if (mission.xp_task?.ref) lines.push(` - AgentXP task: ${mission.xp_task.ref}`);
|
|
369
444
|
if (mission.receipt_path) lines.push(` - proof: ${mission.receipt_path}`);
|
|
370
445
|
}
|
|
371
446
|
lines.push('');
|
|
@@ -376,6 +451,18 @@ function renderMissionStatus(root = process.cwd()) {
|
|
|
376
451
|
return paths.statusNow;
|
|
377
452
|
}
|
|
378
453
|
|
|
454
|
+
function missionXpTaskRefFromMission(mission) {
|
|
455
|
+
if (mission?.xp_task?.ref) return mission.xp_task.ref;
|
|
456
|
+
if (mission?.xp_task_enabled && mission?.task_ids?.[0]) return mission.task_ids[0];
|
|
457
|
+
return '';
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function missionXpReadyAction(mission, receiptPath) {
|
|
461
|
+
const ref = missionXpTaskRefFromMission(mission);
|
|
462
|
+
if (!ref || !receiptPath) return null;
|
|
463
|
+
return `queue AgentXP review: atris task ready ${ref} --proof "${receiptPath}"`;
|
|
464
|
+
}
|
|
465
|
+
|
|
379
466
|
function missionFromArgs(args) {
|
|
380
467
|
const objective = stripKnownFlags(args, [
|
|
381
468
|
'--owner',
|
|
@@ -387,7 +474,7 @@ function missionFromArgs(args) {
|
|
|
387
474
|
'--stop',
|
|
388
475
|
'--task',
|
|
389
476
|
'--ask',
|
|
390
|
-
], ['--json', '--always-on']).join(' ').trim();
|
|
477
|
+
], ['--json', '--always-on', '--xp-task', '--agent-xp']).join(' ').trim();
|
|
391
478
|
if (!objective) {
|
|
392
479
|
exitMissionError('Usage: atris mission start "<objective>" --owner <member> [--verify "..."] [--cadence manual]', 1, wantsJson(args));
|
|
393
480
|
}
|
|
@@ -401,6 +488,7 @@ function missionFromArgs(args) {
|
|
|
401
488
|
const taskIds = readRepeatedFlag(args, '--task');
|
|
402
489
|
const humanAsks = readRepeatedFlag(args, '--ask');
|
|
403
490
|
const alwaysOn = hasFlag(args, '--always-on');
|
|
491
|
+
const xpTaskEnabled = hasFlag(args, '--xp-task') || hasFlag(args, '--agent-xp');
|
|
404
492
|
const id = missionId(objective);
|
|
405
493
|
const mission = {
|
|
406
494
|
schema: 'atris.mission.v1',
|
|
@@ -414,6 +502,7 @@ function missionFromArgs(args) {
|
|
|
414
502
|
lane,
|
|
415
503
|
verifier,
|
|
416
504
|
always_on: alwaysOn,
|
|
505
|
+
xp_task_enabled: xpTaskEnabled,
|
|
417
506
|
stop_condition: stopCondition,
|
|
418
507
|
task_ids: taskIds,
|
|
419
508
|
human_asks: humanAsks,
|
|
@@ -437,6 +526,14 @@ function missingVerifierWarning(mission) {
|
|
|
437
526
|
function startMission(args) {
|
|
438
527
|
const asJson = wantsJson(args);
|
|
439
528
|
const mission = missionFromArgs(args);
|
|
529
|
+
if (mission.xp_task_enabled) {
|
|
530
|
+
const xpTask = createMissionXpTask(mission, process.cwd(), asJson);
|
|
531
|
+
mission.xp_task = xpTask;
|
|
532
|
+
mission.task_ids = Array.from(new Set([...(mission.task_ids || []), xpTask.task_id]));
|
|
533
|
+
if (!mission.verifier && !mission.always_on) {
|
|
534
|
+
mission.next_action = `work task then run: atris task ready ${xpTask.ref} --proof "<proof>"`;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
440
537
|
const warnings = [missingVerifierWarning(mission)].filter(Boolean);
|
|
441
538
|
ensureMemberMissionFile(mission.owner, process.cwd(), mission.objective);
|
|
442
539
|
const { mission: saved } = saveMission(mission, process.cwd(), 'mission_started', { objective: mission.objective });
|
|
@@ -455,6 +552,7 @@ function startMission(args) {
|
|
|
455
552
|
`Owner: ${saved.owner}`,
|
|
456
553
|
`State: ${saved.status}`,
|
|
457
554
|
...warnings.map((warning) => `Warning: ${warning.message}`),
|
|
555
|
+
...(saved.xp_task ? [`AgentXP task: ${saved.xp_task.ref}`] : []),
|
|
458
556
|
`Next: atris mission tick ${saved.id}`,
|
|
459
557
|
],
|
|
460
558
|
asJson,
|
|
@@ -694,6 +792,10 @@ function codexGoalObjective(mission) {
|
|
|
694
792
|
}
|
|
695
793
|
|
|
696
794
|
function codexGoalNextCommand(mission) {
|
|
795
|
+
if (mission.status === 'ready') {
|
|
796
|
+
const xpAction = missionXpReadyAction(mission, mission.receipt_path);
|
|
797
|
+
if (xpAction) return xpAction.replace(/^queue AgentXP review: /, '');
|
|
798
|
+
}
|
|
697
799
|
if (mission.verifier && missionDueAt(mission)) {
|
|
698
800
|
return 'atris mission run --due --max-ticks 1 --complete-on-pass';
|
|
699
801
|
}
|
|
@@ -1294,7 +1396,9 @@ async function runMission(args) {
|
|
|
1294
1396
|
worktree: tickWorktree,
|
|
1295
1397
|
});
|
|
1296
1398
|
|
|
1399
|
+
const xpReadyAction = missionXpReadyAction(mission, receiptPath);
|
|
1297
1400
|
const newStatus = (verifierResult?.passed && mission.always_on) ? 'running' :
|
|
1401
|
+
(verifierResult?.passed && xpReadyAction) ? 'ready' :
|
|
1298
1402
|
(verifierResult?.passed && completeOnPass) ? 'complete' :
|
|
1299
1403
|
(verifierResult?.passed ? 'ready' :
|
|
1300
1404
|
(verifierResult ? 'blocked' :
|
|
@@ -1302,6 +1406,8 @@ async function runMission(args) {
|
|
|
1302
1406
|
let nextAction = mission.next_action;
|
|
1303
1407
|
if (verifierResult?.passed && mission.always_on) {
|
|
1304
1408
|
nextAction = nextCandidateTickAction(mission);
|
|
1409
|
+
} else if (verifierResult?.passed && xpReadyAction) {
|
|
1410
|
+
nextAction = xpReadyAction;
|
|
1305
1411
|
} else if (verifierResult?.passed && completeOnPass) {
|
|
1306
1412
|
nextAction = 'mission complete';
|
|
1307
1413
|
} else if (verifierResult?.passed) {
|
|
@@ -1482,9 +1588,10 @@ function tickMission(args) {
|
|
|
1482
1588
|
let status = 'running';
|
|
1483
1589
|
let nextAction = mission.verifier ? `run verifier: ${mission.verifier}` : 'attach task, verifier, or proof';
|
|
1484
1590
|
if (verifierResult?.passed) {
|
|
1485
|
-
|
|
1591
|
+
const xpReadyAction = missionXpReadyAction(mission, receiptPath);
|
|
1592
|
+
status = (completeOnPass && !mission.always_on && !xpReadyAction) ? 'complete' : 'ready';
|
|
1486
1593
|
nextAction = mission.always_on ? nextCandidateTickAction(mission) :
|
|
1487
|
-
(completeOnPass ? 'mission complete' : `review proof then run: atris mission complete ${mission.id} --proof "${receiptPath}"`);
|
|
1594
|
+
(xpReadyAction || (completeOnPass ? 'mission complete' : `review proof then run: atris mission complete ${mission.id} --proof "${receiptPath}"`));
|
|
1488
1595
|
} else if (verifierResult) {
|
|
1489
1596
|
status = 'blocked';
|
|
1490
1597
|
nextAction = 'fix verifier failure or revise mission';
|
|
@@ -1549,9 +1656,10 @@ function completeMission(args) {
|
|
|
1549
1656
|
const { mission: saved } = saveMission(next, process.cwd(), 'mission_completed', { proof });
|
|
1550
1657
|
const logPath = appendMemberLog(saved.owner, 'Mission completed', { mission: saved.objective, proof });
|
|
1551
1658
|
const codexGoalState = refreshCodexGoalController(process.cwd());
|
|
1659
|
+
const xpNextCommand = missionXpReadyAction(saved, proof);
|
|
1552
1660
|
printJsonOrText(
|
|
1553
|
-
{ ok: true, action: 'mission_completed', mission: saved, log_path: logPath, codex_goal_state: codexGoalState },
|
|
1554
|
-
[`Completed mission: ${saved.objective}`, `Proof: ${proof}`],
|
|
1661
|
+
{ ok: true, action: 'mission_completed', mission: saved, log_path: logPath, codex_goal_state: codexGoalState, xp_next_command: xpNextCommand },
|
|
1662
|
+
[`Completed mission: ${saved.objective}`, `Proof: ${proof}`, ...(xpNextCommand ? [`AgentXP: ${xpNextCommand}`] : [])],
|
|
1555
1663
|
asJson,
|
|
1556
1664
|
);
|
|
1557
1665
|
}
|
|
@@ -1683,7 +1791,7 @@ function help() {
|
|
|
1683
1791
|
console.log(`
|
|
1684
1792
|
atris mission - durable goal + loop + owner + proof state
|
|
1685
1793
|
|
|
1686
|
-
atris mission start "<objective>" --owner <member> [--verify "..."] [--always-on]
|
|
1794
|
+
atris mission start "<objective>" --owner <member> [--verify "..."] [--always-on] [--xp-task]
|
|
1687
1795
|
atris mission status [id] [--status <state>] [--limit <n>] [--json]
|
|
1688
1796
|
atris mission goal [--heartbeat] [--json]
|
|
1689
1797
|
atris mission goal-loop [--max-wall 28800] [--max-iterations 32] [--no-claude] [--json]
|
|
@@ -1696,13 +1804,15 @@ atris mission - durable goal + loop + owner + proof state
|
|
|
1696
1804
|
Autonomy recipe:
|
|
1697
1805
|
1. Pick an owner member: atris member create <member> (if missing)
|
|
1698
1806
|
2. Start a current-agent mission with a verifier:
|
|
1699
|
-
atris mission start "ship one proof" --owner <member> --runner codex_goal --lane code --verify "npm test" --stop "verifier passes"
|
|
1807
|
+
atris mission start "ship one proof" --owner <member> --runner codex_goal --lane code --verify "npm test" --stop "verifier passes" --xp-task
|
|
1700
1808
|
3. Codex sessions: atris mission goal --json, then set /goal to goal.objective
|
|
1701
1809
|
Overnight controller: atris mission goal --heartbeat --json
|
|
1702
1810
|
Bounded overnight runner: atris mission goal-loop --max-wall 28800 --no-claude --json
|
|
1703
1811
|
4. Do one bounded step, then record it:
|
|
1704
1812
|
atris mission tick <id> --verify --summary "what changed"
|
|
1705
1813
|
5. Close or continue from the receipt:
|
|
1814
|
+
atris task ready <xp_task_ref> --proof "<receipt_path>" (if --xp-task)
|
|
1815
|
+
atris task accept <xp_task_ref> --reward <n> (human accept mints AgentXP)
|
|
1706
1816
|
atris mission complete <id> --proof "<receipt_path>"
|
|
1707
1817
|
repeat status -> step -> tick for current-agent work
|
|
1708
1818
|
atris mission run <id> --max-ticks 4 --complete-on-pass (Claude/always-on runner)
|
package/commands/play.js
CHANGED
|
@@ -6,6 +6,8 @@ const fs = require('fs');
|
|
|
6
6
|
const { spawnSync } = require('child_process');
|
|
7
7
|
const { getSessionProfile, loadCredentials } = require('../utils/auth');
|
|
8
8
|
|
|
9
|
+
const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
|
|
10
|
+
|
|
9
11
|
function showHelp() {
|
|
10
12
|
console.log('');
|
|
11
13
|
console.log('Usage: atris play [--as <player>] [--workspace <path>] [--json]');
|
|
@@ -252,10 +254,17 @@ function starterMissionPrompt(player) {
|
|
|
252
254
|
].join(' ');
|
|
253
255
|
}
|
|
254
256
|
|
|
257
|
+
function globalSyncCommands(player) {
|
|
258
|
+
return [
|
|
259
|
+
'atris login',
|
|
260
|
+
`atris xp sync --local --as ${player}`,
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
|
|
255
264
|
function ensureStarterMission(taskDb, db, workspaceRoot, player, tasks, args = []) {
|
|
256
265
|
if (hasFlag(args, '--no-seed')) return { tasks, seeded: null };
|
|
257
266
|
if (selectMission(tasks, player)) return { tasks, seeded: null };
|
|
258
|
-
|
|
267
|
+
fs.mkdirSync(path.join(workspaceRoot, 'atris'), { recursive: true });
|
|
259
268
|
|
|
260
269
|
const result = taskDb.addTask(db, {
|
|
261
270
|
title: starterMissionTitle(),
|
|
@@ -282,7 +291,21 @@ function ensureStarterMission(taskDb, db, workspaceRoot, player, tasks, args = [
|
|
|
282
291
|
return { tasks: refreshed, seeded };
|
|
283
292
|
}
|
|
284
293
|
|
|
294
|
+
function playWorkspaceRoot(taskDb, workspaceArg) {
|
|
295
|
+
let requested = path.resolve(workspaceArg || process.cwd());
|
|
296
|
+
try { requested = fs.realpathSync(requested); } catch {}
|
|
297
|
+
if (
|
|
298
|
+
fs.existsSync(path.join(requested, '.git'))
|
|
299
|
+
|| fs.existsSync(path.join(requested, 'atris'))
|
|
300
|
+
|| fs.existsSync(path.join(requested, '.atris'))
|
|
301
|
+
) {
|
|
302
|
+
return taskDb.workspaceRoot(requested);
|
|
303
|
+
}
|
|
304
|
+
return requested;
|
|
305
|
+
}
|
|
306
|
+
|
|
285
307
|
function nextCommands(task, player) {
|
|
308
|
+
const helper = 'game-manager';
|
|
286
309
|
if (!task) {
|
|
287
310
|
return [
|
|
288
311
|
`atris task delegate "AgentXP first rep: one proof-backed mission" --to ${player} --tag agent-xp`,
|
|
@@ -293,40 +316,45 @@ function nextCommands(task, player) {
|
|
|
293
316
|
const ref = taskRef(task);
|
|
294
317
|
if (task.status === 'open') {
|
|
295
318
|
return [
|
|
296
|
-
`atris task claim ${ref} --as ${
|
|
297
|
-
`atris task ready ${ref} --proof "<artifact path + verifier result>"`,
|
|
298
|
-
`atris task accept ${ref} --proof "<human review>"`,
|
|
319
|
+
`atris task claim ${ref} --as ${helper}`,
|
|
320
|
+
`atris task ready ${ref} --as ${helper} --proof "<artifact path + verifier result>"`,
|
|
321
|
+
`atris task accept ${ref} --as ${player} --proof "<human review>"`,
|
|
299
322
|
'atris xp card --local',
|
|
323
|
+
...globalSyncCommands(player),
|
|
300
324
|
];
|
|
301
325
|
}
|
|
302
326
|
|
|
303
327
|
if (task.status === 'claimed') {
|
|
328
|
+
const actor = task.claimed_by || helper;
|
|
304
329
|
return [
|
|
305
|
-
`atris task ready ${ref} --proof "<artifact path + verifier result>"`,
|
|
306
|
-
`atris task accept ${ref} --proof "<human review>"`,
|
|
330
|
+
`atris task ready ${ref} --as ${actor} --proof "<artifact path + verifier result>"`,
|
|
331
|
+
`atris task accept ${ref} --as ${player} --proof "<human review>"`,
|
|
307
332
|
'atris xp card --local',
|
|
333
|
+
...globalSyncCommands(player),
|
|
308
334
|
];
|
|
309
335
|
}
|
|
310
336
|
|
|
311
337
|
if (task.status === 'review') {
|
|
312
338
|
return [
|
|
313
339
|
`atris task show ${ref}`,
|
|
314
|
-
`atris task accept ${ref} --proof "<human review>"`,
|
|
315
|
-
`atris task revise ${ref} --note "<what must change>"`,
|
|
340
|
+
`atris task accept ${ref} --as ${player} --proof "<human review>"`,
|
|
341
|
+
`atris task revise ${ref} --as ${player} --note "<what must change>"`,
|
|
316
342
|
'atris xp card --local',
|
|
343
|
+
...globalSyncCommands(player),
|
|
317
344
|
];
|
|
318
345
|
}
|
|
319
346
|
|
|
320
347
|
return [
|
|
321
348
|
`atris task show ${ref}`,
|
|
322
349
|
'atris xp card --local',
|
|
350
|
+
...globalSyncCommands(player),
|
|
323
351
|
];
|
|
324
352
|
}
|
|
325
353
|
|
|
326
354
|
function modeState(args = []) {
|
|
327
355
|
const taskDb = require('../lib/task-db');
|
|
328
356
|
const workspaceArg = flag(args, '--workspace') || flag(args, '--root') || process.cwd();
|
|
329
|
-
const workspaceRoot = taskDb
|
|
357
|
+
const workspaceRoot = playWorkspaceRoot(taskDb, workspaceArg);
|
|
330
358
|
const db = taskDb.open();
|
|
331
359
|
const rows = taskDb.listTasks(db, {
|
|
332
360
|
workspaceRoot,
|
|
@@ -367,6 +395,8 @@ function modeState(args = []) {
|
|
|
367
395
|
prompt: latestMessage(events),
|
|
368
396
|
} : null,
|
|
369
397
|
xp_rule: 'AgentXP lands only after proof is ready and a human accepts the task.',
|
|
398
|
+
global_sync_rule: 'Run atris login once before syncing to the hosted AgentXP leaderboard.',
|
|
399
|
+
leaderboard_url: AGENTXP_LEADERBOARD_URL,
|
|
370
400
|
next_commands: commandList,
|
|
371
401
|
};
|
|
372
402
|
}
|
|
@@ -399,6 +429,8 @@ function render(state) {
|
|
|
399
429
|
console.log('');
|
|
400
430
|
console.log('Win condition: real artifact + verifier + human accept.');
|
|
401
431
|
console.log('XP rule: no proof, no AgentXP; accept/revise stays human-gated.');
|
|
432
|
+
console.log('Global sync: run atris login once before hosted leaderboard sync.');
|
|
433
|
+
console.log(`Leaderboard: ${state.leaderboard_url}`);
|
|
402
434
|
console.log('');
|
|
403
435
|
console.log('Next commands:');
|
|
404
436
|
for (const command of state.next_commands) console.log(`- ${command}`);
|
package/commands/sync.js
CHANGED
|
@@ -48,6 +48,10 @@ function _substituteParams(content, params) {
|
|
|
48
48
|
.replace(/\{\{workspace_template\}\}/g, params.workspace_template || 'business');
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function _templateTargetRelPath(relPath) {
|
|
52
|
+
return relPath === 'persona.md' ? 'PERSONA.md' : relPath;
|
|
53
|
+
}
|
|
54
|
+
|
|
51
55
|
/**
|
|
52
56
|
* Sync the canonical skill set from atris-cli/atris/skills/* into a
|
|
53
57
|
* workspace's atris/skills/* (plus ensure .claude/skills/ symlinks).
|
|
@@ -212,14 +216,15 @@ function syncWorkspaceTemplate(targetRoot, bizMeta, options = {}) {
|
|
|
212
216
|
const addedList = [], updatedList = [], preservedList = [];
|
|
213
217
|
|
|
214
218
|
for (const relPath of templateFiles) {
|
|
219
|
+
const targetRelPath = _templateTargetRelPath(relPath);
|
|
215
220
|
const templatePath = path.join(template.dir, relPath);
|
|
216
|
-
const targetPath = path.join(targetAtrisDir,
|
|
221
|
+
const targetPath = path.join(targetAtrisDir, targetRelPath);
|
|
217
222
|
let templateContent;
|
|
218
223
|
try { templateContent = fs.readFileSync(templatePath, 'utf-8'); } catch { continue; }
|
|
219
224
|
const finalContent = _substituteParams(templateContent, params);
|
|
220
225
|
|
|
221
226
|
if (!fs.existsSync(targetPath)) {
|
|
222
|
-
addedList.push(
|
|
227
|
+
addedList.push(targetRelPath); added++;
|
|
223
228
|
if (!dryRun) {
|
|
224
229
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
225
230
|
fs.writeFileSync(targetPath, finalContent);
|
|
@@ -229,10 +234,10 @@ function syncWorkspaceTemplate(targetRoot, bizMeta, options = {}) {
|
|
|
229
234
|
if (existing === finalContent) {
|
|
230
235
|
skipped++;
|
|
231
236
|
} else if (force) {
|
|
232
|
-
updatedList.push(
|
|
237
|
+
updatedList.push(targetRelPath); updated++;
|
|
233
238
|
if (!dryRun) fs.writeFileSync(targetPath, finalContent);
|
|
234
239
|
} else {
|
|
235
|
-
preservedList.push(
|
|
240
|
+
preservedList.push(targetRelPath); preserved++;
|
|
236
241
|
}
|
|
237
242
|
}
|
|
238
243
|
}
|
package/commands/xp.js
CHANGED
|
@@ -17,6 +17,7 @@ const CAREER_XP_SESSIONS_DIR = path.join('.atris', 'state', 'career_xp_sessions'
|
|
|
17
17
|
const TASK_PROJECTION_FILE = path.join('.atris', 'state', 'tasks.projection.json');
|
|
18
18
|
const CODEX_STATE_FILE = path.join(os.homedir(), '.codex', 'state_5.sqlite');
|
|
19
19
|
const AGENT_XP_LABEL = 'AgentXP';
|
|
20
|
+
const AGENTXP_LEADERBOARD_URL = 'https://api.atris.ai/api/agentxp/leaderboard';
|
|
20
21
|
const LEVEL_XP = 1000;
|
|
21
22
|
const RECEIPT_CHAIN_VERSION = 'atris.career_xp_receipt_chain.v1';
|
|
22
23
|
const XP_STATE_FILES = new Set([
|
|
@@ -37,14 +38,16 @@ const SEARCH_EXCLUDED_DIRS = new Set([
|
|
|
37
38
|
const DEFAULT_SEARCH_DEPTH = 6;
|
|
38
39
|
|
|
39
40
|
function showHelp() {
|
|
40
|
-
console.log('Usage: atris xp [card|status|collect|session] [--json] [--workspace <path>] [--all] [--root <path>]');
|
|
41
|
+
console.log('Usage: atris xp [card|status|collect|session|sync] [--json] [--workspace <path>] [--all] [--root <path>]');
|
|
41
42
|
console.log(' atris xp session [--since today|tonight|YYYY-MM-DD] [--until <time>] [--mission <text>] [--thread <id>] [--no-write]');
|
|
43
|
+
console.log(' atris xp sync [--as <player>] [--local|--all] [--token <token>|logged-in] [--dry-run] [--json]');
|
|
42
44
|
console.log(' atris xp [--json] [--local] [--workspace <path>] [--operator <name>]');
|
|
43
45
|
console.log('');
|
|
44
46
|
console.log('Show your AgentXP graph for the active Atris account.');
|
|
45
47
|
console.log('Use status to show account-level AgentXP across verified local ledgers.');
|
|
46
48
|
console.log('Use collect or status --local to project accepted task proof in the current workspace.');
|
|
47
49
|
console.log('Use session to encapsulate the current work window into a local XP capsule.');
|
|
50
|
+
console.log('Use sync to upload a path-private AgentXP packet to the hosted leaderboard.');
|
|
48
51
|
console.log('Use status --all to explicitly aggregate verified local XP ledgers across workspaces.');
|
|
49
52
|
console.log('Use --local to render from proof receipts in the current workspace.');
|
|
50
53
|
}
|
|
@@ -187,6 +190,14 @@ function readFlag(args, name, fallback = null) {
|
|
|
187
190
|
return fallback;
|
|
188
191
|
}
|
|
189
192
|
|
|
193
|
+
function readFirstFlag(args, names, fallback = null) {
|
|
194
|
+
for (const name of names) {
|
|
195
|
+
const value = readFlag(args, name, null);
|
|
196
|
+
if (value !== null && value !== undefined && value !== '') return value;
|
|
197
|
+
}
|
|
198
|
+
return fallback;
|
|
199
|
+
}
|
|
200
|
+
|
|
190
201
|
function readFlagValues(args, names) {
|
|
191
202
|
const wanted = Array.isArray(names) ? names : [names];
|
|
192
203
|
const values = [];
|
|
@@ -312,6 +323,15 @@ function hashPayload(value) {
|
|
|
312
323
|
return sha256(canonicalJson(value));
|
|
313
324
|
}
|
|
314
325
|
|
|
326
|
+
function slugify(value) {
|
|
327
|
+
return String(value || '')
|
|
328
|
+
.trim()
|
|
329
|
+
.toLowerCase()
|
|
330
|
+
.replace(/@.*$/, '')
|
|
331
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
332
|
+
.replace(/^-+|-+$/g, '');
|
|
333
|
+
}
|
|
334
|
+
|
|
315
335
|
function readJsonFile(filePath, fallback = null) {
|
|
316
336
|
if (!fs.existsSync(filePath)) return fallback;
|
|
317
337
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
@@ -1418,7 +1438,191 @@ function loadLocalPayload(args) {
|
|
|
1418
1438
|
return normalizeLocalScore(JSON.parse(result.stdout), workspace);
|
|
1419
1439
|
}
|
|
1420
1440
|
|
|
1441
|
+
function publicAgentXp(value) {
|
|
1442
|
+
return Math.max(0, Math.min(99, asNumber(value)));
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
function verifiedProjection(projection) {
|
|
1446
|
+
return projection?.integrity_status === 'verified'
|
|
1447
|
+
&& projection?.integrity?.status === 'verified';
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
function projectionWorkspaceSummaries(projection) {
|
|
1451
|
+
if (Array.isArray(projection?.workspaces)) {
|
|
1452
|
+
return projection.workspaces.map(workspace => ({
|
|
1453
|
+
name: workspace.name || workspaceName(workspace.workspace_root || 'workspace'),
|
|
1454
|
+
workspace_root_hash: workspace.workspace_root ? sha256(path.resolve(workspace.workspace_root)) : null,
|
|
1455
|
+
included: Boolean(workspace.included),
|
|
1456
|
+
agent_xp: asNumber(workspace.total_xp),
|
|
1457
|
+
receipts_count: asNumber(workspace.receipts_count),
|
|
1458
|
+
integrity_status: workspace.integrity_status || 'unknown',
|
|
1459
|
+
}));
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
const workspaceRoot = projection?.workspace_root || path.resolve(process.cwd());
|
|
1463
|
+
return [{
|
|
1464
|
+
name: workspaceName(workspaceRoot),
|
|
1465
|
+
workspace_root_hash: sha256(path.resolve(workspaceRoot)),
|
|
1466
|
+
included: verifiedProjection(projection),
|
|
1467
|
+
agent_xp: asNumber(projection?.total_agent_xp ?? projection?.total_xp),
|
|
1468
|
+
receipts_count: asNumber(projection?.receipts_count),
|
|
1469
|
+
integrity_status: projection?.integrity_status || projection?.integrity?.status || 'unknown',
|
|
1470
|
+
}];
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function syncPlayer(args, projection) {
|
|
1474
|
+
const explicit = readFirstFlag(args, ['--as', '--player', '--user', '--operator'], null);
|
|
1475
|
+
return slugify(
|
|
1476
|
+
explicit
|
|
1477
|
+
|| process.env.ATRIS_PLAYER
|
|
1478
|
+
|| process.env.ATRIS_USERNAME
|
|
1479
|
+
|| process.env.ATRIS_PROFILE
|
|
1480
|
+
|| process.env.USER
|
|
1481
|
+
|| os.userInfo().username
|
|
1482
|
+
|| projection?.operator
|
|
1483
|
+
|| 'player'
|
|
1484
|
+
) || 'player';
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function buildAgentXpSyncPacket(args = []) {
|
|
1488
|
+
const localMode = hasFlag(args, '--local') || hasFlag(args, '--workspace') || hasFlag(args, '--operator');
|
|
1489
|
+
const projectionArgs = args.filter(arg => !['--dry-run', '--no-post', '--packet'].includes(arg));
|
|
1490
|
+
const projection = hasFlag(args, '--all') || !localMode
|
|
1491
|
+
? collectAllLocalXpProjection(projectionArgs)
|
|
1492
|
+
: collectLocalXpProjection(projectionArgs);
|
|
1493
|
+
const player = syncPlayer(args, projection);
|
|
1494
|
+
const workspaces = projectionWorkspaceSummaries(projection);
|
|
1495
|
+
const totalXp = asNumber(projection.total_agent_xp ?? projection.agent_xp ?? projection.total_xp ?? projection.career_xp);
|
|
1496
|
+
const receiptsCount = asNumber(projection.receipts_count);
|
|
1497
|
+
const eligible = verifiedProjection(projection) && receiptsCount > 0 && totalXp > 0;
|
|
1498
|
+
const publicXp = publicAgentXp(totalXp);
|
|
1499
|
+
const entry = {
|
|
1500
|
+
user_id: player,
|
|
1501
|
+
username: player,
|
|
1502
|
+
agent_xp: publicXp,
|
|
1503
|
+
career_xp: publicXp,
|
|
1504
|
+
current_form: publicXp,
|
|
1505
|
+
ovr: publicXp,
|
|
1506
|
+
level: Math.max(1, asNumber(projection.level, 1)),
|
|
1507
|
+
verified_receipts: receiptsCount,
|
|
1508
|
+
reviewed_tasks: receiptsCount,
|
|
1509
|
+
recent_verified_receipts: receiptsCount,
|
|
1510
|
+
recent_reviewed_tasks: receiptsCount,
|
|
1511
|
+
leaderboard_eligible: eligible,
|
|
1512
|
+
integrity_status: eligible ? 'trusted' : (projection.integrity_status || projection.integrity?.status || 'unknown'),
|
|
1513
|
+
lock_reason: eligible ? null : 'not_enough_trusted_proof',
|
|
1514
|
+
public_adjustment: null,
|
|
1515
|
+
next_move: eligible ? 'Play the next proof-backed AgentXP mission.' : 'Complete one proof-backed AgentXP rep.',
|
|
1516
|
+
};
|
|
1517
|
+
const packet = {
|
|
1518
|
+
schema: 'atris.agentxp_sync_packet.v1',
|
|
1519
|
+
generated_at: new Date().toISOString(),
|
|
1520
|
+
workspace_root_hash: sha256(workspaces.map(item => item.workspace_root_hash || item.name).sort().join(':')),
|
|
1521
|
+
computer: projection.workspace_name || workspaces[0]?.name || 'local',
|
|
1522
|
+
operator: player,
|
|
1523
|
+
privacy: {
|
|
1524
|
+
raw_proofs_included: false,
|
|
1525
|
+
raw_receipts_included: false,
|
|
1526
|
+
contains_absolute_workspace_root: false,
|
|
1527
|
+
public_user_board_omits_lifetime_xp: true,
|
|
1528
|
+
},
|
|
1529
|
+
sync_contract: {
|
|
1530
|
+
anti_bs_rule: 'No accepted proof-backed task episodes means no AgentXP leaderboard movement.',
|
|
1531
|
+
trust_rule: 'Only verified local ledgers are uploaded; raw proof and paths stay local.',
|
|
1532
|
+
},
|
|
1533
|
+
local_evidence: {
|
|
1534
|
+
workspaces,
|
|
1535
|
+
verified_workspace_count: asNumber(projection.verified_workspace_count, verifiedProjection(projection) ? 1 : 0),
|
|
1536
|
+
receipts_count: receiptsCount,
|
|
1537
|
+
integrity_status: projection.integrity?.status || projection.integrity_status || 'unknown',
|
|
1538
|
+
ledger_head_hash: projection.integrity?.head_hash || null,
|
|
1539
|
+
},
|
|
1540
|
+
user_leaderboard: {
|
|
1541
|
+
schema: 'atris.agentxp_user_leaderboard.v1',
|
|
1542
|
+
score_name: AGENT_XP_LABEL,
|
|
1543
|
+
entries: [entry],
|
|
1544
|
+
},
|
|
1545
|
+
};
|
|
1546
|
+
packet.packet_hash = hashPayload(packet);
|
|
1547
|
+
return {
|
|
1548
|
+
schema: 'atris.agentxp_sync_preview.v1',
|
|
1549
|
+
generated_at: new Date().toISOString(),
|
|
1550
|
+
dry_run: true,
|
|
1551
|
+
player,
|
|
1552
|
+
entry,
|
|
1553
|
+
packet,
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
async function syncAgentXp(args = []) {
|
|
1558
|
+
const preview = buildAgentXpSyncPacket(args);
|
|
1559
|
+
const dryRun = hasFlag(args, '--dry-run') || hasFlag(args, '--no-post') || hasFlag(args, '--packet');
|
|
1560
|
+
if (dryRun) return preview;
|
|
1561
|
+
|
|
1562
|
+
const token = readFlag(args, '--token', process.env.ATRIS_AGENTXP_SYNC_TOKEN || process.env.AGENTXP_SYNC_TOKEN || '');
|
|
1563
|
+
const options = {
|
|
1564
|
+
method: 'POST',
|
|
1565
|
+
body: preview.packet,
|
|
1566
|
+
retries: 0,
|
|
1567
|
+
};
|
|
1568
|
+
if (token) {
|
|
1569
|
+
options.headers = { 'X-AgentXP-Sync-Token': token };
|
|
1570
|
+
} else {
|
|
1571
|
+
const ensured = await ensureValidCredentials(apiRequestJson);
|
|
1572
|
+
if (ensured.error) {
|
|
1573
|
+
throw new Error(`Missing sync auth. Run atris login, or set ATRIS_AGENTXP_SYNC_TOKEN${ensured.detail ? ` (${ensured.detail})` : ''}.`);
|
|
1574
|
+
}
|
|
1575
|
+
options.token = ensured.credentials.token;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
const response = await apiRequestJson('/agentxp/leaderboard/sync', options);
|
|
1579
|
+
if (!response.ok) {
|
|
1580
|
+
throw new Error(`AgentXP sync failed: ${response.error || response.status}`);
|
|
1581
|
+
}
|
|
1582
|
+
return {
|
|
1583
|
+
schema: 'atris.agentxp_sync_result.v1',
|
|
1584
|
+
generated_at: new Date().toISOString(),
|
|
1585
|
+
dry_run: false,
|
|
1586
|
+
player: preview.player,
|
|
1587
|
+
entry: preview.entry,
|
|
1588
|
+
packet_hash: preview.packet.packet_hash,
|
|
1589
|
+
server: response.data || {},
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
function renderSync(payload) {
|
|
1594
|
+
const entry = payload.entry || {};
|
|
1595
|
+
console.log('AgentXP Sync');
|
|
1596
|
+
console.log(`Player ${payload.player || entry.username || 'player'} | AgentXP ${formatNumber(entry.agent_xp)} | receipts ${formatNumber(entry.verified_receipts)}`);
|
|
1597
|
+
if (payload.dry_run) {
|
|
1598
|
+
console.log(`Packet ${payload.packet?.packet_hash || 'unhashed'} ready; no network upload ran.`);
|
|
1599
|
+
console.log('Run with ATRIS_AGENTXP_SYNC_TOKEN set to publish to the hosted leaderboard.');
|
|
1600
|
+
console.log(`Leaderboard: ${AGENTXP_LEADERBOARD_URL}`);
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
const server = payload.server || {};
|
|
1604
|
+
console.log(`Uploaded: accepted ${formatNumber(server.accepted_count)} | stored ${formatNumber(server.stored_count)}`);
|
|
1605
|
+
const acceptedUsernames = Array.isArray(server.accepted_usernames)
|
|
1606
|
+
? server.accepted_usernames.map(value => String(value || '').trim()).filter(Boolean)
|
|
1607
|
+
: [];
|
|
1608
|
+
if (acceptedUsernames.length) {
|
|
1609
|
+
console.log(`Public identity: ${acceptedUsernames.join(', ')}`);
|
|
1610
|
+
}
|
|
1611
|
+
const player = String(payload.player || entry.username || '').trim().toLowerCase();
|
|
1612
|
+
const accepted = acceptedUsernames.map(value => value.toLowerCase());
|
|
1613
|
+
if (server.mapped_to_authenticated_user === true && player && !accepted.includes(player)) {
|
|
1614
|
+
console.log('Login auth mapped this sync to your Atris account.');
|
|
1615
|
+
}
|
|
1616
|
+
console.log(`Packet ${payload.packet_hash}`);
|
|
1617
|
+
console.log(`Leaderboard: ${AGENTXP_LEADERBOARD_URL}`);
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1421
1620
|
function render(payload) {
|
|
1621
|
+
if (payload.schema === 'atris.agentxp_sync_preview.v1' || payload.schema === 'atris.agentxp_sync_result.v1') {
|
|
1622
|
+
renderSync(payload);
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1422
1626
|
if (payload.schema === 'atris.career_xp_session_capsule.v1') {
|
|
1423
1627
|
const xp = payload.xp || {};
|
|
1424
1628
|
const tasks = payload.tasks || {};
|
|
@@ -1508,6 +1712,23 @@ async function xpCommand(...args) {
|
|
|
1508
1712
|
}
|
|
1509
1713
|
|
|
1510
1714
|
const subcommand = args[0] && !args[0].startsWith('--') ? args[0] : null;
|
|
1715
|
+
if (subcommand === 'sync') {
|
|
1716
|
+
const commandArgs = args.slice(1);
|
|
1717
|
+
let payload;
|
|
1718
|
+
try {
|
|
1719
|
+
payload = await syncAgentXp(commandArgs);
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
console.error(`Failed to sync AgentXP: ${error.message}`);
|
|
1722
|
+
process.exit(1);
|
|
1723
|
+
}
|
|
1724
|
+
if (args.includes('--json')) {
|
|
1725
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
render(payload);
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1511
1732
|
if (subcommand === 'session') {
|
|
1512
1733
|
const commandArgs = args.slice(1);
|
|
1513
1734
|
let payload;
|
|
@@ -1603,6 +1824,8 @@ module.exports = {
|
|
|
1603
1824
|
buildCareerXpSessionCapsule,
|
|
1604
1825
|
collectAllLocalXpProjection,
|
|
1605
1826
|
collectLocalXpProjection,
|
|
1827
|
+
buildAgentXpSyncPacket,
|
|
1828
|
+
syncAgentXp,
|
|
1606
1829
|
receiptFromTaskEpisode,
|
|
1607
1830
|
render,
|
|
1608
1831
|
};
|