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.
@@ -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 = await chooseCloudLane(token, ctx, initialOptions);
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) === 'claude' ? await describeClaudeAuth(token, ctx) : null;
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 (isCodeOps) {
2606
- printCodeOpsStartPanel(ctx, worker, model, billingLabel, authSummary);
2607
- } else {
2608
- printCloudStartPanel(ctx, worker, model, billingLabel, authSummary);
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;
@@ -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 result = spawnSync(command, {
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 && !TERMINAL_STATUSES.has(mission.status) && mission.status !== 'paused';
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', 'Blocked'].includes(section)) {
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 = countOpenTodoItems(todoPath);
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 = countOpenTodoItems(workspace.todoPath);
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,