atris 3.48.0 → 3.49.0
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/atris/CLAUDE.md +2 -0
- package/atris/skills/youtube/SKILL.md +2 -2
- package/bin/atris.js +5 -0
- package/commands/dream.js +1 -1
- package/commands/engine.js +12 -5
- package/commands/member.js +38 -1
- package/commands/task.js +94 -22
- package/commands/who.js +176 -0
- package/commands/worktree.js +7 -5
- package/commands/youtube.js +71 -0
- package/lib/engine-registry.js +1 -0
- package/lib/fleet.js +108 -45
- package/lib/known-commands.js +1 -1
- package/lib/task-db.js +2 -2
- package/lib/workforce-presence.js +448 -0
- package/package.json +1 -1
package/commands/youtube.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { apiRequestJson } = require('../utils/api');
|
|
2
2
|
const { ensureValidCredentials } = require('../utils/auth');
|
|
3
3
|
const { spawnSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const https = require('https');
|
|
6
7
|
|
|
@@ -444,6 +445,67 @@ function formatYoutubeResult(data) {
|
|
|
444
445
|
return lines.join('\n');
|
|
445
446
|
}
|
|
446
447
|
|
|
448
|
+
function videoIdFromUrl(url) {
|
|
449
|
+
const text = String(url || '');
|
|
450
|
+
const watch = text.match(/[?&]v=([^&]+)/);
|
|
451
|
+
if (watch) return watch[1];
|
|
452
|
+
const short = text.match(/youtu\.be\/([^?&/]+)/);
|
|
453
|
+
return short ? short[1] : null;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function dateStamp(now) {
|
|
457
|
+
if (typeof now === 'string' && /^\d{4}-\d{2}-\d{2}/.test(now)) {
|
|
458
|
+
return now.slice(0, 10);
|
|
459
|
+
}
|
|
460
|
+
const value = now instanceof Date ? now : new Date(now || Date.now());
|
|
461
|
+
if (Number.isNaN(value.getTime())) return new Date().toISOString().slice(0, 10);
|
|
462
|
+
return value.toISOString().slice(0, 10);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function firstHeading(notes) {
|
|
466
|
+
const match = String(notes || '').replace(/\r\n/g, '\n').match(/^#{1,6}\s+(.+)$/m);
|
|
467
|
+
return match ? match[1].trim() : '';
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function fileBriefFromNotes({ cwd, url, workDir, now } = {}) {
|
|
471
|
+
try {
|
|
472
|
+
const id = videoIdFromUrl(url);
|
|
473
|
+
if (!id) return;
|
|
474
|
+
const notesPath = path.join(workDir, `yt_${id}.md`);
|
|
475
|
+
if (!fs.existsSync(notesPath)) return;
|
|
476
|
+
const notes = fs.readFileSync(notesPath, 'utf8');
|
|
477
|
+
const wikiDir = path.join(cwd, 'atris', 'wiki');
|
|
478
|
+
if (!fs.existsSync(wikiDir)) return;
|
|
479
|
+
|
|
480
|
+
const heading = firstHeading(notes);
|
|
481
|
+
const date = dateStamp(now);
|
|
482
|
+
const header = [
|
|
483
|
+
heading.toLowerCase(),
|
|
484
|
+
'',
|
|
485
|
+
`date: ${date}`,
|
|
486
|
+
`source: ${url}`,
|
|
487
|
+
'rail: atris youtube notes, quotes repaired against the transcript',
|
|
488
|
+
].join('\n');
|
|
489
|
+
const briefsDir = path.join(wikiDir, 'briefs');
|
|
490
|
+
fs.mkdirSync(briefsDir, { recursive: true });
|
|
491
|
+
const relBrief = `atris/wiki/briefs/youtube-${id}.md`;
|
|
492
|
+
fs.writeFileSync(path.join(cwd, relBrief), `${header}\n${notes}`);
|
|
493
|
+
|
|
494
|
+
const year = date.slice(0, 4);
|
|
495
|
+
const journalPath = path.join(cwd, 'atris', 'logs', year, `${date}.md`);
|
|
496
|
+
fs.mkdirSync(path.dirname(journalPath), { recursive: true });
|
|
497
|
+
let existing = '';
|
|
498
|
+
if (fs.existsSync(journalPath)) existing = fs.readFileSync(journalPath, 'utf8');
|
|
499
|
+
const prefix = existing && !existing.endsWith('\n') ? '\n' : '';
|
|
500
|
+
const line = `- [claimable] watched: ${heading} -> ${relBrief}`;
|
|
501
|
+
fs.writeFileSync(journalPath, `${existing}${prefix}${line}\n`);
|
|
502
|
+
|
|
503
|
+
console.log(`brief filed: ${relBrief}`);
|
|
504
|
+
} catch {
|
|
505
|
+
// notes filing must never break the youtube command
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
447
509
|
function runYoutubeNotes(args = [], deps = {}) {
|
|
448
510
|
const output = deps.output || ((line = '') => console.error(line));
|
|
449
511
|
const url = args[0];
|
|
@@ -459,6 +521,14 @@ function runYoutubeNotes(args = [], deps = {}) {
|
|
|
459
521
|
const childArgs = engine ? [url, engine] : [url];
|
|
460
522
|
const result = spawn(script, childArgs, { stdio: 'inherit' });
|
|
461
523
|
if (result.status == null) return 1;
|
|
524
|
+
if (result.status === 0) {
|
|
525
|
+
fileBriefFromNotes({
|
|
526
|
+
cwd: deps.cwd || process.cwd(),
|
|
527
|
+
url,
|
|
528
|
+
workDir: deps.workDir || path.join(process.env.TMPDIR || '/tmp', 'ytnotes'),
|
|
529
|
+
now: deps.now || new Date(),
|
|
530
|
+
});
|
|
531
|
+
}
|
|
462
532
|
return result.status;
|
|
463
533
|
}
|
|
464
534
|
|
|
@@ -488,5 +558,6 @@ module.exports = {
|
|
|
488
558
|
processYoutube,
|
|
489
559
|
shouldRetryWithLocalTranscript,
|
|
490
560
|
formatYoutubeResult,
|
|
561
|
+
fileBriefFromNotes,
|
|
491
562
|
youtubeCommand,
|
|
492
563
|
};
|
package/lib/engine-registry.js
CHANGED
|
@@ -33,6 +33,7 @@ function engineFailureHealthStatus(result) {
|
|
|
33
33
|
if (/usage[ _-]?limit|purchase more credits|insufficient credits|credit(?:s)?[ _-]?(?:out|limit)|rate[ _-]?limit|not authenticated|please log in|login required|auth(?:entication)?[ _-]?expired|payment required|subscription/.test(signalText)) {
|
|
34
34
|
return 'credit_out';
|
|
35
35
|
}
|
|
36
|
+
if (/not installed|command not found|\benoent\b/.test(signalText)) return 'not_installed';
|
|
36
37
|
if (/timeout|model-unavailable/.test(signalText)) return 'not_installed';
|
|
37
38
|
return 'error';
|
|
38
39
|
}
|
package/lib/fleet.js
CHANGED
|
@@ -208,7 +208,7 @@ function buildEngineCommand(engineName, promptFile, { yolo = false, sealed = fal
|
|
|
208
208
|
if (sealed && engineName === 'codex') {
|
|
209
209
|
cmd = cmd.replace(/\bexec\b/, 'exec --sandbox workspace-write --ephemeral --ignore-user-config --ignore-rules');
|
|
210
210
|
}
|
|
211
|
-
if (sealed && engineName === 'claude') {
|
|
211
|
+
if (sealed && (engineName === 'claude' || engineName === 'fable')) {
|
|
212
212
|
cmd = `${cmd} --safe-mode --no-session-persistence --permission-mode acceptEdits --settings '${JSON.stringify({ sandbox: { enabled: true, autoAllowBashIfSandboxed: true } })}'`;
|
|
213
213
|
}
|
|
214
214
|
if (sealed && engineName === 'cursor') cmd = `${cmd} --sandbox enabled`;
|
|
@@ -218,7 +218,7 @@ function buildEngineCommand(engineName, promptFile, { yolo = false, sealed = fal
|
|
|
218
218
|
}
|
|
219
219
|
if (engineName === 'devin') return cmd.replace(/^devin -p /, 'devin -p --permission-mode dangerous ');
|
|
220
220
|
if (yolo && engineName === 'codex') cmd = cmd.replace(/\bexec\b/, `exec ${YOLO_ENGINE_FLAGS.codex}`);
|
|
221
|
-
if (yolo && engineName === 'claude') cmd = `${cmd} ${YOLO_ENGINE_FLAGS.claude}`;
|
|
221
|
+
if (yolo && (engineName === 'claude' || engineName === 'fable')) cmd = `${cmd} ${YOLO_ENGINE_FLAGS.claude}`;
|
|
222
222
|
if (engineName === 'codex') cmd = wrapCodexWithWatchdog(cmd, watchdogPath, watchdogReceiptPath);
|
|
223
223
|
return cmd;
|
|
224
224
|
} finally {
|
|
@@ -561,6 +561,25 @@ function detectDeadEngineDispatch(result) {
|
|
|
561
561
|
return { reason: 'nonzero_exit', exitCode };
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
+
function plainDispatchFailureCause(result, failure = {}) {
|
|
565
|
+
const reason = String(failure.reason || failure.stage || '').trim();
|
|
566
|
+
const output = dispatchResultOutput(result);
|
|
567
|
+
if (reason === 'no_output') return 'the engine returned no output';
|
|
568
|
+
if (/not authenticated|please log in|login required|auth(?:entication)?[ _-]?expired/i.test(output)) {
|
|
569
|
+
return 'the engine login expired';
|
|
570
|
+
}
|
|
571
|
+
if (/spawn[^\n]*enoent|enoent[^\n]*spawn|failed to spawn/i.test(output)) {
|
|
572
|
+
return 'the engine could not start';
|
|
573
|
+
}
|
|
574
|
+
if (reason === 'timeout') return 'the engine timed out';
|
|
575
|
+
if (reason === 'cancelled') return 'the engine run was cancelled';
|
|
576
|
+
if (reason === 'unknown') return 'the engine exited without a status';
|
|
577
|
+
if (reason === 'signalled') return `the engine stopped with ${failure.signal || 'a signal'}`;
|
|
578
|
+
const detail = String(failure.detail || output || '').trim().split('\n')[0].trim();
|
|
579
|
+
if (detail) return detail.replace(/[.]+$/, '');
|
|
580
|
+
return (reason || 'the engine failed').replace(/_/g, ' ');
|
|
581
|
+
}
|
|
582
|
+
|
|
564
583
|
function recordDispatchEngineHealth(result, failure, root) {
|
|
565
584
|
if (!result || !result.engine) return null;
|
|
566
585
|
const status = failure
|
|
@@ -1081,6 +1100,7 @@ module.exports = {
|
|
|
1081
1100
|
shipWithRetry,
|
|
1082
1101
|
shipFailureDetail,
|
|
1083
1102
|
get FLEET_CAPABLE() { return FLEET_CAPABLE; },
|
|
1103
|
+
get DISPATCH_CAPABLE() { return DISPATCH_CAPABLE; },
|
|
1084
1104
|
get runFleetFlight() { return runFleetFlight; },
|
|
1085
1105
|
get focusedCheck() { return focusedCheck; },
|
|
1086
1106
|
get dispatchCheck() { return dispatchCheck; },
|
|
@@ -1122,6 +1142,7 @@ module.exports = {
|
|
|
1122
1142
|
// Engines that can edit a repo headlessly. atris-fast (ax) is a chat lane,
|
|
1123
1143
|
// not a repo worker — it keeps owning normal mission ticks, not fleet builds.
|
|
1124
1144
|
const FLEET_CAPABLE = ['claude', 'codex', 'cursor', 'devin', 'grok'];
|
|
1145
|
+
const DISPATCH_CAPABLE = [...FLEET_CAPABLE, 'fable'];
|
|
1125
1146
|
|
|
1126
1147
|
let receiptSequence = 0;
|
|
1127
1148
|
function nowStamp() {
|
|
@@ -2658,6 +2679,18 @@ async function runFleetFlight({
|
|
|
2658
2679
|
// ---------------------------------------------------------------------------
|
|
2659
2680
|
// T5 — one-command dispatch: `atris engine dispatch <task-id> --engine <name>`
|
|
2660
2681
|
|
|
2682
|
+
// A claim taken before the engine starts must not stay held if the flight
|
|
2683
|
+
// refuses or errors first. Release through the same task plane the claim used.
|
|
2684
|
+
function releaseUnstartedDispatchClaim(cli, { taskId, actor, detail }) {
|
|
2685
|
+
const released = cli(['task', 'release', taskId, '--as', actor]);
|
|
2686
|
+
const releasedOk = Boolean(released && released.status === 0);
|
|
2687
|
+
const suffix = releasedOk
|
|
2688
|
+
? 'claim released, safe to retry'
|
|
2689
|
+
: `claim release failed: ${String(released && (released.stderr || released.stdout) || 'unknown').trim().slice(0, 80)}`;
|
|
2690
|
+
const base = String(detail || '').trim().slice(0, 200);
|
|
2691
|
+
return { released: releasedOk, detail: base ? `${base}. ${suffix}` : suffix };
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2661
2694
|
// The manual version of this loop took 6 Bash calls per task the night this
|
|
2662
2695
|
// was written: claim, worktree start, prompt file, engine -p, verify, ship.
|
|
2663
2696
|
// One or more explicit task ids build in parallel isolated worktrees on ONE
|
|
@@ -2693,8 +2726,8 @@ async function runDispatchFlight({
|
|
|
2693
2726
|
scoutAsk = null,
|
|
2694
2727
|
} = {}) {
|
|
2695
2728
|
if (!engine) throw new Error('runDispatchFlight: engine is required');
|
|
2696
|
-
if (!
|
|
2697
|
-
throw new Error(`runDispatchFlight: engine "${engine}" cannot build headlessly (capable: ${
|
|
2729
|
+
if (!DISPATCH_CAPABLE.includes(engine)) {
|
|
2730
|
+
throw new Error(`runDispatchFlight: engine "${engine}" cannot build headlessly (capable: ${DISPATCH_CAPABLE.join(', ')})`);
|
|
2698
2731
|
}
|
|
2699
2732
|
const ids = [...new Set((taskIds || []).map((id) => String(id).trim()).filter(Boolean))];
|
|
2700
2733
|
if (!ids.length) throw new Error('runDispatchFlight: at least one task id is required');
|
|
@@ -2760,51 +2793,71 @@ async function runDispatchFlight({
|
|
|
2760
2793
|
log(` x ${taskId} claim failed`);
|
|
2761
2794
|
continue;
|
|
2762
2795
|
}
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2796
|
+
try {
|
|
2797
|
+
let landingWorktreePath = '';
|
|
2798
|
+
let remoteBoundary = null;
|
|
2799
|
+
if (enforceRemoteBoundary) {
|
|
2800
|
+
remoteBoundary = prepareReviewSandbox({ root, taskId, engine });
|
|
2801
|
+
} else {
|
|
2802
|
+
const started = cli(['worktree', 'start', '--agent', engine, '--task', `dispatch-${taskId.toLowerCase()}`, ...startBaseArgs]);
|
|
2803
|
+
const wt = (started.stdout.match(/next: cd (.+)/) || [])[1];
|
|
2804
|
+
if (!wt) {
|
|
2805
|
+
const released = releaseUnstartedDispatchClaim(cli, {
|
|
2806
|
+
taskId,
|
|
2807
|
+
actor: taskActor,
|
|
2808
|
+
detail: String(started.stderr || '').slice(0, 200),
|
|
2809
|
+
});
|
|
2810
|
+
flight.paused.push({ task: taskId, stage: 'worktree_start', detail: released.detail });
|
|
2811
|
+
log(` ✗ ${taskId} worktree start failed`);
|
|
2812
|
+
continue;
|
|
2813
|
+
}
|
|
2814
|
+
landingWorktreePath = wt.trim();
|
|
2815
|
+
}
|
|
2816
|
+
if (enforceRemoteBoundary && (!remoteBoundary || remoteBoundary.ok !== true)) {
|
|
2817
|
+
const released = releaseUnstartedDispatchClaim(cli, {
|
|
2818
|
+
taskId,
|
|
2819
|
+
actor: taskActor,
|
|
2820
|
+
detail: String(remoteBoundary && remoteBoundary.detail || 'could not prepare a sealed review sandbox').slice(-500),
|
|
2821
|
+
});
|
|
2822
|
+
flight.paused.push({
|
|
2823
|
+
task: taskId,
|
|
2824
|
+
stage: 'remote_quarantine',
|
|
2825
|
+
detail: released.detail,
|
|
2826
|
+
worktree: null,
|
|
2827
|
+
});
|
|
2828
|
+
log(` paused ${taskId} because its sealed review sandbox could not be prepared`);
|
|
2773
2829
|
continue;
|
|
2774
2830
|
}
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2831
|
+
const worktreePath = enforceRemoteBoundary ? remoteBoundary.worktreePath : landingWorktreePath;
|
|
2832
|
+
const startCommit = yolo ? readStartCommit({ worktreePath }) : '';
|
|
2833
|
+
const basePrompt = promptOverride || buildFleetPrompt(task, { worktreePath, yolo });
|
|
2834
|
+
const safetyPrompt = reviewOnly
|
|
2835
|
+
? `${basePrompt}\n\nSafety boundary: edit and test this checkout only. Do not push, merge, deploy, publish, send messages, change cloud state, install dependencies, or use credentials.`
|
|
2836
|
+
: basePrompt;
|
|
2837
|
+
const trustedPrompt = trustedVerifier
|
|
2838
|
+
? `${safetyPrompt}\n\nTrusted verifier (run it bare before reporting done): ${trustedVerifier}`
|
|
2839
|
+
: promptOverride;
|
|
2840
|
+
prepared.push({
|
|
2841
|
+
task,
|
|
2842
|
+
taskId,
|
|
2843
|
+
worktreePath,
|
|
2844
|
+
landingWorktreePath,
|
|
2845
|
+
engine,
|
|
2846
|
+
remoteBoundary,
|
|
2847
|
+
remoteMasterBefore: remoteBoundary ? remoteBoundary.protectedMaster : '',
|
|
2848
|
+
startCommit,
|
|
2849
|
+
...(trustedPrompt ? { prompt: trustedPrompt } : {}),
|
|
2783
2850
|
});
|
|
2784
|
-
log(`
|
|
2785
|
-
|
|
2851
|
+
log(` building ${taskId} in ${path.basename(worktreePath)}`);
|
|
2852
|
+
} catch (err) {
|
|
2853
|
+
const released = releaseUnstartedDispatchClaim(cli, {
|
|
2854
|
+
taskId,
|
|
2855
|
+
actor: taskActor,
|
|
2856
|
+
detail: String(err && err.message || err).slice(0, 200),
|
|
2857
|
+
});
|
|
2858
|
+
flight.paused.push({ task: taskId, stage: 'prepare', detail: released.detail });
|
|
2859
|
+
log(` ✗ ${taskId} prepare failed`);
|
|
2786
2860
|
}
|
|
2787
|
-
const worktreePath = enforceRemoteBoundary ? remoteBoundary.worktreePath : landingWorktreePath;
|
|
2788
|
-
const startCommit = yolo ? readStartCommit({ worktreePath }) : '';
|
|
2789
|
-
const basePrompt = promptOverride || buildFleetPrompt(task, { worktreePath, yolo });
|
|
2790
|
-
const safetyPrompt = reviewOnly
|
|
2791
|
-
? `${basePrompt}\n\nSafety boundary: edit and test this checkout only. Do not push, merge, deploy, publish, send messages, change cloud state, install dependencies, or use credentials.`
|
|
2792
|
-
: basePrompt;
|
|
2793
|
-
const trustedPrompt = trustedVerifier
|
|
2794
|
-
? `${safetyPrompt}\n\nTrusted verifier (run it bare before reporting done): ${trustedVerifier}`
|
|
2795
|
-
: promptOverride;
|
|
2796
|
-
prepared.push({
|
|
2797
|
-
task,
|
|
2798
|
-
taskId,
|
|
2799
|
-
worktreePath,
|
|
2800
|
-
landingWorktreePath,
|
|
2801
|
-
engine,
|
|
2802
|
-
remoteBoundary,
|
|
2803
|
-
remoteMasterBefore: remoteBoundary ? remoteBoundary.protectedMaster : '',
|
|
2804
|
-
startCommit,
|
|
2805
|
-
...(trustedPrompt ? { prompt: trustedPrompt } : {}),
|
|
2806
|
-
});
|
|
2807
|
-
log(` building ${taskId} in ${path.basename(worktreePath)}`);
|
|
2808
2861
|
}
|
|
2809
2862
|
|
|
2810
2863
|
const dispatch = dispatcher || ((entry) => new Promise((resolve) => {
|
|
@@ -2828,6 +2881,10 @@ async function runDispatchFlight({
|
|
|
2828
2881
|
}));
|
|
2829
2882
|
const restaffState = { used: false };
|
|
2830
2883
|
|
|
2884
|
+
if (engine === 'fable' && prepared.length) {
|
|
2885
|
+
log(` fable handoff started: receipt ${path.relative(root, receiptPath)}`);
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2831
2888
|
const results = await Promise.all(prepared.map((entry) => {
|
|
2832
2889
|
const startedAtMs = Date.now();
|
|
2833
2890
|
return dispatchEntryWithRestaff({
|
|
@@ -3343,6 +3400,12 @@ async function runDispatchFlight({
|
|
|
3343
3400
|
flight.finished_at = new Date().toISOString();
|
|
3344
3401
|
writeDispatchReceipt(flight, receiptPath, { ids, reviewOnly, enforceRemoteBoundary });
|
|
3345
3402
|
log('');
|
|
3403
|
+
if (engine === 'fable' && flight.paused.length) {
|
|
3404
|
+
const paused = flight.paused[0];
|
|
3405
|
+
const failed = results.find(({ entry }) => entry.taskId === paused.task);
|
|
3406
|
+
const cause = plainDispatchFailureCause(failed && failed.result, paused);
|
|
3407
|
+
log(` fable handoff failed: ${cause}. receipt: ${path.relative(root, receiptPath)}`);
|
|
3408
|
+
}
|
|
3346
3409
|
const completedLabel = reviewOnly ? `${flight.ready.length} proof ready` : `${flight.landed.length} landed`;
|
|
3347
3410
|
log(` dispatch over: ${completedLabel}, ${flight.paused.length} paused - receipt: ${path.relative(root, flight.receipt)}`);
|
|
3348
3411
|
log('');
|
package/lib/known-commands.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const knownCommands = ['init', 'log', 'logs', 'wish', 'ask', 'approve', 'stop', 'ready', 'check', 'drill', 'dream', 'now', 'goal', 'wtf', 'founder', 'orb', 'radar', 'stream', 'ctop', 'launchpad', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', '_start', 'plan', 'do', 'review', 'release',
|
|
3
|
+
const knownCommands = ['init', 'log', 'logs', 'wish', 'ask', 'approve', 'stop', 'ready', 'check', 'drill', 'dream', 'now', 'goal', 'wtf', 'founder', 'orb', 'radar', 'who', 'stream', 'ctop', 'launchpad', 'status', 'analytics', 'visualize', 'brain', 'brainstorm', 'autopilot', 'run', '_start', 'plan', 'do', 'review', 'release',
|
|
4
4
|
'activate', '_activate', 'agent', 'team', 'chat', 'fast', 'ax', 'console', 'serve', 'login', 'logout', 'whoami', 'switch', 'use', 'accounts', '_resolve', '_profile-email', '_switch-session', 'shell-init', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
|
|
5
5
|
'clean', 'close', 'harvest', 'verify', 'recover', 'search', 'scout', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'taste', 'teach', 'plugin', 'experiments', 'bench', 'router', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'watch', 'cloud', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync', 'youtube',
|
|
6
6
|
'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'decide', 'agents', 'probe', 'worktree', 'land', 'caretaker', 'autoland', 'drive', 'aeo', 'slop', 'voice', 'strings', 'write', 'security-review', 'secure', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'study', 'rainmaker', 'xp', 'play', 'gm', 'game', 'x', 'recap', 'report', 'signup', 'clarity', 'interview', 'meet', 'moves', 'unknowns', 'avail', 'sync-checkout',
|
package/lib/task-db.js
CHANGED
|
@@ -369,8 +369,8 @@ function withTaskDisplayRefs(rows, refRows = rows) {
|
|
|
369
369
|
const ids = sorted.map(row => row && row.id);
|
|
370
370
|
sorted.forEach((row, index) => {
|
|
371
371
|
refs.set(row.id, {
|
|
372
|
-
display_id: taskDisplayRef(row, index),
|
|
373
|
-
legacy_ref: shortestUniqueTaskRef(row.id, ids, 8),
|
|
372
|
+
display_id: row.display_id || taskDisplayRef(row, index),
|
|
373
|
+
legacy_ref: row.legacy_ref || shortestUniqueTaskRef(row.id, ids, 8),
|
|
374
374
|
});
|
|
375
375
|
});
|
|
376
376
|
}
|