atris 3.30.12 → 3.32.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/AGENTS.md +16 -3
- package/FOR_AGENTS.md +81 -0
- package/README.md +6 -4
- package/atris/CLAUDE.md +8 -0
- package/atris/atris.md +51 -19
- package/atris/skills/README.md +1 -0
- package/atris/skills/blocks/SKILL.md +134 -0
- package/atris/skills/clawhub/philosophy-of-work/SKILL.md +56 -0
- package/atris/skills/youtube/SKILL.md +31 -11
- package/atris.md +15 -0
- package/ax +189 -7
- package/bin/atris.js +273 -225
- package/commands/autoland.js +379 -0
- package/commands/autopilot-front.js +273 -0
- package/commands/autopilot.js +94 -4
- package/commands/business.js +1 -1
- package/commands/clean.js +72 -9
- package/commands/codex-goal.js +72 -22
- package/commands/computer.js +48 -3
- package/commands/gm.js +1 -1
- package/commands/harvest.js +179 -0
- package/commands/init.js +22 -1
- package/commands/land.js +442 -0
- package/commands/loop-front.js +122 -4
- package/commands/member.js +551 -19
- package/commands/mission.js +3674 -278
- package/commands/play.js +1 -1
- package/commands/pulse.js +65 -7
- package/commands/run-front.js +144 -0
- package/commands/run.js +10 -7
- package/commands/sign.js +90 -0
- package/commands/strings.js +301 -0
- package/commands/task.js +782 -108
- package/commands/truth.js +171 -0
- package/commands/xp.js +32 -8
- package/commands/youtube.js +72 -5
- package/decks/README.md +6 -12
- package/lib/auto-accept-certified.js +10 -0
- package/lib/autoland.js +391 -0
- package/lib/context-gatherer.js +0 -8
- package/lib/mission-artifact.js +504 -0
- package/lib/mission-room.js +846 -0
- package/lib/next-moves.js +212 -6
- package/lib/operator-next.js +7 -0
- package/lib/pulse.js +78 -4
- package/lib/runner-command.js +51 -20
- package/lib/runs-prune.js +242 -0
- package/lib/task-db.js +19 -2
- package/lib/task-proof.js +1 -1
- package/package.json +4 -4
- package/decks/atris-seed-pitch-v3.json +0 -118
- package/decks/atris-seed-pitch-v4-skeleton.json +0 -106
- package/decks/atris-seed-pitch-v5.json +0 -109
- package/decks/atris-seed-pitch-v6.json +0 -137
- package/decks/atris-seed-pitch-v7.json +0 -133
- package/decks/mark-pincus-narrative.json +0 -102
- package/decks/mark-pincus-sourcery.json +0 -94
- package/decks/yash-applied-compute-detailed.json +0 -150
- package/decks/yash-applied-compute-generalist.json +0 -82
- package/decks/yash-applied-compute-narrative.json +0 -54
- package/lib/ax-chat-input.js +0 -164
- package/lib/ax-goal.js +0 -307
- package/lib/ax-prefs.js +0 -63
- package/lib/ax-shimmer.js +0 -63
package/commands/autopilot.js
CHANGED
|
@@ -15,7 +15,7 @@ const { parseTodo } = require('../lib/todo');
|
|
|
15
15
|
const {
|
|
16
16
|
buildRunnerCommand,
|
|
17
17
|
buildRunnerAvailabilityCommand,
|
|
18
|
-
|
|
18
|
+
runnerAvailabilityFailureMessage,
|
|
19
19
|
} = require('../lib/runner-command');
|
|
20
20
|
const { findStalePages, findStaleTasks, healBrokenMapRefs } = require('./clean');
|
|
21
21
|
const {
|
|
@@ -484,6 +484,91 @@ function executePhaseDetailed(phase, context, options = {}) {
|
|
|
484
484
|
}
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
+
function autopilotGlassLogDir(cwd = process.cwd()) {
|
|
488
|
+
const dir = path.join(cwd, 'atris', 'logs', 'autopilot');
|
|
489
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
490
|
+
return dir;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function autopilotGlassSlug(value) {
|
|
494
|
+
const slug = String(value || 'tick')
|
|
495
|
+
.toLowerCase()
|
|
496
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
497
|
+
.replace(/^-+|-+$/g, '')
|
|
498
|
+
.slice(0, 64);
|
|
499
|
+
return slug || 'tick';
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function autopilotGlassTimestamp(now = new Date()) {
|
|
503
|
+
return now.toISOString().replace(/[:.]/g, '-');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function phaseGlassText(phaseResults, key) {
|
|
507
|
+
const phase = phaseResults && phaseResults[key] ? phaseResults[key] : {};
|
|
508
|
+
const output = String(phase.output || '').trim();
|
|
509
|
+
const elapsed = Number.isFinite(phase.elapsedSeconds) ? `\n\nElapsed: ${phase.elapsedSeconds}s` : '';
|
|
510
|
+
return `${output || '(no output)'}${elapsed}`;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function buildAutopilotGlassLog(context, execution, options = {}) {
|
|
514
|
+
const phaseResults = execution.phaseResults || {};
|
|
515
|
+
const task = context.task || 'Autopilot tick';
|
|
516
|
+
const generatedAt = options.generatedAt || new Date().toISOString();
|
|
517
|
+
const proof = execution.verifyRan
|
|
518
|
+
? `${execution.verifyPass ? 'PASS' : 'FAIL'}: ${phaseGlassText(phaseResults, 'verify')}`
|
|
519
|
+
: 'NOT RUN: no verifier command was available';
|
|
520
|
+
|
|
521
|
+
return [
|
|
522
|
+
'# Autopilot Glass Log',
|
|
523
|
+
'',
|
|
524
|
+
`Generated: ${generatedAt}`,
|
|
525
|
+
`Task: ${task}`,
|
|
526
|
+
`Kind: ${context.kind || 'unknown'}`,
|
|
527
|
+
`Verify: ${execution.verifyCmd || '(none)'}`,
|
|
528
|
+
`Outcome: ${execution.success ? 'success' : 'needs attention'}`,
|
|
529
|
+
'',
|
|
530
|
+
'## Plan',
|
|
531
|
+
'',
|
|
532
|
+
phaseGlassText(phaseResults, 'plan'),
|
|
533
|
+
'',
|
|
534
|
+
'## Plan Review',
|
|
535
|
+
'',
|
|
536
|
+
phaseGlassText(phaseResults, 'plan-review'),
|
|
537
|
+
'',
|
|
538
|
+
'## Action',
|
|
539
|
+
'',
|
|
540
|
+
phaseGlassText(phaseResults, 'do'),
|
|
541
|
+
'',
|
|
542
|
+
'## Result',
|
|
543
|
+
'',
|
|
544
|
+
phaseGlassText(phaseResults, 'review'),
|
|
545
|
+
'',
|
|
546
|
+
'## Proof',
|
|
547
|
+
'',
|
|
548
|
+
proof,
|
|
549
|
+
''
|
|
550
|
+
].join('\n');
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function writeAutopilotGlassLog(cwd, context, execution, options = {}) {
|
|
554
|
+
const dir = autopilotGlassLogDir(cwd);
|
|
555
|
+
const now = options.now || new Date();
|
|
556
|
+
const file = `${autopilotGlassTimestamp(now)}-${autopilotGlassSlug(context.task)}.md`;
|
|
557
|
+
const absPath = path.join(dir, file);
|
|
558
|
+
fs.writeFileSync(absPath, buildAutopilotGlassLog(context, execution, {
|
|
559
|
+
generatedAt: now.toISOString(),
|
|
560
|
+
}));
|
|
561
|
+
return path.relative(cwd, absPath);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function maybeWriteAutopilotGlassLog(cwd, context, execution) {
|
|
565
|
+
try {
|
|
566
|
+
return writeAutopilotGlassLog(cwd, context, execution);
|
|
567
|
+
} catch {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
487
572
|
/**
|
|
488
573
|
* Build context-aware file list for prompts.
|
|
489
574
|
*/
|
|
@@ -1806,7 +1891,7 @@ function runTaskOnce(context, options = {}) {
|
|
|
1806
1891
|
}
|
|
1807
1892
|
}
|
|
1808
1893
|
|
|
1809
|
-
|
|
1894
|
+
const execution = {
|
|
1810
1895
|
success: verifyRan && verifyPass,
|
|
1811
1896
|
elapsedSeconds: Math.round((Date.now() - startedAt) / 1000),
|
|
1812
1897
|
phaseResults,
|
|
@@ -1815,6 +1900,8 @@ function runTaskOnce(context, options = {}) {
|
|
|
1815
1900
|
verifyPass,
|
|
1816
1901
|
verifyRan,
|
|
1817
1902
|
};
|
|
1903
|
+
execution.glassLogPath = maybeWriteAutopilotGlassLog(cwd, context, execution);
|
|
1904
|
+
return execution;
|
|
1818
1905
|
}
|
|
1819
1906
|
|
|
1820
1907
|
/**
|
|
@@ -3022,8 +3109,8 @@ async function autopilotAtris(description, options = {}) {
|
|
|
3022
3109
|
process.exit(1);
|
|
3023
3110
|
}
|
|
3024
3111
|
|
|
3025
|
-
try { execSync(buildRunnerAvailabilityCommand(), { stdio: 'pipe' }); } catch {
|
|
3026
|
-
console.error(
|
|
3112
|
+
try { execSync(buildRunnerAvailabilityCommand(), { stdio: 'pipe' }); } catch (err) {
|
|
3113
|
+
console.error(runnerAvailabilityFailureMessage(err));
|
|
3027
3114
|
process.exit(1);
|
|
3028
3115
|
}
|
|
3029
3116
|
|
|
@@ -3679,5 +3766,8 @@ module.exports = {
|
|
|
3679
3766
|
isPhaseKillError,
|
|
3680
3767
|
execPhaseCommandSync,
|
|
3681
3768
|
executePhaseDetailed,
|
|
3769
|
+
autopilotGlassLogDir,
|
|
3770
|
+
buildAutopilotGlassLog,
|
|
3771
|
+
writeAutopilotGlassLog,
|
|
3682
3772
|
lessonSlug
|
|
3683
3773
|
};
|
package/commands/business.js
CHANGED
|
@@ -840,7 +840,7 @@ function renderBusinessOsLines(os = {}, prefix = '- ') {
|
|
|
840
840
|
`${prefix}Team goals: ${team.members || 0} member lanes, ${team.activeGoalMembers || 0} with active goals`,
|
|
841
841
|
`${prefix}${xp.metric || 'AgentXP'}: ${xp.total || 0} total, ${xp.today || 0} today, ${xp.receipts || 0} receipts, integrity ${xp.integrity || 'unknown'}`,
|
|
842
842
|
`${prefix}Loop: ${loop.ticks || 0} mission ticks; Codex goal ${loop.codexGoal || 'none'}`,
|
|
843
|
-
`${prefix}XP gate: proof can move to Review; XP
|
|
843
|
+
`${prefix}XP gate: proof can move to Review; XP is awarded only after human approval`,
|
|
844
844
|
];
|
|
845
845
|
}
|
|
846
846
|
|
package/commands/clean.js
CHANGED
|
@@ -15,20 +15,23 @@ function cleanAtris(options = {}) {
|
|
|
15
15
|
const atrisDir = path.join(cwd, 'atris');
|
|
16
16
|
|
|
17
17
|
if (!fs.existsSync(atrisDir)) {
|
|
18
|
+
if (options.json) {
|
|
19
|
+
console.log(JSON.stringify({
|
|
20
|
+
ok: false,
|
|
21
|
+
action: 'clean',
|
|
22
|
+
error: 'atris/ folder not found. Run "atris init" first.',
|
|
23
|
+
}, null, 2));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
18
26
|
console.log('✗ atris/ folder not found. Run "atris init" first.');
|
|
19
27
|
process.exit(1);
|
|
20
28
|
}
|
|
21
29
|
|
|
22
|
-
console.log('');
|
|
23
|
-
console.log('┌─────────────────────────────────────────────────────────────┐');
|
|
24
|
-
console.log('│ Atris Clean │');
|
|
25
|
-
console.log('└─────────────────────────────────────────────────────────────┘');
|
|
26
|
-
console.log('');
|
|
27
|
-
|
|
28
30
|
const results = {
|
|
29
31
|
staleTasks: [],
|
|
30
32
|
brokenRefs: [],
|
|
31
33
|
healedRefs: 0,
|
|
34
|
+
healedRefDetails: [],
|
|
32
35
|
unhealableRefs: [],
|
|
33
36
|
archivedJournals: 0,
|
|
34
37
|
cleanedSections: 0
|
|
@@ -39,8 +42,9 @@ function cleanAtris(options = {}) {
|
|
|
39
42
|
results.staleTasks = staleTasks;
|
|
40
43
|
|
|
41
44
|
// 2. Find and HEAL broken MAP.md references
|
|
42
|
-
const { healed, unhealable } = healBrokenMapRefs(cwd, atrisDir, options.dryRun);
|
|
45
|
+
const { healed, unhealable, replacements } = healBrokenMapRefs(cwd, atrisDir, options.dryRun);
|
|
43
46
|
results.healedRefs = healed;
|
|
47
|
+
results.healedRefDetails = replacements;
|
|
44
48
|
results.unhealableRefs = unhealable;
|
|
45
49
|
|
|
46
50
|
// 3. Archive old journals (>30 days)
|
|
@@ -55,6 +59,17 @@ function cleanAtris(options = {}) {
|
|
|
55
59
|
const stalePages = findStalePages(cwd, atrisDir);
|
|
56
60
|
results.stalePages = stalePages;
|
|
57
61
|
|
|
62
|
+
if (options.json) {
|
|
63
|
+
console.log(JSON.stringify(cleanResultPayload(results, options, cwd), null, 2));
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log('');
|
|
68
|
+
console.log('┌─────────────────────────────────────────────────────────────┐');
|
|
69
|
+
console.log('│ Atris Clean │');
|
|
70
|
+
console.log('└─────────────────────────────────────────────────────────────┘');
|
|
71
|
+
console.log('');
|
|
72
|
+
|
|
58
73
|
// Report results
|
|
59
74
|
console.log('Results:');
|
|
60
75
|
console.log('');
|
|
@@ -74,6 +89,12 @@ function cleanAtris(options = {}) {
|
|
|
74
89
|
if (healed > 0) {
|
|
75
90
|
const verb = options.dryRun ? 'Would heal' : 'Healed';
|
|
76
91
|
console.log(`✓ ${verb} ${healed} MAP.md ${healed === 1 ? 'reference' : 'references'}`);
|
|
92
|
+
(results.healedRefDetails || []).slice(0, 3).forEach(ref => {
|
|
93
|
+
console.log(` • ${ref.old} -> ${ref.new}${ref.symbol ? ` (${ref.symbol})` : ''}`);
|
|
94
|
+
});
|
|
95
|
+
if ((results.healedRefDetails || []).length > 3) {
|
|
96
|
+
console.log(` ... and ${results.healedRefDetails.length - 3} more`);
|
|
97
|
+
}
|
|
77
98
|
}
|
|
78
99
|
|
|
79
100
|
// Unhealable refs
|
|
@@ -138,6 +159,47 @@ function cleanAtris(options = {}) {
|
|
|
138
159
|
return results;
|
|
139
160
|
}
|
|
140
161
|
|
|
162
|
+
function cleanResultPayload(results, options = {}, cwd = process.cwd()) {
|
|
163
|
+
const manualAction = [];
|
|
164
|
+
if (results.staleTasks.length > 0) manualAction.push('Delete stale tasks or finish them');
|
|
165
|
+
if (results.unhealableRefs.length > 0) manualAction.push('Manually fix unhealable MAP.md refs');
|
|
166
|
+
if (results.stalePages.length > 0) manualAction.push('Re-compile stale pages');
|
|
167
|
+
return {
|
|
168
|
+
ok: manualAction.length === 0,
|
|
169
|
+
action: 'clean',
|
|
170
|
+
dry_run: !!options.dryRun,
|
|
171
|
+
results: {
|
|
172
|
+
stale_tasks: {
|
|
173
|
+
count: results.staleTasks.length,
|
|
174
|
+
items: results.staleTasks,
|
|
175
|
+
},
|
|
176
|
+
map_refs: {
|
|
177
|
+
healed_count: results.healedRefs,
|
|
178
|
+
verb: options.dryRun ? 'would_heal' : 'healed',
|
|
179
|
+
items: results.healedRefDetails || [],
|
|
180
|
+
unhealable_count: results.unhealableRefs.length,
|
|
181
|
+
unhealable: results.unhealableRefs,
|
|
182
|
+
},
|
|
183
|
+
journals: {
|
|
184
|
+
archived_count: results.archivedJournals,
|
|
185
|
+
verb: options.dryRun ? 'would_archive' : 'archived',
|
|
186
|
+
},
|
|
187
|
+
sections: {
|
|
188
|
+
cleaned_count: results.cleanedSections,
|
|
189
|
+
verb: options.dryRun ? 'would_clean' : 'cleaned',
|
|
190
|
+
},
|
|
191
|
+
stale_pages: {
|
|
192
|
+
count: results.stalePages.length,
|
|
193
|
+
items: results.stalePages.map(page => ({
|
|
194
|
+
page: path.relative(cwd, page.page),
|
|
195
|
+
stale_source: page.staleSource,
|
|
196
|
+
})),
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
manual_action: manualAction,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
141
203
|
/**
|
|
142
204
|
* Find tasks claimed but not completed after 3+ days
|
|
143
205
|
*/
|
|
@@ -295,7 +357,7 @@ function healBrokenMapRefs(cwd, atrisDir, dryRun = false) {
|
|
|
295
357
|
fs.writeFileSync(mapFile, mapContent);
|
|
296
358
|
}
|
|
297
359
|
|
|
298
|
-
return { healed, unhealable, replacements
|
|
360
|
+
return { healed, unhealable, replacements };
|
|
299
361
|
}
|
|
300
362
|
|
|
301
363
|
/**
|
|
@@ -337,7 +399,7 @@ function findFunctionEnd(lines, startLine) {
|
|
|
337
399
|
}
|
|
338
400
|
|
|
339
401
|
/**
|
|
340
|
-
* Extract a symbol name from context like "(
|
|
402
|
+
* Extract a symbol name from context like "(interactiveEntry function)" or "— Main entry"
|
|
341
403
|
*/
|
|
342
404
|
function extractSymbol(context) {
|
|
343
405
|
if (!context) return null;
|
|
@@ -568,6 +630,7 @@ function cleanEmptySections(atrisDir, dryRun = false) {
|
|
|
568
630
|
|
|
569
631
|
module.exports = {
|
|
570
632
|
cleanAtris,
|
|
633
|
+
cleanResultPayload,
|
|
571
634
|
findStaleTasks,
|
|
572
635
|
healBrokenMapRefs,
|
|
573
636
|
archiveOldJournals,
|
package/commands/codex-goal.js
CHANGED
|
@@ -32,7 +32,50 @@ function sqlString(value) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
function resolveStatePath(args = []) {
|
|
35
|
-
|
|
35
|
+
const explicit = readFlag(args, '--state', process.env.CODEX_STATE_DB || '');
|
|
36
|
+
if (explicit) return path.resolve(expandHome(explicit));
|
|
37
|
+
// Codex moved native goals into ~/.codex/goals_1.sqlite; older builds kept them in state_5.sqlite.
|
|
38
|
+
// Prefer the live goals DB so the bridge sees real goal activity, fall back to the legacy state DB.
|
|
39
|
+
const goalsDb = path.join(os.homedir(), '.codex', 'goals_1.sqlite');
|
|
40
|
+
const legacyDb = path.join(os.homedir(), '.codex', 'state_5.sqlite');
|
|
41
|
+
return path.resolve(fs.existsSync(goalsDb) ? goalsDb : legacyDb);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Thread metadata (cwd/title) stayed in state_5.sqlite even after goals moved to goals_1.sqlite.
|
|
45
|
+
function resolveThreadsPath(args = []) {
|
|
46
|
+
const explicit = readFlag(args, '--threads-db', process.env.CODEX_THREADS_DB || '');
|
|
47
|
+
if (explicit) return path.resolve(expandHome(explicit));
|
|
48
|
+
const legacyDb = path.join(os.homedir(), '.codex', 'state_5.sqlite');
|
|
49
|
+
return fs.existsSync(legacyDb) ? path.resolve(legacyDb) : '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function tableExists(dbPath, table) {
|
|
53
|
+
if (!dbPath || !fs.existsSync(dbPath)) return false;
|
|
54
|
+
try {
|
|
55
|
+
const rows = runSqliteJson(dbPath, `SELECT name FROM sqlite_master WHERE type='table' AND name=${sqlString(table)} LIMIT 1;`);
|
|
56
|
+
return rows.length > 0;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Goals live in goals_1.sqlite (no `threads` table); thread metadata lives in state_5.sqlite.
|
|
63
|
+
// Legacy single-file layouts (and test fixtures) keep both in one DB. Resolve which join to use.
|
|
64
|
+
function goalQueryContext(args = []) {
|
|
65
|
+
const goalsDb = resolveStatePath(args);
|
|
66
|
+
if (tableExists(goalsDb, 'threads')) {
|
|
67
|
+
return { goalsDb, threadsTable: 'threads', prefix: '' };
|
|
68
|
+
}
|
|
69
|
+
const threadsDb = resolveThreadsPath(args);
|
|
70
|
+
if (threadsDb && threadsDb !== goalsDb && tableExists(threadsDb, 'threads')) {
|
|
71
|
+
return { goalsDb, threadsTable: 'tdb.threads', prefix: `ATTACH ${sqlString(threadsDb)} AS tdb;\n` };
|
|
72
|
+
}
|
|
73
|
+
return { goalsDb, threadsTable: null, prefix: '' };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function runGoalQuery(args, buildSql) {
|
|
77
|
+
const ctx = goalQueryContext(args);
|
|
78
|
+
return runSqliteJson(ctx.goalsDb, ctx.prefix + buildSql(ctx.threadsTable));
|
|
36
79
|
}
|
|
37
80
|
|
|
38
81
|
function defaultRunsDir(args = []) {
|
|
@@ -81,7 +124,11 @@ function ensureStateDb(dbPath) {
|
|
|
81
124
|
}
|
|
82
125
|
}
|
|
83
126
|
|
|
84
|
-
function selectGoalSql(whereClause, limit = 10) {
|
|
127
|
+
function selectGoalSql(whereClause, limit = 10, threadsTable = 'threads') {
|
|
128
|
+
const join = threadsTable ? `LEFT JOIN ${threadsTable} t ON t.id = tg.thread_id` : '';
|
|
129
|
+
const cwd = threadsTable ? 't.cwd' : 'NULL';
|
|
130
|
+
const title = threadsTable ? 't.title' : 'NULL';
|
|
131
|
+
const threadUpdated = threadsTable ? 't.updated_at_ms' : 'NULL';
|
|
85
132
|
return `
|
|
86
133
|
SELECT
|
|
87
134
|
tg.thread_id,
|
|
@@ -93,41 +140,43 @@ SELECT
|
|
|
93
140
|
tg.time_used_seconds,
|
|
94
141
|
tg.created_at_ms,
|
|
95
142
|
tg.updated_at_ms,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
143
|
+
${cwd} AS thread_cwd,
|
|
144
|
+
${title} AS thread_title,
|
|
145
|
+
${threadUpdated} AS thread_updated_at_ms
|
|
99
146
|
FROM thread_goals tg
|
|
100
|
-
|
|
147
|
+
${join}
|
|
101
148
|
${whereClause}
|
|
102
|
-
ORDER BY COALESCE(
|
|
149
|
+
ORDER BY COALESCE(${threadUpdated}, tg.updated_at_ms) DESC
|
|
103
150
|
LIMIT ${Number(limit) || 10}
|
|
104
151
|
`;
|
|
105
152
|
}
|
|
106
153
|
|
|
107
|
-
function readGoalByThread(
|
|
108
|
-
const rows =
|
|
154
|
+
function readGoalByThread(args, threadId) {
|
|
155
|
+
const rows = runGoalQuery(args, (tt) => selectGoalSql(`WHERE tg.thread_id = ${sqlString(threadId)}`, 1, tt));
|
|
109
156
|
return rows[0] || null;
|
|
110
157
|
}
|
|
111
158
|
|
|
112
|
-
function readLatestGoalForCwd(
|
|
159
|
+
function readLatestGoalForCwd(args, cwd) {
|
|
160
|
+
const ctx = goalQueryContext(args);
|
|
161
|
+
if (!ctx.threadsTable) return null; // cannot cwd-match without thread metadata
|
|
113
162
|
const realCwd = fs.realpathSync.native ? fs.realpathSync.native(cwd) : fs.realpathSync(cwd);
|
|
114
163
|
const pwd = process.env.PWD || '';
|
|
115
164
|
const pwdReal = pwd && fs.existsSync(pwd) ? (fs.realpathSync.native ? fs.realpathSync.native(pwd) : fs.realpathSync(pwd)) : '';
|
|
116
165
|
const candidates = [...new Set([cwd, realCwd, pwdReal === realCwd ? pwd : ''].filter(Boolean))];
|
|
117
|
-
const rows = runSqliteJson(
|
|
166
|
+
const rows = runSqliteJson(ctx.goalsDb, ctx.prefix + selectGoalSql(`WHERE t.cwd IN (${candidates.map(sqlString).join(', ')})`, 1, ctx.threadsTable));
|
|
118
167
|
return rows[0] || null;
|
|
119
168
|
}
|
|
120
169
|
|
|
121
|
-
function readRecentGoals(
|
|
122
|
-
return
|
|
170
|
+
function readRecentGoals(args, limit = 10) {
|
|
171
|
+
return runGoalQuery(args, (tt) => selectGoalSql('', limit, tt));
|
|
123
172
|
}
|
|
124
173
|
|
|
125
|
-
function resolveThreadGoal(
|
|
174
|
+
function resolveThreadGoal(args) {
|
|
126
175
|
const explicitThread = readFlag(args, '--thread', '');
|
|
127
|
-
if (explicitThread) return readGoalByThread(
|
|
128
|
-
if (hasFlag(args, '--latest')) return readLatestGoalForCwd(
|
|
176
|
+
if (explicitThread) return readGoalByThread(args, explicitThread);
|
|
177
|
+
if (hasFlag(args, '--latest')) return readLatestGoalForCwd(args, process.cwd());
|
|
129
178
|
const envThread = process.env.CODEX_THREAD_ID || '';
|
|
130
|
-
if (envThread) return readGoalByThread(
|
|
179
|
+
if (envThread) return readGoalByThread(args, envThread);
|
|
131
180
|
return null;
|
|
132
181
|
}
|
|
133
182
|
|
|
@@ -163,7 +212,7 @@ function statusCommand(args) {
|
|
|
163
212
|
const dbPath = resolveStatePath(args);
|
|
164
213
|
ensureStateDb(dbPath);
|
|
165
214
|
|
|
166
|
-
const goal = resolveThreadGoal(
|
|
215
|
+
const goal = resolveThreadGoal(args);
|
|
167
216
|
if (goal) {
|
|
168
217
|
const payload = { ok: true, schema: SCHEMA, action: 'status', state_path: dbPath, goal };
|
|
169
218
|
printJsonOrText(payload, [
|
|
@@ -176,7 +225,7 @@ function statusCommand(args) {
|
|
|
176
225
|
}
|
|
177
226
|
|
|
178
227
|
const limit = Math.max(1, Math.min(50, Number(readFlag(args, '--limit', '10')) || 10));
|
|
179
|
-
const goals = readRecentGoals(
|
|
228
|
+
const goals = readRecentGoals(args, limit);
|
|
180
229
|
const payload = { ok: true, schema: SCHEMA, action: 'status', state_path: dbPath, goals };
|
|
181
230
|
printJsonOrText(payload, [
|
|
182
231
|
`Codex goals: ${goals.length} recent`,
|
|
@@ -192,7 +241,7 @@ function resetCommand(args) {
|
|
|
192
241
|
ensureStateDb(dbPath);
|
|
193
242
|
|
|
194
243
|
const startedAt = new Date().toISOString();
|
|
195
|
-
const goal = resolveThreadGoal(
|
|
244
|
+
const goal = resolveThreadGoal(args);
|
|
196
245
|
if (!goal) {
|
|
197
246
|
throw new Error('No Codex goal found. Pass --thread <thread-id> or --latest.');
|
|
198
247
|
}
|
|
@@ -238,7 +287,7 @@ SELECT changes() AS deleted;
|
|
|
238
287
|
COMMIT;
|
|
239
288
|
`, { readonly: false });
|
|
240
289
|
const deleted = Number(rows[0]?.deleted || 0);
|
|
241
|
-
const remaining = readGoalByThread(
|
|
290
|
+
const remaining = readGoalByThread(args, goal.thread_id);
|
|
242
291
|
const ok = deleted === 1 && !remaining;
|
|
243
292
|
const payload = writeReceipt(outDir, {
|
|
244
293
|
ok,
|
|
@@ -277,7 +326,8 @@ function usage() {
|
|
|
277
326
|
` atris codex-goal reset --thread <id> ${CONFIRM_RESET_FLAG}`,
|
|
278
327
|
'',
|
|
279
328
|
'Flags:',
|
|
280
|
-
' --state <path> Codex
|
|
329
|
+
' --state <path> Codex goals DB (default ~/.codex/goals_1.sqlite, falls back to state_5.sqlite)',
|
|
330
|
+
' --threads-db <path> Codex thread metadata DB for cwd/title (default ~/.codex/state_5.sqlite)',
|
|
281
331
|
' --latest Use the latest Codex goal whose thread cwd matches the current directory',
|
|
282
332
|
' --out-dir <path> Receipt/backup directory (default .atris/runs)',
|
|
283
333
|
'',
|
package/commands/computer.js
CHANGED
|
@@ -1635,6 +1635,41 @@ async function resolveBusinessContextBySlug(token, slug, options = {}) {
|
|
|
1635
1635
|
return null;
|
|
1636
1636
|
}
|
|
1637
1637
|
|
|
1638
|
+
function shortcutBusinessLabel(slug, fallbackName) {
|
|
1639
|
+
if (normalizeBusinessSlug(slug) === RECRUITING_BUSINESS_SLUG) return 'Atris Labs';
|
|
1640
|
+
return fallbackName || slug;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
async function resolveSingleBusinessShortcutFallback(token, slug) {
|
|
1644
|
+
const wantedSlug = normalizeBusinessSlug(slug);
|
|
1645
|
+
if (wantedSlug !== RECRUITING_BUSINESS_SLUG) return null;
|
|
1646
|
+
|
|
1647
|
+
const list = await apiRequestJson('/business/', { method: 'GET', token });
|
|
1648
|
+
const rows = list.ok && Array.isArray(list.data)
|
|
1649
|
+
? list.data.filter((business) => business && business.id)
|
|
1650
|
+
: [];
|
|
1651
|
+
if (rows.length !== 1) return null;
|
|
1652
|
+
|
|
1653
|
+
const match = rows[0];
|
|
1654
|
+
const businesses = loadBusinesses();
|
|
1655
|
+
const businessName = shortcutBusinessLabel(wantedSlug, match.name || match.slug);
|
|
1656
|
+
businesses[wantedSlug] = {
|
|
1657
|
+
business_id: match.id,
|
|
1658
|
+
workspace_id: match.workspace_id,
|
|
1659
|
+
name: businessName,
|
|
1660
|
+
slug: wantedSlug,
|
|
1661
|
+
cloud_slug: match.slug || null,
|
|
1662
|
+
added_at: new Date().toISOString(),
|
|
1663
|
+
};
|
|
1664
|
+
saveBusinesses(businesses);
|
|
1665
|
+
return {
|
|
1666
|
+
slug: wantedSlug,
|
|
1667
|
+
businessId: match.id,
|
|
1668
|
+
workspaceId: match.workspace_id,
|
|
1669
|
+
businessName,
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1638
1673
|
async function resolveComputerCommandContext(token, options = {}) {
|
|
1639
1674
|
if (options.businessSlug || options.workspaceId) {
|
|
1640
1675
|
const ctx = options.businessSlug
|
|
@@ -1660,7 +1695,10 @@ async function resolveTypedBusinessComputerContext(token, options = {}, defaults
|
|
|
1660
1695
|
return resolveComputerCommandContext(token, { ...options, businessSlug });
|
|
1661
1696
|
}
|
|
1662
1697
|
|
|
1663
|
-
|
|
1698
|
+
let ctx = await resolveBusinessContextBySlug(token, businessSlug, { preferCache: true });
|
|
1699
|
+
if (!ctx?.businessId && (options.allowCanonicalShortcutFallback || defaults.allowCanonicalShortcutFallback)) {
|
|
1700
|
+
ctx = await resolveSingleBusinessShortcutFallback(token, businessSlug);
|
|
1701
|
+
}
|
|
1664
1702
|
if (!ctx?.businessId) return null;
|
|
1665
1703
|
const workspaces = await listBusinessWorkspaces(token, ctx);
|
|
1666
1704
|
const workspace = resolveWorkspaceByComputerType(workspaces, computerType);
|
|
@@ -1705,7 +1743,7 @@ async function resolveWorkspaceSelector(token, ctx, input) {
|
|
|
1705
1743
|
return workspace?.id || selector;
|
|
1706
1744
|
}
|
|
1707
1745
|
|
|
1708
|
-
async function resolveBusinessOwnerForCreate(token, businessSlug = null) {
|
|
1746
|
+
async function resolveBusinessOwnerForCreate(token, businessSlug = null, options = {}) {
|
|
1709
1747
|
const wantedSlug = businessSlug ? String(businessSlug).trim() : null;
|
|
1710
1748
|
if (wantedSlug) {
|
|
1711
1749
|
const fromApi = await resolveBusinessContextBySlug(token, wantedSlug);
|
|
@@ -1720,6 +1758,9 @@ async function resolveBusinessOwnerForCreate(token, businessSlug = null) {
|
|
|
1720
1758
|
businessName: cached.name || cached.slug || wantedSlug,
|
|
1721
1759
|
};
|
|
1722
1760
|
}
|
|
1761
|
+
if (options.allowCanonicalShortcutFallback) {
|
|
1762
|
+
return resolveSingleBusinessShortcutFallback(token, wantedSlug);
|
|
1763
|
+
}
|
|
1723
1764
|
return null;
|
|
1724
1765
|
}
|
|
1725
1766
|
|
|
@@ -2248,7 +2289,9 @@ async function computerCreate(token, args = [], defaults = {}) {
|
|
|
2248
2289
|
return;
|
|
2249
2290
|
}
|
|
2250
2291
|
|
|
2251
|
-
const ctx = await resolveBusinessOwnerForCreate(token, options.businessSlug
|
|
2292
|
+
const ctx = await resolveBusinessOwnerForCreate(token, options.businessSlug, {
|
|
2293
|
+
allowCanonicalShortcutFallback: Boolean(defaults.allowCanonicalShortcutFallback),
|
|
2294
|
+
});
|
|
2252
2295
|
if (!ctx?.businessId) {
|
|
2253
2296
|
console.error('No business found.');
|
|
2254
2297
|
console.error('Run inside a bound business workspace or pass: --business <slug>');
|
|
@@ -3874,6 +3917,7 @@ async function runRecruitingComputerShortcut(token, args, cloudOptions = {}) {
|
|
|
3874
3917
|
return computerCreate(token, finalArgs, {
|
|
3875
3918
|
businessSlug: cloudOptions.businessSlug || RECRUITING_BUSINESS_SLUG,
|
|
3876
3919
|
computerType: 'recruiting',
|
|
3920
|
+
allowCanonicalShortcutFallback: !cloudOptions.businessSlug,
|
|
3877
3921
|
});
|
|
3878
3922
|
}
|
|
3879
3923
|
|
|
@@ -3888,6 +3932,7 @@ async function runRecruitingComputerShortcut(token, args, cloudOptions = {}) {
|
|
|
3888
3932
|
const ctx = await resolveTypedBusinessComputerContext(token, recruitingOptions, {
|
|
3889
3933
|
businessSlug: RECRUITING_BUSINESS_SLUG,
|
|
3890
3934
|
computerType: 'recruiting',
|
|
3935
|
+
allowCanonicalShortcutFallback: !recruitingOptions.businessSlug,
|
|
3891
3936
|
});
|
|
3892
3937
|
if (!ctx?.businessId) {
|
|
3893
3938
|
console.error('Atris Recruiting is not available for this account.');
|
package/commands/gm.js
CHANGED
|
@@ -331,7 +331,7 @@ function gmState(args = []) {
|
|
|
331
331
|
missions,
|
|
332
332
|
review_queue: reviewQueue,
|
|
333
333
|
next_commands: commands,
|
|
334
|
-
xp_rule: 'GM can route missions and review proof, but AgentXP
|
|
334
|
+
xp_rule: 'GM can route missions and review proof, but AgentXP is awarded only after human approval.',
|
|
335
335
|
global_sync_rule: AGENTXP_GLOBAL_SYNC_RULE,
|
|
336
336
|
leaderboard_url: AGENTXP_LEADERBOARD_URL,
|
|
337
337
|
};
|