atris 3.15.46 → 3.15.49
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 +2 -2
- package/atris/skills/atris/SKILL.md +1 -1
- package/atris/wiki/index.md +2 -0
- package/bin/atris.js +12 -4
- package/commands/brain.js +77 -3
- package/commands/business.js +574 -21
- package/commands/computer.js +23 -17
- package/commands/mission.js +18 -2
- package/commands/now.js +45 -5
- package/commands/radar.js +754 -0
- package/commands/sync.js +101 -1
- package/commands/task.js +84 -0
- package/lib/task-db.js +29 -3
- package/package.json +2 -1
- package/templates/business-starter/MAP.md +1 -0
- package/templates/business-starter/team/README.md +3 -0
- package/templates/business-starter/team/START_HERE.md +45 -0
package/commands/computer.js
CHANGED
|
@@ -1457,12 +1457,12 @@ async function runBusinessPromptViaRunnerProxy(token, ctx, prompt, options = {})
|
|
|
1457
1457
|
return { ok: false, error: 'runner proxy timed out', status: 0 };
|
|
1458
1458
|
}
|
|
1459
1459
|
|
|
1460
|
-
async function ensureBusinessAwake(token, ctx, maxWaitSec = 90) {
|
|
1460
|
+
async function ensureBusinessAwake(token, ctx, maxWaitSec = 90, options = {}) {
|
|
1461
1461
|
const status = await apiRequestJson(`/business/${ctx.businessId}/ai-computer/status`, { method: 'GET', token });
|
|
1462
1462
|
if (status.ok && status.data && status.data.status === 'running' && status.data.endpoint) {
|
|
1463
1463
|
return true;
|
|
1464
1464
|
}
|
|
1465
|
-
process.stdout.write(' Waking business computer... ');
|
|
1465
|
+
if (!options.quiet) process.stdout.write(' Waking business computer... ');
|
|
1466
1466
|
await apiRequestJson(`/business/${ctx.businessId}/ai-computer/wake`, { method: 'POST', token, body: {} });
|
|
1467
1467
|
const start = Date.now();
|
|
1468
1468
|
while (Date.now() - start < maxWaitSec * 1000) {
|
|
@@ -1470,12 +1470,12 @@ async function ensureBusinessAwake(token, ctx, maxWaitSec = 90) {
|
|
|
1470
1470
|
const next = await apiRequestJson(`/business/${ctx.businessId}/ai-computer/status`, { method: 'GET', token });
|
|
1471
1471
|
if (next.ok && next.data && next.data.status === 'running' && next.data.endpoint) {
|
|
1472
1472
|
const elapsed = Math.floor((Date.now() - start) / 1000);
|
|
1473
|
-
console.log(`awake (${elapsed}s)`);
|
|
1473
|
+
if (!options.quiet) console.log(`awake (${elapsed}s)`);
|
|
1474
1474
|
await bootstrapBusinessComputerRuntime(token, ctx, 'computer-auto-wake');
|
|
1475
1475
|
return true;
|
|
1476
1476
|
}
|
|
1477
1477
|
}
|
|
1478
|
-
console.log('timeout');
|
|
1478
|
+
if (!options.quiet) console.log('timeout');
|
|
1479
1479
|
return false;
|
|
1480
1480
|
}
|
|
1481
1481
|
|
|
@@ -2420,7 +2420,7 @@ async function computerAudit(token, ctx, limit = 10) {
|
|
|
2420
2420
|
printBusinessChatAudit(result.data?.rows || []);
|
|
2421
2421
|
}
|
|
2422
2422
|
|
|
2423
|
-
async function streamBusinessChatResult(token, ctx, executionId, rl = null) {
|
|
2423
|
+
async function streamBusinessChatResult(token, ctx, executionId, rl = null, options = {}) {
|
|
2424
2424
|
let fromIndex = 0;
|
|
2425
2425
|
let errors = 0;
|
|
2426
2426
|
let cancelling = false;
|
|
@@ -2458,7 +2458,7 @@ async function streamBusinessChatResult(token, ctx, executionId, rl = null) {
|
|
|
2458
2458
|
const sigintTarget = rl || process;
|
|
2459
2459
|
sigintTarget.on('SIGINT', onSigint);
|
|
2460
2460
|
|
|
2461
|
-
console.log(ui.dim('Running on cloud. Ctrl-C interrupts this run.'));
|
|
2461
|
+
if (!options.quiet) console.log(ui.dim('Running on cloud. Ctrl-C interrupts this run.'));
|
|
2462
2462
|
|
|
2463
2463
|
try {
|
|
2464
2464
|
while (true) {
|
|
@@ -2486,7 +2486,7 @@ async function streamBusinessChatResult(token, ctx, executionId, rl = null) {
|
|
|
2486
2486
|
} else if (event.type === 'result' && event.result && !sawVisibleOutput) {
|
|
2487
2487
|
sawVisibleOutput = true;
|
|
2488
2488
|
process.stdout.write(String(event.result));
|
|
2489
|
-
} else if (event.type === 'tool_use' && event.tool) {
|
|
2489
|
+
} else if (!options.quiet && event.type === 'tool_use' && event.tool) {
|
|
2490
2490
|
const arg = event.input?.file_path || event.input?.path || event.input?.pattern || event.input?.command || '';
|
|
2491
2491
|
if (arg) {
|
|
2492
2492
|
console.log(`\n [${event.tool}] ${String(arg).slice(0, 120)}`);
|
|
@@ -2566,7 +2566,7 @@ async function sendBusinessChat(token, ctx, message, sessionId, resetContext = f
|
|
|
2566
2566
|
const nextSessionId = data.session_id || sessionId;
|
|
2567
2567
|
if (rl) rl.pause();
|
|
2568
2568
|
try {
|
|
2569
|
-
await streamBusinessChatResult(token, ctx, data.execution_id, rl);
|
|
2569
|
+
await streamBusinessChatResult(token, ctx, data.execution_id, rl, { quiet: Boolean(options.quiet) });
|
|
2570
2570
|
} finally {
|
|
2571
2571
|
if (rl) rl.resume();
|
|
2572
2572
|
}
|
|
@@ -2584,28 +2584,33 @@ async function computerChat(token, ctx, initialOptions = {}) {
|
|
|
2584
2584
|
const chatSystemPrompt = isCodeOps
|
|
2585
2585
|
? appendSystemPrompt(initialOptions.systemPrompt, CODEOPS_WORKFLOW_PROMPT)
|
|
2586
2586
|
: initialOptions.systemPrompt;
|
|
2587
|
+
const oneShotMessage = initialOptions.message != null;
|
|
2587
2588
|
let sessionId = `biz-${ctx.businessId.slice(0, 8)}-${Date.now().toString(36)}`;
|
|
2588
2589
|
const pipedInput = initialOptions.message != null ? null : await readPipedStdin();
|
|
2589
2590
|
const scriptedInput = initialOptions.message != null ? String(initialOptions.message) : pipedInput;
|
|
2590
|
-
printCloudWordmark();
|
|
2591
|
-
const selection =
|
|
2591
|
+
if (!oneShotMessage) printCloudWordmark();
|
|
2592
|
+
const selection = oneShotMessage
|
|
2593
|
+
? { worker: initialOptions.worker, model: initialOptions.model }
|
|
2594
|
+
: await chooseCloudLane(token, ctx, initialOptions);
|
|
2592
2595
|
if (selection.cancelled) return;
|
|
2593
2596
|
let worker = activeWorker(selection.worker);
|
|
2594
2597
|
let model = selection.model || null;
|
|
2595
2598
|
let awaitingLoginCode = false;
|
|
2596
|
-
let billingLabel = await describeBillingMode(token, ctx, worker);
|
|
2597
|
-
let authSummary = activeWorker(worker)
|
|
2599
|
+
let billingLabel = oneShotMessage ? null : await describeBillingMode(token, ctx, worker);
|
|
2600
|
+
let authSummary = oneShotMessage || activeWorker(worker) !== 'claude' ? null : await describeClaudeAuth(token, ctx);
|
|
2598
2601
|
|
|
2599
|
-
const awake = await ensureBusinessAwake(token, ctx);
|
|
2602
|
+
const awake = await ensureBusinessAwake(token, ctx, 90, { quiet: oneShotMessage });
|
|
2600
2603
|
if (!awake) {
|
|
2601
2604
|
console.error(' Computer did not become ready in time.');
|
|
2602
2605
|
return;
|
|
2603
2606
|
}
|
|
2604
2607
|
|
|
2605
|
-
if (
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2608
|
+
if (!oneShotMessage) {
|
|
2609
|
+
if (isCodeOps) {
|
|
2610
|
+
printCodeOpsStartPanel(ctx, worker, model, billingLabel, authSummary);
|
|
2611
|
+
} else {
|
|
2612
|
+
printCloudStartPanel(ctx, worker, model, billingLabel, authSummary);
|
|
2613
|
+
}
|
|
2609
2614
|
}
|
|
2610
2615
|
|
|
2611
2616
|
if (scriptedInput !== null) {
|
|
@@ -2640,6 +2645,7 @@ async function computerChat(token, ctx, initialOptions = {}) {
|
|
|
2640
2645
|
model,
|
|
2641
2646
|
systemPrompt: chatSystemPrompt,
|
|
2642
2647
|
allowedTools: initialOptions.allowedTools,
|
|
2648
|
+
quiet: oneShotMessage,
|
|
2643
2649
|
});
|
|
2644
2650
|
}
|
|
2645
2651
|
return;
|
package/commands/mission.js
CHANGED
|
@@ -7,6 +7,7 @@ const { spawn, spawnSync } = require('child_process');
|
|
|
7
7
|
|
|
8
8
|
const VALID_STATUSES = new Set(['planning', 'running', 'ready', 'paused', 'blocked', 'stopped', 'complete']);
|
|
9
9
|
const TERMINAL_STATUSES = new Set(['stopped', 'complete']);
|
|
10
|
+
const GOAL_LOOP_STATUSES = new Set(['planning', 'running', 'ready']);
|
|
10
11
|
const STATUS_ALIASES = new Set(['active']);
|
|
11
12
|
|
|
12
13
|
function stampIso() {
|
|
@@ -622,9 +623,23 @@ function writeReceipt(mission, result, root = process.cwd()) {
|
|
|
622
623
|
return path.relative(root, receiptPath);
|
|
623
624
|
}
|
|
624
625
|
|
|
626
|
+
function shellQuote(value) {
|
|
627
|
+
return `'${String(value || '').replace(/'/g, `'\\''`)}'`;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function resolveVerifierCommand(command) {
|
|
631
|
+
const raw = String(command || '');
|
|
632
|
+
const leading = raw.match(/^\s*/)?.[0] || '';
|
|
633
|
+
const trimmed = raw.trimStart();
|
|
634
|
+
if (!trimmed || !/^atris(?:\s|$)/.test(trimmed)) return raw;
|
|
635
|
+
const cliPath = path.resolve(__dirname, '..', 'bin', 'atris.js');
|
|
636
|
+
return `${leading}${shellQuote(process.execPath)} ${shellQuote(cliPath)}${trimmed.slice('atris'.length)}`;
|
|
637
|
+
}
|
|
638
|
+
|
|
625
639
|
function runVerifier(command, root = process.cwd()) {
|
|
626
640
|
if (!command) return null;
|
|
627
|
-
const
|
|
641
|
+
const resolvedCommand = resolveVerifierCommand(command);
|
|
642
|
+
const result = spawnSync(resolvedCommand, {
|
|
628
643
|
cwd: root,
|
|
629
644
|
shell: true,
|
|
630
645
|
encoding: 'utf8',
|
|
@@ -633,6 +648,7 @@ function runVerifier(command, root = process.cwd()) {
|
|
|
633
648
|
});
|
|
634
649
|
return {
|
|
635
650
|
command,
|
|
651
|
+
resolved_command: resolvedCommand === command ? null : resolvedCommand,
|
|
636
652
|
status: result.status,
|
|
637
653
|
signal: result.signal || null,
|
|
638
654
|
passed: result.status === 0,
|
|
@@ -748,7 +764,7 @@ function secondsUntilMissionDue(mission, now = new Date()) {
|
|
|
748
764
|
}
|
|
749
765
|
|
|
750
766
|
function missionIsRunnable(mission) {
|
|
751
|
-
return mission &&
|
|
767
|
+
return mission && GOAL_LOOP_STATUSES.has(String(mission.status || ''));
|
|
752
768
|
}
|
|
753
769
|
|
|
754
770
|
function missionSortTime(mission) {
|
package/commands/now.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
4
|
const NOW_PATH = path.join('atris', 'now.md');
|
|
5
|
+
const EXECUTABLE_TASK_STATUSES = new Set(['open', 'claimed']);
|
|
5
6
|
|
|
6
7
|
function formatLocalDate(date = new Date()) {
|
|
7
8
|
const year = String(date.getFullYear());
|
|
@@ -79,7 +80,7 @@ function countOpenTodoItems(filePath) {
|
|
|
79
80
|
}
|
|
80
81
|
const isTaskBullet = /^-\s+(?:\[[ ]\]\s+)?\*\*.+?\*\*/.test(line);
|
|
81
82
|
if (!isTaskBullet) continue;
|
|
82
|
-
if (!hasRenderedSections || ['Backlog', 'In Progress'
|
|
83
|
+
if (!hasRenderedSections || ['Backlog', 'In Progress'].includes(section)) {
|
|
83
84
|
count += 1;
|
|
84
85
|
}
|
|
85
86
|
}
|
|
@@ -87,6 +88,25 @@ function countOpenTodoItems(filePath) {
|
|
|
87
88
|
return count;
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
function countTaskProjectionItems(root = process.cwd()) {
|
|
92
|
+
const projectionPath = path.join(root, '.atris', 'state', 'tasks.projection.json');
|
|
93
|
+
if (!fs.existsSync(projectionPath)) return null;
|
|
94
|
+
try {
|
|
95
|
+
const projection = JSON.parse(fs.readFileSync(projectionPath, 'utf8'));
|
|
96
|
+
const tasks = Array.isArray(projection?.tasks) ? projection.tasks : null;
|
|
97
|
+
if (!tasks) return null;
|
|
98
|
+
return tasks.filter(task => EXECUTABLE_TASK_STATUSES.has(String(task?.status || '').toLowerCase())).length;
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function countOpenWorkItems(root = process.cwd(), todoPath = path.join(root, 'atris', 'TODO.md')) {
|
|
105
|
+
const projectionCount = countTaskProjectionItems(root);
|
|
106
|
+
if (projectionCount !== null) return projectionCount;
|
|
107
|
+
return countOpenTodoItems(todoPath);
|
|
108
|
+
}
|
|
109
|
+
|
|
90
110
|
function countJournalCompletedReceipts(filePath) {
|
|
91
111
|
if (!fs.existsSync(filePath)) return 0;
|
|
92
112
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
@@ -107,7 +127,7 @@ function renderDefaultNow(root = process.cwd()) {
|
|
|
107
127
|
const mapHeading = readFirstHeading(path.join(atrisDir, 'MAP.md')) || 'MAP not filled yet';
|
|
108
128
|
const todoPath = path.join(atrisDir, 'TODO.md');
|
|
109
129
|
const journalPath = currentJournalPath(root);
|
|
110
|
-
const openTodoCount =
|
|
130
|
+
const openTodoCount = countOpenWorkItems(root, todoPath);
|
|
111
131
|
const inboxCount = countMatches(journalPath, /^-\s+\*\*I\d+:/gm);
|
|
112
132
|
const completedCount = countJournalCompletedReceipts(journalPath);
|
|
113
133
|
const generated = todayIso();
|
|
@@ -161,7 +181,7 @@ function renderPortfolioNow(root = process.cwd()) {
|
|
|
161
181
|
const generated = todayIso();
|
|
162
182
|
const lines = workspaces.map((workspace) => {
|
|
163
183
|
const heading = readFirstHeading(workspace.mapPath) || workspace.slug;
|
|
164
|
-
const todoCount =
|
|
184
|
+
const todoCount = countOpenWorkItems(workspace.root, workspace.todoPath);
|
|
165
185
|
const nowState = fs.existsSync(workspace.nowPath) ? 'has now.md' : 'needs now.md';
|
|
166
186
|
return `- ${workspace.slug}: ${heading}; ${todoCount} open TODO item${todoCount === 1 ? '' : 's'}; ${nowState}.`;
|
|
167
187
|
});
|
|
@@ -201,6 +221,18 @@ ${workspaces.map((workspace) => `- \`${workspace.slug}/atris/MAP.md\``).join('\n
|
|
|
201
221
|
`;
|
|
202
222
|
}
|
|
203
223
|
|
|
224
|
+
function isGeneratedNowFile(content) {
|
|
225
|
+
const text = String(content || '');
|
|
226
|
+
const hasGeneratedSignature = (
|
|
227
|
+
text.includes('> Current operating truth for this workspace.') ||
|
|
228
|
+
text.includes('> Current operating truth for this portfolio of Atris workspaces.')
|
|
229
|
+
) && text.includes('## Receipts');
|
|
230
|
+
const hasLegacyGeneratedCounters = /^#\s+now\s*$/m.test(text)
|
|
231
|
+
&& /Open TODO items:\s*\d+/m.test(text)
|
|
232
|
+
&& /Completed receipts today:\s*\d+/m.test(text);
|
|
233
|
+
return hasGeneratedSignature || hasLegacyGeneratedCounters;
|
|
234
|
+
}
|
|
235
|
+
|
|
204
236
|
function ensureNowFile(root = process.cwd()) {
|
|
205
237
|
let atrisDir = path.join(root, 'atris');
|
|
206
238
|
const isWorkspace = fs.existsSync(atrisDir) && hasWorkspaceMarkers(atrisDir);
|
|
@@ -220,7 +252,7 @@ function ensureNowFile(root = process.cwd()) {
|
|
|
220
252
|
return { created: false, path: nowPath };
|
|
221
253
|
}
|
|
222
254
|
|
|
223
|
-
function refreshNowFile(root = process.cwd()) {
|
|
255
|
+
function refreshNowFile(root = process.cwd(), options = {}) {
|
|
224
256
|
const atrisDir = path.join(root, 'atris');
|
|
225
257
|
const isWorkspace = fs.existsSync(atrisDir) && hasWorkspaceMarkers(atrisDir);
|
|
226
258
|
const childWorkspaces = isWorkspace ? [] : findChildWorkspaces(root);
|
|
@@ -231,9 +263,15 @@ function refreshNowFile(root = process.cwd()) {
|
|
|
231
263
|
fs.mkdirSync(atrisDir, { recursive: true });
|
|
232
264
|
}
|
|
233
265
|
const nowPath = path.join(atrisDir, 'now.md');
|
|
266
|
+
if (options.preserveCustom && fs.existsSync(nowPath)) {
|
|
267
|
+
const current = fs.readFileSync(nowPath, 'utf8');
|
|
268
|
+
if (!isGeneratedNowFile(current)) {
|
|
269
|
+
return { path: nowPath, preserved: true };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
234
272
|
const content = isWorkspace ? renderDefaultNow(root) : renderPortfolioNow(root);
|
|
235
273
|
fs.writeFileSync(nowPath, content, 'utf8');
|
|
236
|
-
return { path: nowPath };
|
|
274
|
+
return { path: nowPath, preserved: false };
|
|
237
275
|
}
|
|
238
276
|
|
|
239
277
|
function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
|
|
@@ -295,8 +333,10 @@ module.exports = {
|
|
|
295
333
|
ensureNowFile,
|
|
296
334
|
formatLocalDate,
|
|
297
335
|
countJournalCompletedReceipts,
|
|
336
|
+
countOpenWorkItems,
|
|
298
337
|
countOpenTodoItems,
|
|
299
338
|
findChildWorkspaces,
|
|
339
|
+
isGeneratedNowFile,
|
|
300
340
|
nowAtris,
|
|
301
341
|
refreshNowFile,
|
|
302
342
|
renderDefaultNow,
|