atris 3.15.36 → 3.15.38
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 +190 -8
- package/commands/gm.js +2 -2
- package/commands/mission.js +14 -8
- package/commands/play.js +2 -2
- package/commands/pull.js +11 -7
- package/commands/task.js +46 -23
- package/commands/xp.js +48 -12
- package/lib/todo-fallback.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ AgentXP is the proof-backed game loop for getting better with agents. Start it
|
|
|
106
106
|
inside any project folder:
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
|
-
npm exec --yes --package
|
|
109
|
+
npm exec --yes --package atris@latest -- atris play
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
The first run creates a local starter mission if one does not exist. The loop is:
|
|
@@ -118,13 +118,13 @@ start -> proof -> accept -> login -> sync
|
|
|
118
118
|
The player path:
|
|
119
119
|
|
|
120
120
|
```bash
|
|
121
|
-
atris play
|
|
121
|
+
atris play
|
|
122
122
|
atris task claim <mission-ref> --as game-manager
|
|
123
123
|
atris task ready <mission-ref> --as game-manager --proof "<artifact path + verifier result>"
|
|
124
124
|
atris task accept <mission-ref> --as justin --proof "<human review>"
|
|
125
125
|
atris xp card --local
|
|
126
126
|
atris login
|
|
127
|
-
atris xp sync --local
|
|
127
|
+
atris xp sync --local
|
|
128
128
|
```
|
|
129
129
|
|
|
130
130
|
The manager path:
|
package/ax
CHANGED
|
@@ -107,6 +107,54 @@ function formatUsage() {
|
|
|
107
107
|
].join('\n');
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
function timestampForFile(date = new Date()) {
|
|
111
|
+
return date.toISOString().replace(/[:.]/g, '-');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function stripAnsi(value) {
|
|
115
|
+
return String(value || '').replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createRunLogger({ cwd = process.cwd(), mode = 'pro', kind = 'play', output = process.stdout } = {}) {
|
|
119
|
+
if (process.env.AX_AUTO_LOG === '0') return null;
|
|
120
|
+
if (output !== process.stdout && output !== process.stderr && !output.isTTY) return null;
|
|
121
|
+
|
|
122
|
+
const baseDir = fs.existsSync(path.join(cwd, 'atris'))
|
|
123
|
+
? path.join(cwd, 'atris', 'runs')
|
|
124
|
+
: path.join(os.homedir(), '.atris', 'runs');
|
|
125
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
126
|
+
const logPath = path.join(baseDir, `ax-${kind}-${timestampForFile()}.log`);
|
|
127
|
+
fs.appendFileSync(logPath, [
|
|
128
|
+
`command: ${process.argv.join(' ')}`,
|
|
129
|
+
`cwd: ${cwd}`,
|
|
130
|
+
`mode: ${mode}`,
|
|
131
|
+
`started_at: ${new Date().toISOString()}`,
|
|
132
|
+
'',
|
|
133
|
+
].join('\n'));
|
|
134
|
+
|
|
135
|
+
const writeLog = (chunk) => {
|
|
136
|
+
fs.appendFileSync(logPath, stripAnsi(chunk));
|
|
137
|
+
};
|
|
138
|
+
const teeOutput = {
|
|
139
|
+
isTTY: Boolean(output && output.isTTY),
|
|
140
|
+
write(chunk) {
|
|
141
|
+
const text = String(chunk || '');
|
|
142
|
+
if (output && typeof output.write === 'function') output.write(text);
|
|
143
|
+
writeLog(text);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
path: logPath,
|
|
150
|
+
output: teeOutput,
|
|
151
|
+
write: writeLog,
|
|
152
|
+
close(exitCode = 0) {
|
|
153
|
+
writeLog(`\nexit_code: ${exitCode}\nfinished_at: ${new Date().toISOString()}\n`);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
110
158
|
function backendBaseUrl() {
|
|
111
159
|
return (process.env.AX_BACKEND_URL
|
|
112
160
|
|| process.env.OBELISK_LOCAL_ATRIS2_BACKEND_URL
|
|
@@ -339,9 +387,16 @@ function workspaceIntent(message) {
|
|
|
339
387
|
return /\b(files?|folders?|repo|workspace|project|directory|tree|read|open|inspect|search|grep|find|locate|where|edit|write|change|modify|patch|fix|test|tests?|build|src|source|code|diff|git|backend|frontend|atris task|atris xp|xp game|career xp|agentxp|todo|map)\b/i.test(message || '');
|
|
340
388
|
}
|
|
341
389
|
|
|
390
|
+
function githubWorkspaceIntent(message) {
|
|
391
|
+
const text = String(message || '');
|
|
392
|
+
if (!/\bgithub\b/i.test(text)) return false;
|
|
393
|
+
return /\b(push|commit|commits?|branch|branches|checkout|merge|rebase|tag|release|pr|pull request|pull-request|repo change|code change|small change)\b/i.test(text);
|
|
394
|
+
}
|
|
395
|
+
|
|
342
396
|
function resolveRoute(message, options = {}) {
|
|
343
397
|
if (options.route === 'local' || options.forceLocal) return 'local';
|
|
344
398
|
if (options.route === 'cloud' || options.forceCloud) return 'cloud';
|
|
399
|
+
if (githubWorkspaceIntent(message)) return 'local';
|
|
345
400
|
if (mentionsConnector(message) && !workspaceIntent(message)) return 'cloud';
|
|
346
401
|
return 'local';
|
|
347
402
|
}
|
|
@@ -370,6 +425,112 @@ function paint(text, codes, options = {}) {
|
|
|
370
425
|
return `${codes.join('')}${text}${ANSI.reset}`;
|
|
371
426
|
}
|
|
372
427
|
|
|
428
|
+
function renderTerminalMarkdown(text, options = {}) {
|
|
429
|
+
let rendered = String(text || '');
|
|
430
|
+
const codeSpans = [];
|
|
431
|
+
rendered = rendered.replace(/`([^`\n]+)`/g, (_, code) => {
|
|
432
|
+
const token = `\u0000CODE${codeSpans.length}\u0000`;
|
|
433
|
+
codeSpans.push(code);
|
|
434
|
+
return token;
|
|
435
|
+
});
|
|
436
|
+
rendered = rendered.replace(/^#{1,6}\s+(.+)$/gm, (_, title) => paint(title, [ANSI.bold], options));
|
|
437
|
+
rendered = rendered.replace(/\*\*([^*\n]+)\*\*/g, (_, value) => paint(value, [ANSI.bold], options));
|
|
438
|
+
rendered = rendered.replace(/__([^_\n]+)__/g, (_, value) => paint(value, [ANSI.bold], options));
|
|
439
|
+
rendered = rendered.replace(/\u0000CODE(\d+)\u0000/g, (_, index) => paint(codeSpans[Number(index)] || '', [ANSI.accent], options));
|
|
440
|
+
return rendered;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function resetMarkdownState(state) {
|
|
444
|
+
state.markdownMode = 'normal';
|
|
445
|
+
state.markdownBuffer = '';
|
|
446
|
+
state.markdownCarry = '';
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function ensureMarkdownState(state) {
|
|
450
|
+
if (!state.markdownMode) state.markdownMode = 'normal';
|
|
451
|
+
if (typeof state.markdownBuffer !== 'string') state.markdownBuffer = '';
|
|
452
|
+
if (typeof state.markdownCarry !== 'string') state.markdownCarry = '';
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function renderStreamingMarkdown(state, text, options = {}) {
|
|
456
|
+
ensureMarkdownState(state);
|
|
457
|
+
const input = `${state.markdownCarry || ''}${String(text || '')}`;
|
|
458
|
+
state.markdownCarry = '';
|
|
459
|
+
let out = '';
|
|
460
|
+
|
|
461
|
+
for (let i = 0; i < input.length;) {
|
|
462
|
+
const char = input[i];
|
|
463
|
+
const next = input[i + 1];
|
|
464
|
+
|
|
465
|
+
if (state.markdownMode === 'normal') {
|
|
466
|
+
if (char === '*' && next === undefined) {
|
|
467
|
+
state.markdownCarry = '*';
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
if (char === '*' && next === '*') {
|
|
471
|
+
state.markdownMode = 'bold';
|
|
472
|
+
state.markdownBuffer = '';
|
|
473
|
+
i += 2;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (char === '`') {
|
|
477
|
+
state.markdownMode = 'code';
|
|
478
|
+
state.markdownBuffer = '';
|
|
479
|
+
i += 1;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
out += char;
|
|
483
|
+
i += 1;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (state.markdownMode === 'bold') {
|
|
488
|
+
if (char === '*' && next === undefined) {
|
|
489
|
+
state.markdownCarry = '*';
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
if (char === '*' && next === '*') {
|
|
493
|
+
out += paint(state.markdownBuffer, [ANSI.bold], options);
|
|
494
|
+
state.markdownMode = 'normal';
|
|
495
|
+
state.markdownBuffer = '';
|
|
496
|
+
i += 2;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
state.markdownBuffer += char;
|
|
500
|
+
i += 1;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (state.markdownMode === 'code') {
|
|
505
|
+
if (char === '`') {
|
|
506
|
+
out += paint(state.markdownBuffer, [ANSI.accent], options);
|
|
507
|
+
state.markdownMode = 'normal';
|
|
508
|
+
state.markdownBuffer = '';
|
|
509
|
+
i += 1;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
state.markdownBuffer += char;
|
|
513
|
+
i += 1;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return out;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function flushStreamingMarkdown(state, output) {
|
|
522
|
+
ensureMarkdownState(state);
|
|
523
|
+
let out = state.markdownCarry || '';
|
|
524
|
+
if (state.markdownMode === 'bold' && state.markdownBuffer) {
|
|
525
|
+
out += paint(state.markdownBuffer, [ANSI.bold], output);
|
|
526
|
+
} else if (state.markdownMode === 'code' && state.markdownBuffer) {
|
|
527
|
+
out += paint(state.markdownBuffer, [ANSI.accent], output);
|
|
528
|
+
}
|
|
529
|
+
resetMarkdownState(state);
|
|
530
|
+
if (out) output.write(out);
|
|
531
|
+
return out;
|
|
532
|
+
}
|
|
533
|
+
|
|
373
534
|
function truncateMiddle(value, limit = 120) {
|
|
374
535
|
const text = String(value || '');
|
|
375
536
|
if (text.length <= limit) return text;
|
|
@@ -523,6 +684,7 @@ function clearRetriedText(state) {
|
|
|
523
684
|
state.wroteText = false;
|
|
524
685
|
state.lastChar = '\n';
|
|
525
686
|
state.inAuxBlock = false;
|
|
687
|
+
resetMarkdownState(state);
|
|
526
688
|
}
|
|
527
689
|
|
|
528
690
|
function stopProgress(state) {
|
|
@@ -532,13 +694,20 @@ function stopProgress(state) {
|
|
|
532
694
|
}
|
|
533
695
|
|
|
534
696
|
function flushPendingText(state, output) {
|
|
535
|
-
|
|
697
|
+
const hasMarkdownRemainder = output && output.isTTY && (
|
|
698
|
+
state.markdownCarry
|
|
699
|
+
|| state.markdownBuffer
|
|
700
|
+
|| (state.markdownMode && state.markdownMode !== 'normal')
|
|
701
|
+
);
|
|
702
|
+
if (!state.pendingText && !hasMarkdownRemainder) return;
|
|
536
703
|
stopProgress(state);
|
|
537
704
|
if (state.inAuxBlock && state.lastChar === '\n') output.write('\n');
|
|
538
|
-
output.write(state.pendingText);
|
|
705
|
+
if (state.pendingText) output.write(output && output.isTTY ? renderTerminalMarkdown(state.pendingText, output) : state.pendingText);
|
|
706
|
+
const flushedMarkdown = hasMarkdownRemainder ? flushStreamingMarkdown(state, output) : '';
|
|
539
707
|
state.wroteText = true;
|
|
540
708
|
state.wroteActivity = true;
|
|
541
|
-
|
|
709
|
+
const written = `${state.pendingText || ''}${flushedMarkdown || ''}`;
|
|
710
|
+
state.lastChar = written.slice(-1);
|
|
542
711
|
state.pendingText = '';
|
|
543
712
|
state.inAuxBlock = false;
|
|
544
713
|
}
|
|
@@ -547,10 +716,11 @@ function writeStreamingText(state, output, content) {
|
|
|
547
716
|
if (!content) return;
|
|
548
717
|
stopProgress(state);
|
|
549
718
|
if (state.inAuxBlock && state.lastChar === '\n') output.write('\n');
|
|
550
|
-
|
|
719
|
+
const rendered = renderStreamingMarkdown(state, content, output);
|
|
720
|
+
if (rendered) output.write(rendered);
|
|
551
721
|
state.wroteText = true;
|
|
552
722
|
state.wroteActivity = true;
|
|
553
|
-
state.lastChar = String(content).slice(-1);
|
|
723
|
+
state.lastChar = String(rendered || content).slice(-1);
|
|
554
724
|
state.inAuxBlock = false;
|
|
555
725
|
}
|
|
556
726
|
|
|
@@ -691,7 +861,10 @@ async function postTurn(message, options = {}) {
|
|
|
691
861
|
durationMs: 0,
|
|
692
862
|
lastChar: '\n',
|
|
693
863
|
progress: null,
|
|
694
|
-
inAuxBlock: false
|
|
864
|
+
inAuxBlock: false,
|
|
865
|
+
markdownMode: 'normal',
|
|
866
|
+
markdownBuffer: '',
|
|
867
|
+
markdownCarry: ''
|
|
695
868
|
};
|
|
696
869
|
|
|
697
870
|
return new Promise((resolve, reject) => {
|
|
@@ -789,16 +962,20 @@ async function chat(options = {}) {
|
|
|
789
962
|
const mode = options.mode === 'fast' ? 'fast' : 'pro';
|
|
790
963
|
const cwd = options.cwd || process.cwd();
|
|
791
964
|
const input = options.input || process.stdin;
|
|
792
|
-
const
|
|
965
|
+
const baseOutput = options.output || process.stdout;
|
|
966
|
+
const logger = createRunLogger({ cwd, mode, kind: 'play', output: baseOutput });
|
|
967
|
+
const output = logger ? logger.output : baseOutput;
|
|
793
968
|
const history = [];
|
|
794
969
|
|
|
795
970
|
output.write(`${formatHeader({ mode, cwd, chat: true })}\n\n`);
|
|
971
|
+
if (logger) output.write(`${formatAuxRow('log', formatPathSubject(logger.path, output), output)}\n\n`);
|
|
796
972
|
|
|
797
973
|
const runLine = async (line) => {
|
|
798
974
|
const trimmed = String(line || '').trim();
|
|
799
975
|
if (!trimmed) return false;
|
|
800
976
|
if (EXIT_WORDS.has(trimmed.toLowerCase())) return true;
|
|
801
977
|
|
|
978
|
+
if (logger) logger.write(`${formatPrompt(mode)}${trimmed}\n`);
|
|
802
979
|
output.write('\n');
|
|
803
980
|
const result = await postTurn(trimmed, { mode, cwd, history, output });
|
|
804
981
|
if (result.output && !result.output.endsWith('\n')) output.write('\n');
|
|
@@ -813,10 +990,11 @@ async function chat(options = {}) {
|
|
|
813
990
|
for await (const line of rl) {
|
|
814
991
|
if (await runLine(line)) break;
|
|
815
992
|
}
|
|
993
|
+
if (logger) logger.close(0);
|
|
816
994
|
return;
|
|
817
995
|
}
|
|
818
996
|
|
|
819
|
-
const rl = readline.createInterface({ input, output });
|
|
997
|
+
const rl = readline.createInterface({ input, output: baseOutput });
|
|
820
998
|
const ask = () => new Promise(resolve => rl.question(formatPrompt(mode), resolve));
|
|
821
999
|
while (true) {
|
|
822
1000
|
const line = await ask();
|
|
@@ -825,6 +1003,7 @@ async function chat(options = {}) {
|
|
|
825
1003
|
break;
|
|
826
1004
|
}
|
|
827
1005
|
}
|
|
1006
|
+
if (logger) logger.close(0);
|
|
828
1007
|
}
|
|
829
1008
|
|
|
830
1009
|
function printBackendHint() {
|
|
@@ -1061,6 +1240,7 @@ module.exports = {
|
|
|
1061
1240
|
buildConnectionContext,
|
|
1062
1241
|
buildRunProfile,
|
|
1063
1242
|
chat,
|
|
1243
|
+
createRunLogger,
|
|
1064
1244
|
createProgressReporter,
|
|
1065
1245
|
formatDoneLine,
|
|
1066
1246
|
formatDuration,
|
|
@@ -1076,6 +1256,8 @@ module.exports = {
|
|
|
1076
1256
|
modelForMode,
|
|
1077
1257
|
parseSseBlock,
|
|
1078
1258
|
postTurn,
|
|
1259
|
+
renderStreamingMarkdown,
|
|
1260
|
+
renderTerminalMarkdown,
|
|
1079
1261
|
resolveRoute,
|
|
1080
1262
|
runBenchmark,
|
|
1081
1263
|
summarizeToolInput,
|
package/commands/gm.js
CHANGED
|
@@ -267,8 +267,8 @@ function compactTask(task) {
|
|
|
267
267
|
function globalSyncCommands(player) {
|
|
268
268
|
return [
|
|
269
269
|
'atris login',
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
'atris xp sync --local',
|
|
271
|
+
'atris xp sync --local --token <owner-provided-token>',
|
|
272
272
|
];
|
|
273
273
|
}
|
|
274
274
|
|
package/commands/mission.js
CHANGED
|
@@ -747,9 +747,17 @@ function secondsUntilMissionDue(mission, now = new Date()) {
|
|
|
747
747
|
return Math.max(0, Math.ceil((dueAt - now.getTime()) / 1000));
|
|
748
748
|
}
|
|
749
749
|
|
|
750
|
+
function missionIsRunnable(mission) {
|
|
751
|
+
return mission && !TERMINAL_STATUSES.has(mission.status) && mission.status !== 'paused';
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function missionSortTime(mission) {
|
|
755
|
+
return Date.parse(mission?.updated_at || mission?.created_at || '') || 0;
|
|
756
|
+
}
|
|
757
|
+
|
|
750
758
|
function selectDueMission(root = process.cwd(), now = new Date()) {
|
|
751
759
|
const candidates = listMissions(root)
|
|
752
|
-
.filter(
|
|
760
|
+
.filter(missionIsRunnable)
|
|
753
761
|
.filter((mission) => mission.verifier)
|
|
754
762
|
.filter((mission) => mission.always_on || !missionVerifierPassed(mission))
|
|
755
763
|
.filter((mission) => missionDueAt(mission, now));
|
|
@@ -763,11 +771,8 @@ function selectDueMission(root = process.cwd(), now = new Date()) {
|
|
|
763
771
|
}
|
|
764
772
|
|
|
765
773
|
function selectCodexGoalMission(root = process.cwd(), now = new Date()) {
|
|
766
|
-
const due = selectDueMission(root, now);
|
|
767
|
-
if (due) return { mission: due, reason: 'due' };
|
|
768
|
-
|
|
769
774
|
const candidates = listMissions(root)
|
|
770
|
-
.filter(
|
|
775
|
+
.filter(missionIsRunnable);
|
|
771
776
|
|
|
772
777
|
candidates.sort((a, b) => {
|
|
773
778
|
const aCaller = runnerUsesCallerSession(a.runner) ? 1 : 0;
|
|
@@ -778,14 +783,15 @@ function selectCodexGoalMission(root = process.cwd(), now = new Date()) {
|
|
|
778
783
|
const bVerifier = b.verifier ? 1 : 0;
|
|
779
784
|
if (aVerifier !== bVerifier) return bVerifier - aVerifier;
|
|
780
785
|
|
|
781
|
-
const aTime =
|
|
782
|
-
const bTime =
|
|
786
|
+
const aTime = missionSortTime(a);
|
|
787
|
+
const bTime = missionSortTime(b);
|
|
783
788
|
return bTime - aTime;
|
|
784
789
|
});
|
|
785
790
|
|
|
786
791
|
const mission = candidates[0] || null;
|
|
787
792
|
if (!mission) return null;
|
|
788
|
-
|
|
793
|
+
const due = mission.verifier && missionDueAt(mission, now);
|
|
794
|
+
return { mission, reason: due ? 'due' : 'active' };
|
|
789
795
|
}
|
|
790
796
|
|
|
791
797
|
function codexGoalObjective(mission) {
|
package/commands/play.js
CHANGED
|
@@ -258,8 +258,8 @@ function starterMissionPrompt(player) {
|
|
|
258
258
|
function globalSyncCommands(player) {
|
|
259
259
|
return [
|
|
260
260
|
'atris login',
|
|
261
|
-
|
|
262
|
-
|
|
261
|
+
'atris xp sync --local',
|
|
262
|
+
'atris xp sync --local --token <owner-provided-token>',
|
|
263
263
|
];
|
|
264
264
|
}
|
|
265
265
|
|
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/`);
|
package/commands/task.js
CHANGED
|
@@ -795,6 +795,15 @@ function taskStatusSummary(projection, { history = false } = {}) {
|
|
|
795
795
|
done: tasks.filter(task => taskColumn(task) === 'done'),
|
|
796
796
|
};
|
|
797
797
|
const active = [...columns.do, ...columns.review, ...columns.plan];
|
|
798
|
+
const reviewNeedingAgentAction = columns.review.filter(task => {
|
|
799
|
+
const handoff = reviewHandoffForTask(task);
|
|
800
|
+
return handoff && handoff.next_action === 'agent_review_again';
|
|
801
|
+
});
|
|
802
|
+
const reviewAgentCertified = columns.review.filter(task => {
|
|
803
|
+
const handoff = reviewHandoffForTask(task);
|
|
804
|
+
return handoff && handoff.next_action === 'continue_work';
|
|
805
|
+
}).length;
|
|
806
|
+
const blocked = columns.review.filter(task => taskColumn(task) === 'blocked').length;
|
|
798
807
|
const lastUpdated = tasks.reduce((max, task) => Math.max(max, Number(task.updated_at || 0)), 0);
|
|
799
808
|
const swarloFeed = history ? tasks
|
|
800
809
|
.flatMap(task => (task.events || []).map(event => ({
|
|
@@ -830,14 +839,17 @@ function taskStatusSummary(projection, { history = false } = {}) {
|
|
|
830
839
|
goals: projection.goals || { source_path: null, items: [] },
|
|
831
840
|
counts: {
|
|
832
841
|
total: fullTaskCount,
|
|
833
|
-
active: columns.plan.length + columns.do.length +
|
|
842
|
+
active: columns.plan.length + columns.do.length + reviewNeedingAgentAction.length,
|
|
834
843
|
backlog: columns.backlog.length,
|
|
835
844
|
plan: columns.plan.length,
|
|
836
845
|
do: columns.do.length,
|
|
837
846
|
review: columns.review.length,
|
|
847
|
+
review_blocking: reviewNeedingAgentAction.length,
|
|
848
|
+
review_certified: reviewAgentCertified,
|
|
849
|
+
blocked,
|
|
838
850
|
done: tasks.filter(task => task.status === 'done' || (task.status === 'failed' && taskHasReview(task))).length + hiddenDoneCount,
|
|
839
851
|
},
|
|
840
|
-
current: compactTaskForStatus(columns.do[0] ||
|
|
852
|
+
current: compactTaskForStatus(columns.do[0] || reviewNeedingAgentAction[0] || null),
|
|
841
853
|
next: compactTaskForStatus(columns.plan[0] || null),
|
|
842
854
|
needs_review: columns.review.slice(0, 5).map(compactTaskForStatus),
|
|
843
855
|
streams: (projection.streams || []).slice(0, 8).map(stream => ({
|
|
@@ -1299,25 +1311,6 @@ function cmdNext(args) {
|
|
|
1299
1311
|
const reviewTasks = (reviewProjection.projection.tasks || [])
|
|
1300
1312
|
.map(compactTaskForStatus)
|
|
1301
1313
|
.filter(task => task && task.review && task.review.handoff);
|
|
1302
|
-
const secondReviewTask = reviewTasks.find(task => task.review.handoff.next_action === 'agent_review_again');
|
|
1303
|
-
if (secondReviewTask) {
|
|
1304
|
-
const handoff = secondReviewTask.review.handoff;
|
|
1305
|
-
if (wantsJson(args)) {
|
|
1306
|
-
printJson({
|
|
1307
|
-
ok: true,
|
|
1308
|
-
action: handoff.next_action,
|
|
1309
|
-
task_id: secondReviewTask.id,
|
|
1310
|
-
owner: String(owner),
|
|
1311
|
-
projection_path: reviewProjection.outPath,
|
|
1312
|
-
handoff,
|
|
1313
|
-
review_task: secondReviewTask,
|
|
1314
|
-
});
|
|
1315
|
-
return;
|
|
1316
|
-
}
|
|
1317
|
-
console.log(`${taskRef(secondReviewTask)} needs one more agent review before continuation.`);
|
|
1318
|
-
console.log('Review this task again before claiming new work.');
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
1314
|
const open = taskDb.listTasks(db, {
|
|
1322
1315
|
workspaceRoot: taskDb.workspaceRoot(),
|
|
1323
1316
|
status: 'open',
|
|
@@ -1325,7 +1318,8 @@ function cmdNext(args) {
|
|
|
1325
1318
|
});
|
|
1326
1319
|
if (!open.length) {
|
|
1327
1320
|
const { projection, outPath } = reviewProjection;
|
|
1328
|
-
const reviewTask = reviewTasks.find(task => task.review.handoff.next_action === '
|
|
1321
|
+
const reviewTask = reviewTasks.find(task => task.review.handoff.next_action === 'agent_review_again')
|
|
1322
|
+
|| reviewTasks.find(task => task.review.handoff.next_action === 'continue_work');
|
|
1329
1323
|
if (reviewTask) {
|
|
1330
1324
|
const handoff = reviewTask.review.handoff;
|
|
1331
1325
|
if (wantsJson(args)) {
|
|
@@ -2013,11 +2007,34 @@ function extractTodoSectionMarkdown(content, sectionName) {
|
|
|
2013
2007
|
return match ? match[1].trimEnd() : null;
|
|
2014
2008
|
}
|
|
2015
2009
|
|
|
2010
|
+
function normalizeRenderedTaskRef(value) {
|
|
2011
|
+
return String(value || '').replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
function renderedTaskRefSet(taskDb, rows, refRows) {
|
|
2015
|
+
const byId = new Map();
|
|
2016
|
+
for (const row of [...(Array.isArray(rows) ? rows : []), ...(Array.isArray(refRows) ? refRows : [])]) {
|
|
2017
|
+
if (row && row.id && !byId.has(row.id)) byId.set(row.id, row);
|
|
2018
|
+
}
|
|
2019
|
+
const displayRows = taskDb.withTaskDisplayRefs([...byId.values()]);
|
|
2020
|
+
const refs = new Set();
|
|
2021
|
+
for (const row of displayRows) {
|
|
2022
|
+
for (const value of [row.id, row.display_id, row.legacy_ref]) {
|
|
2023
|
+
const ref = normalizeRenderedTaskRef(value);
|
|
2024
|
+
if (ref) refs.add(ref);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
return refs;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2016
2030
|
function markdownRowsForRender(taskDb, existingTodoPath, rows, refRows) {
|
|
2017
2031
|
if (!existingTodoPath || !fs.existsSync(existingTodoPath)) return [];
|
|
2018
2032
|
const { parseTodoFile } = require('../lib/todo-fallback');
|
|
2033
|
+
const existingTodo = fs.readFileSync(existingTodoPath, 'utf8');
|
|
2034
|
+
const generatedTodo = existingTodo.includes('Regenerated from durable Atris task state');
|
|
2019
2035
|
const parsed = parseTodoFile(existingTodoPath);
|
|
2020
2036
|
const ws = taskDb.workspaceRoot();
|
|
2037
|
+
const existingRefs = renderedTaskRefSet(taskDb, rows, refRows);
|
|
2021
2038
|
const existingSourceKeys = new Set(
|
|
2022
2039
|
(Array.isArray(refRows) ? refRows : [])
|
|
2023
2040
|
.map(row => row && row.source_key)
|
|
@@ -2041,7 +2058,13 @@ function markdownRowsForRender(taskDb, existingTodoPath, rows, refRows) {
|
|
|
2041
2058
|
if (!task.title) continue;
|
|
2042
2059
|
const sk = taskDb.sourceKey(existingTodoPath, task.title);
|
|
2043
2060
|
const normalizedTitle = taskDb.normalizeTitle(task.title);
|
|
2044
|
-
|
|
2061
|
+
const renderedRef = normalizeRenderedTaskRef(task.id);
|
|
2062
|
+
if (
|
|
2063
|
+
(renderedRef && existingRefs.has(renderedRef)) ||
|
|
2064
|
+
(sk && existingSourceKeys.has(sk)) ||
|
|
2065
|
+
existingTitles.has(normalizedTitle) ||
|
|
2066
|
+
generatedTodo
|
|
2067
|
+
) continue;
|
|
2045
2068
|
out.push({
|
|
2046
2069
|
id: `markdown:${status}:${task.id || index}:${sk ? sk.slice(0, 10) : index}`,
|
|
2047
2070
|
title: task.title,
|
package/commands/xp.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { ensureValidCredentials } = require('../utils/auth');
|
|
1
|
+
const { ensureValidCredentials, getSessionProfile, loadCredentials, profileNameFromEmail } = require('../utils/auth');
|
|
2
2
|
const { apiRequestJson } = require('../utils/api');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const os = require('os');
|
|
@@ -1740,18 +1740,54 @@ function projectionWorkspaceSummaries(projection) {
|
|
|
1740
1740
|
}];
|
|
1741
1741
|
}
|
|
1742
1742
|
|
|
1743
|
+
function credentialHandle(credentials) {
|
|
1744
|
+
return slugify(
|
|
1745
|
+
credentials?.username
|
|
1746
|
+
|| credentials?.handle
|
|
1747
|
+
|| credentials?.display_name
|
|
1748
|
+
|| credentials?.name
|
|
1749
|
+
|| profileNameFromEmail(credentials?.email)
|
|
1750
|
+
|| credentials?.user_id
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
function activeAccountHandle() {
|
|
1755
|
+
const envPlayer = slugify(process.env.ATRIS_PLAYER || process.env.ATRIS_USERNAME);
|
|
1756
|
+
if (envPlayer) return envPlayer;
|
|
1757
|
+
|
|
1758
|
+
const envProfile = slugify(process.env.ATRIS_PROFILE);
|
|
1759
|
+
if (envProfile) return envProfile;
|
|
1760
|
+
|
|
1761
|
+
try {
|
|
1762
|
+
const sessionProfile = slugify(getSessionProfile());
|
|
1763
|
+
if (sessionProfile) return sessionProfile;
|
|
1764
|
+
} catch {}
|
|
1765
|
+
|
|
1766
|
+
try {
|
|
1767
|
+
const credentials = loadCredentials();
|
|
1768
|
+
const handle = credentialHandle(credentials);
|
|
1769
|
+
if (handle) return handle;
|
|
1770
|
+
} catch {}
|
|
1771
|
+
|
|
1772
|
+
return null;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
function projectionHandle(projection) {
|
|
1776
|
+
return slugify(
|
|
1777
|
+
projection?.operator
|
|
1778
|
+
|| projection?.latest_accepted_proof?.actor
|
|
1779
|
+
|| projection?.latest_accepted_proof?.assignee
|
|
1780
|
+
|| projection?.latest_accepted_proof?.assigned_to
|
|
1781
|
+
);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1743
1784
|
function syncPlayer(args, projection) {
|
|
1744
1785
|
const explicit = readFirstFlag(args, ['--as', '--player', '--user', '--operator'], null);
|
|
1745
|
-
return slugify(
|
|
1746
|
-
|
|
1747
|
-
||
|
|
1748
|
-
|| process.env.
|
|
1749
|
-
||
|
|
1750
|
-
|| process.env.USER
|
|
1751
|
-
|| os.userInfo().username
|
|
1752
|
-
|| projection?.operator
|
|
1753
|
-
|| 'player'
|
|
1754
|
-
) || 'player';
|
|
1786
|
+
return slugify(explicit)
|
|
1787
|
+
|| activeAccountHandle()
|
|
1788
|
+
|| projectionHandle(projection)
|
|
1789
|
+
|| slugify(process.env.USER || os.userInfo().username)
|
|
1790
|
+
|| 'player';
|
|
1755
1791
|
}
|
|
1756
1792
|
|
|
1757
1793
|
function buildAgentXpSyncPacket(args = []) {
|
|
@@ -1899,7 +1935,7 @@ function renderSync(payload) {
|
|
|
1899
1935
|
console.log(`Player ${payload.player || entry.username || 'player'} | AgentXP ${formatNumber(entry.agent_xp)} | receipts ${formatNumber(entry.verified_receipts)}`);
|
|
1900
1936
|
if (payload.dry_run) {
|
|
1901
1937
|
console.log(`Packet ${payload.packet?.packet_hash || 'unhashed'} ready; no network upload ran.`);
|
|
1902
|
-
console.log(
|
|
1938
|
+
console.log('Run atris login, then atris xp sync --local to publish to the hosted leaderboard.');
|
|
1903
1939
|
console.log('Guided demos can pass --token <owner-provided-token>.');
|
|
1904
1940
|
console.log(`Leaderboard: ${AGENTXP_LEADERBOARD_URL}`);
|
|
1905
1941
|
return;
|
package/lib/todo-fallback.js
CHANGED
|
@@ -21,7 +21,7 @@ function parseTodoFile(todoPath) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function tagsFromText(text) {
|
|
24
|
-
const allTags = [...String(text || '').matchAll(/\[(\w+)\]/g)].map(m => m[1]);
|
|
24
|
+
const allTags = [...String(text || '').matchAll(/\[([\w-]+)\]/g)].map(m => m[1]);
|
|
25
25
|
return {
|
|
26
26
|
allTags,
|
|
27
27
|
tag: allTags.includes('endgame') ? 'endgame' : (allTags[0] || null),
|
|
@@ -30,7 +30,7 @@ function tagsFromText(text) {
|
|
|
30
30
|
|
|
31
31
|
function cleanTaskTitle(text) {
|
|
32
32
|
const raw = String(text || '').trim();
|
|
33
|
-
const withoutTags = raw.replace(/\s*\[\w+\]/g, '').trim();
|
|
33
|
+
const withoutTags = raw.replace(/\s*\[[\w-]+\]/g, '').trim();
|
|
34
34
|
const bold = withoutTags.match(/^\*\*(.+?)\*\*\s*(?:[—-]\s*)?(.*)$/);
|
|
35
35
|
if (!bold) return withoutTags;
|
|
36
36
|
return [bold[1], bold[2]].filter(Boolean).join(' — ').trim();
|