atris 3.15.56 → 3.15.57

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/bin/atris.js CHANGED
@@ -1567,7 +1567,7 @@ if (command === 'init') {
1567
1567
  searchJournal(keyword);
1568
1568
  } else if (command === 'xp') {
1569
1569
  require('../commands/xp').xpCommand(...process.argv.slice(3))
1570
- .then(() => process.exit(0))
1570
+ .then(() => { process.exitCode = 0; })
1571
1571
  .catch((err) => { console.error(`✗ Error: ${err.message || err}`); process.exit(1); });
1572
1572
  } else if (command === 'play') {
1573
1573
  require('../commands/play').playCommand(...process.argv.slice(3))
@@ -2550,34 +2550,56 @@ async function streamBusinessChatResult(token, ctx, executionId, rl = null, opti
2550
2550
 
2551
2551
  errors = 0;
2552
2552
  let done = false;
2553
- for (const event of (events.data?.events || [])) {
2554
- fromIndex++;
2555
- if (event.type === 'assistant_text' && event.content) {
2556
- sawVisibleOutput = true;
2557
- process.stdout.write(event.content);
2558
- } else if (event.type === 'result' && event.result && !sawVisibleOutput) {
2559
- sawVisibleOutput = true;
2560
- process.stdout.write(String(event.result));
2561
- } else if (!options.quiet && event.type === 'tool_use' && event.tool) {
2562
- const arg = event.input?.file_path || event.input?.path || event.input?.pattern || event.input?.command || '';
2563
- if (arg) {
2564
- console.log(`\n [${event.tool}] ${String(arg).slice(0, 120)}`);
2565
- } else {
2566
- console.log(`\n [${event.tool}]`);
2553
+ const emitEvents = (items, { showTools = true } = {}) => {
2554
+ let batchDone = false;
2555
+ for (const event of (items || [])) {
2556
+ if ((event.type === 'assistant_text' || event.type === 'text') && event.content) {
2557
+ sawVisibleOutput = true;
2558
+ process.stdout.write(event.content);
2559
+ } else if (event.type === 'result' && event.result && !sawVisibleOutput) {
2560
+ sawVisibleOutput = true;
2561
+ process.stdout.write(String(event.result));
2562
+ } else if (showTools && !options.quiet && event.type === 'tool_use' && event.tool) {
2563
+ const arg = event.input?.file_path || event.input?.path || event.input?.pattern || event.input?.command || '';
2564
+ if (arg) {
2565
+ console.log(`\n [${event.tool}] ${String(arg).slice(0, 120)}`);
2566
+ } else {
2567
+ console.log(`\n [${event.tool}]`);
2568
+ }
2569
+ } else if (event.type === 'error') {
2570
+ if (event.error) console.error(`\n${event.error}`);
2571
+ terminalStatus = 'error';
2572
+ batchDone = true;
2573
+ break;
2574
+ } else if (event.type === 'complete') {
2575
+ terminalStatus = 'completed';
2576
+ batchDone = true;
2577
+ break;
2567
2578
  }
2568
- } else if (event.type === 'error') {
2569
- if (event.error) console.error(`\n${event.error}`);
2570
- terminalStatus = 'error';
2571
- done = true;
2572
- break;
2573
- } else if (event.type === 'complete') {
2574
- terminalStatus = 'completed';
2575
- done = true;
2576
- break;
2577
2579
  }
2580
+ return batchDone;
2581
+ };
2582
+
2583
+ const batch = events.data?.events || [];
2584
+ done = emitEvents(batch);
2585
+ const nextIndex = events.data?.next_index;
2586
+ if (Number.isInteger(nextIndex) && nextIndex >= fromIndex) {
2587
+ fromIndex = nextIndex;
2588
+ } else {
2589
+ fromIndex += batch.length;
2578
2590
  }
2579
2591
 
2580
2592
  if (done || ['completed', 'error', 'failed', 'cancelled'].includes(events.data?.status)) {
2593
+ if (!sawVisibleOutput && events.data?.status === 'completed') {
2594
+ const fullEvents = await apiRequestJson(
2595
+ `/business/${ctx.businessId}/chat/events?execution_id=${executionId}&workspace_id=${ctx.workspaceId}&from_index=0`,
2596
+ { method: 'GET', token, timeoutMs: 60000 }
2597
+ );
2598
+ if (fullEvents.ok) {
2599
+ emitEvents(fullEvents.data?.events || [], { showTools: false });
2600
+ }
2601
+ }
2602
+
2581
2603
  if (!process.stdout.write('\n')) {
2582
2604
  // no-op: keep line handling stable
2583
2605
  }
@@ -614,6 +614,31 @@ function printImessageDoctor(result, json = false) {
614
614
  }
615
615
  }
616
616
 
617
+ function boundedImessageLimit(value) {
618
+ const parsed = Number(value || 20);
619
+ if (!Number.isFinite(parsed)) return 20;
620
+ return Math.max(1, Math.min(100, Math.trunc(parsed)));
621
+ }
622
+
623
+ function printImessageRows(result, options = {}) {
624
+ if (options.json) {
625
+ const rows = String(result.stdout || '').trim();
626
+ let messages = [];
627
+ try {
628
+ messages = rows ? JSON.parse(rows) : [];
629
+ } catch {
630
+ messages = [];
631
+ }
632
+ console.log(JSON.stringify({
633
+ ok: result.status === 0,
634
+ count: Array.isArray(messages) ? messages.length : 0,
635
+ messages: Array.isArray(messages) ? messages : [],
636
+ }, null, 2));
637
+ return;
638
+ }
639
+ console.log(String(result.stdout || '').trim() || 'No recent messages found.');
640
+ }
641
+
617
642
  function imessageRecent(handle, options = {}) {
618
643
  if (!handle) {
619
644
  console.error('Usage: atris imessage recent <phone-or-email> [--limit 20]');
@@ -625,7 +650,7 @@ function imessageRecent(handle, options = {}) {
625
650
  process.exit(1);
626
651
  }
627
652
 
628
- const limit = Number(options.limit || 20);
653
+ const limit = boundedImessageLimit(options.limit);
629
654
  const chatDb = path.join(os.homedir(), 'Library', 'Messages', 'chat.db');
630
655
  const sql = `
631
656
  SELECT datetime(m.date/1000000000 + 978307200, 'unixepoch', 'localtime') AS ts,
@@ -635,14 +660,44 @@ function imessageRecent(handle, options = {}) {
635
660
  JOIN handle h ON h.rowid = m.handle_id
636
661
  WHERE h.id = '${String(handle).replace(/'/g, "''")}'
637
662
  ORDER BY m.date DESC
638
- LIMIT ${Math.max(1, Math.min(100, limit))};
663
+ LIMIT ${limit};
639
664
  `;
640
- const result = spawnSync('sqlite3', ['-readonly', chatDb, sql], { encoding: 'utf8' });
665
+ const sqliteArgs = options.json ? ['-json', '-readonly', chatDb, sql] : ['-readonly', chatDb, sql];
666
+ const result = spawnSync('sqlite3', sqliteArgs, { encoding: 'utf8' });
667
+ if (result.status !== 0) {
668
+ console.error(result.stderr || 'Failed to read Messages database.');
669
+ process.exit(1);
670
+ }
671
+ printImessageRows(result, options);
672
+ }
673
+
674
+ function imessageLatest(options = {}) {
675
+ const doctor = imessageDoctor();
676
+ if (!doctor.connected) {
677
+ printImessageDoctor(doctor, Boolean(options.json));
678
+ process.exit(1);
679
+ }
680
+
681
+ const limit = boundedImessageLimit(options.limit);
682
+ const chatDb = path.join(os.homedir(), 'Library', 'Messages', 'chat.db');
683
+ const sql = `
684
+ SELECT datetime(m.date/1000000000 + 978307200, 'unixepoch', 'localtime') AS ts,
685
+ CASE m.is_from_me WHEN 1 THEN 'me' ELSE COALESCE(h.id, 'unknown') END AS sender,
686
+ COALESCE(h.id, 'unknown') AS handle,
687
+ replace(replace(COALESCE(m.text,''), char(10), ' '), char(13), ' ') AS text
688
+ FROM message m
689
+ LEFT JOIN handle h ON h.rowid = m.handle_id
690
+ WHERE length(COALESCE(m.text,'')) > 0
691
+ ORDER BY m.date DESC
692
+ LIMIT ${limit};
693
+ `;
694
+ const sqliteArgs = options.json ? ['-json', '-readonly', chatDb, sql] : ['-readonly', chatDb, sql];
695
+ const result = spawnSync('sqlite3', sqliteArgs, { encoding: 'utf8' });
641
696
  if (result.status !== 0) {
642
697
  console.error(result.stderr || 'Failed to read Messages database.');
643
698
  process.exit(1);
644
699
  }
645
- console.log(result.stdout.trim() || 'No recent messages found.');
700
+ printImessageRows(result, options);
646
701
  }
647
702
 
648
703
  function escapeSqlString(value) {
@@ -1169,12 +1224,22 @@ async function imessageCommand(subcommand, ...args) {
1169
1224
  break;
1170
1225
  }
1171
1226
  case 'recent': {
1172
- const handle = args[0];
1173
1227
  const limitFlag = args.findIndex((x) => x === '--limit');
1174
1228
  const limit = limitFlag >= 0 ? args[limitFlag + 1] : 20;
1229
+ const handle = args.find((arg) => arg && !arg.startsWith('--') && arg !== String(limit));
1230
+ if (!handle) {
1231
+ imessageLatest({ limit, json: args.includes('--json') });
1232
+ break;
1233
+ }
1175
1234
  imessageRecent(handle, { limit, json: args.includes('--json') });
1176
1235
  break;
1177
1236
  }
1237
+ case 'latest': {
1238
+ const limitFlag = args.findIndex((x) => x === '--limit');
1239
+ const limit = limitFlag >= 0 ? args[limitFlag + 1] : 20;
1240
+ imessageLatest({ limit, json: args.includes('--json') });
1241
+ break;
1242
+ }
1178
1243
  case 'lookup': {
1179
1244
  imessageLookup(args);
1180
1245
  break;
@@ -1188,6 +1253,7 @@ async function imessageCommand(subcommand, ...args) {
1188
1253
  console.log(' atris imessage doctor [--json] - Check local Messages access');
1189
1254
  console.log(' atris imessage lookup --name <name> [--json] [--refresh]');
1190
1255
  console.log(' atris imessage recent <handle> - Read recent local messages');
1256
+ console.log(' atris imessage latest [--limit 20] [--json] - Read latest local messages');
1191
1257
  console.log(' atris imessage send --to <handle> --text <text> --approved [--json] [--receipt]');
1192
1258
  }
1193
1259
  }
package/commands/skill.js CHANGED
@@ -88,6 +88,39 @@ function findAllSkills(skillsDir) {
88
88
  return skills;
89
89
  }
90
90
 
91
+ function localSkillsDir() {
92
+ return path.join(process.cwd(), 'atris', 'skills');
93
+ }
94
+
95
+ function bundledSkillsDir() {
96
+ return path.join(__dirname, '..', 'atris', 'skills');
97
+ }
98
+
99
+ function readableSkillRoots() {
100
+ const roots = [localSkillsDir(), bundledSkillsDir()];
101
+ const seen = new Set();
102
+ return roots.filter((root) => {
103
+ if (!root || !fs.existsSync(root)) return false;
104
+ const real = fs.realpathSync(root);
105
+ if (seen.has(real)) return false;
106
+ seen.add(real);
107
+ return true;
108
+ });
109
+ }
110
+
111
+ function findReadableSkills() {
112
+ const seen = new Set();
113
+ const skills = [];
114
+ for (const root of readableSkillRoots()) {
115
+ for (const skill of findAllSkills(root)) {
116
+ if (seen.has(skill.folder)) continue;
117
+ seen.add(skill.folder);
118
+ skills.push(skill);
119
+ }
120
+ }
121
+ return skills;
122
+ }
123
+
91
124
  // --- Audit Checks ---
92
125
 
93
126
  function runAuditChecks(skill) {
@@ -355,11 +388,10 @@ function generateTags(folderName, description) {
355
388
  // --- Subcommand Handlers ---
356
389
 
357
390
  function skillList() {
358
- const skillsDir = path.join(process.cwd(), 'atris', 'skills');
359
- const skills = findAllSkills(skillsDir);
391
+ const skills = findReadableSkills();
360
392
 
361
393
  if (skills.length === 0) {
362
- console.log('No skills found in atris/skills/. Run "atris init" first.');
394
+ console.log('No skills found in local or bundled Atris skill roots.');
363
395
  return;
364
396
  }
365
397
 
@@ -396,15 +428,14 @@ function skillList() {
396
428
  }
397
429
 
398
430
  function skillAudit(name) {
399
- const skillsDir = path.join(process.cwd(), 'atris', 'skills');
400
- const allSkills = findAllSkills(skillsDir);
431
+ const allSkills = findReadableSkills();
401
432
 
402
433
  const targets = name === '--all'
403
434
  ? allSkills
404
435
  : allSkills.filter(s => s.folder === name || s.leafFolder === name);
405
436
 
406
437
  if (targets.length === 0) {
407
- console.error(`Skill "${name}" not found. Run "atris skill list" to see available skills.`);
438
+ console.error(`Skill "${name}" not found. Run "atris skill list" to see available local and bundled skills.`);
408
439
  process.exit(1);
409
440
  }
410
441
 
package/commands/xp.js CHANGED
@@ -7,6 +7,7 @@ const crypto = require('crypto');
7
7
  const { spawnSync } = require('child_process');
8
8
 
9
9
  const DEFAULT_GRAPH_DAYS = 365;
10
+ const MAX_SYNC_GRAPH_DAYS = 370;
10
11
  const INTENSITY_CHARS = [' ', '.', ':', '*', '#'];
11
12
  const ROW_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
12
13
  const TASK_EPISODES_FILE = path.join('.atris', 'state', 'task_episodes.jsonl');
@@ -210,6 +211,38 @@ function combineAgentXpContributionGraphs(graphs, windowDays = DEFAULT_GRAPH_DAY
210
211
  return graphFromDailyTotals(totals, windowDays);
211
212
  }
212
213
 
214
+ function sanitizeContributionGraphForSync(graph) {
215
+ if (!graph || typeof graph !== 'object') return null;
216
+ const rawDays = Array.isArray(graph.days) ? graph.days : [];
217
+ const days = rawDays
218
+ .slice(-MAX_SYNC_GRAPH_DAYS)
219
+ .map((day) => {
220
+ const date = typeof day?.date === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(day.date)
221
+ ? day.date
222
+ : null;
223
+ if (!date) return null;
224
+ const xp = Math.max(0, asNumber(day.xp ?? day.total_xp));
225
+ return {
226
+ date,
227
+ xp,
228
+ total_xp: Math.max(0, asNumber(day.total_xp, xp)),
229
+ intensity: Math.max(0, Math.min(4, asNumber(day.intensity))),
230
+ };
231
+ })
232
+ .filter(Boolean);
233
+ if (days.length === 0) return null;
234
+ return {
235
+ schema: 'atris.agent_xp_contribution_graph.v1',
236
+ metric_label: AGENT_XP_LABEL,
237
+ window_days: days.length,
238
+ total_xp: days.reduce((sum, day) => sum + day.xp, 0),
239
+ active_days: days.filter(day => day.xp > 0).length,
240
+ first_date: days[0]?.date || null,
241
+ last_date: days[days.length - 1]?.date || null,
242
+ days,
243
+ };
244
+ }
245
+
213
246
  function currentForm(payload) {
214
247
  const arenas = payload.current_form_by_arena || {};
215
248
  const local = arenas.local_workspace || {};
@@ -1945,6 +1978,7 @@ function buildAgentXpSyncPacket(args = []) {
1945
1978
  const publicXp = earnedAgentXp(totalXp);
1946
1979
  const currentForm = currentFormScore(totalXp);
1947
1980
  const levelProgress = agentXpLevelProgress(publicXp);
1981
+ const contributionGraph = sanitizeContributionGraphForSync(projection.contribution_graph);
1948
1982
  const workspaceRootHash = sha256(workspaces.map(item => item.workspace_root_hash || item.name).sort().join(':'));
1949
1983
  const entry = {
1950
1984
  user_id: player,
@@ -1964,6 +1998,7 @@ function buildAgentXpSyncPacket(args = []) {
1964
1998
  lock_reason: eligible ? null : 'not_enough_trusted_proof',
1965
1999
  public_adjustment: null,
1966
2000
  next_move: eligible ? 'Play the next proof-backed AgentXP mission.' : 'Complete one proof-backed AgentXP rep.',
2001
+ contribution_graph: contributionGraph,
1967
2002
  };
1968
2003
  const packet = {
1969
2004
  schema: 'atris.agentxp_sync_packet.v1',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.15.56",
3
+ "version": "3.15.57",
4
4
  "main": "bin/atris.js",
5
5
  "bin": {
6
6
  "atris": "bin/atris.js",