atris 3.16.1 → 3.22.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.
Files changed (65) hide show
  1. package/README.md +32 -7
  2. package/atris/skills/atris/SKILL.md +15 -2
  3. package/atris/skills/atris-feedback/SKILL.md +7 -0
  4. package/atris/skills/design/SKILL.md +29 -2
  5. package/atris/skills/engines/SKILL.md +44 -0
  6. package/atris/skills/flow/SKILL.md +1 -1
  7. package/atris/skills/wake/SKILL.md +37 -0
  8. package/atris/skills/youtube/SKILL.md +13 -39
  9. package/atris/team/validator/MEMBER.md +1 -0
  10. package/atris/wiki/concepts/agent-activation-contract.md +3 -3
  11. package/atris/wiki/concepts/workspace-initialization-contract.md +3 -3
  12. package/atris/wiki/index.md +1 -0
  13. package/atris.md +43 -19
  14. package/bin/atris.js +413 -31
  15. package/commands/agent-spawn.js +480 -0
  16. package/commands/analytics.js +6 -3
  17. package/commands/apps.js +11 -0
  18. package/commands/autopilot.js +42 -18
  19. package/commands/brain.js +74 -7
  20. package/commands/brainstorm.js +9 -58
  21. package/commands/clean.js +1 -4
  22. package/commands/compile.js +9 -4
  23. package/commands/console.js +8 -3
  24. package/commands/deck.js +184 -0
  25. package/commands/init.js +22 -11
  26. package/commands/lesson.js +76 -0
  27. package/commands/member.js +252 -48
  28. package/commands/mission.js +405 -13
  29. package/commands/now.js +4 -2
  30. package/commands/probe.js +105 -27
  31. package/commands/pulse.js +504 -0
  32. package/commands/radar.js +1 -0
  33. package/commands/recap.js +71 -25
  34. package/commands/run.js +615 -22
  35. package/commands/site.js +48 -0
  36. package/commands/slop.js +307 -0
  37. package/commands/spaceship.js +39 -0
  38. package/commands/sync.js +0 -2
  39. package/commands/task.js +429 -37
  40. package/commands/theme.js +217 -0
  41. package/commands/verify.js +7 -3
  42. package/lib/activity-stream.js +166 -0
  43. package/lib/auto-accept-certified.js +23 -1
  44. package/lib/context-gatherer.js +170 -0
  45. package/lib/deck-from-md.js +110 -0
  46. package/lib/escape-regexp.js +13 -0
  47. package/lib/file-ops.js +6 -3
  48. package/lib/html-render.js +257 -0
  49. package/lib/journal.js +1 -1
  50. package/lib/lesson-contradiction.js +113 -0
  51. package/lib/memory-view.js +95 -0
  52. package/lib/policy-lessons.js +3 -2
  53. package/lib/pulse.js +401 -0
  54. package/lib/runner-command.js +156 -0
  55. package/lib/site.js +114 -0
  56. package/lib/slides-deck.js +237 -0
  57. package/lib/state-detection.js +1 -4
  58. package/lib/task-db.js +101 -4
  59. package/lib/task-proof.js +1 -1
  60. package/lib/theme.js +264 -0
  61. package/lib/todo-fallback.js +2 -1
  62. package/lib/todo-sections.js +33 -0
  63. package/package.json +1 -2
  64. package/utils/api.js +14 -2
  65. package/atris/atrisDev.md +0 -717
package/commands/now.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const { hasRenderedSections, isOpenSection } = require('../lib/todo-sections');
3
4
 
4
5
  const NOW_PATH = path.join('atris', 'now.md');
5
6
  const TASK_EPISODES_PATH = path.join('.atris', 'state', 'task_episodes.jsonl');
@@ -71,7 +72,8 @@ function countMatches(filePath, pattern) {
71
72
  function countOpenTodoItems(filePath) {
72
73
  if (!fs.existsSync(filePath)) return 0;
73
74
  const content = fs.readFileSync(filePath, 'utf8');
74
- const hasRenderedSections = /^##\s+(Backlog|In Progress|Blocked|Completed)\s*$/m.test(content);
75
+ // Emoji-decorated headings ("## In Progress 🔄") handled by lib/todo-sections.
76
+ const rendered = hasRenderedSections(content);
75
77
  let section = null;
76
78
  let count = 0;
77
79
 
@@ -83,7 +85,7 @@ function countOpenTodoItems(filePath) {
83
85
  }
84
86
  const isTaskBullet = /^-\s+(?:\[[ ]\]\s+)?\*\*.+?\*\*/.test(line);
85
87
  if (!isTaskBullet) continue;
86
- if (!hasRenderedSections || ['Backlog', 'In Progress'].includes(section)) {
88
+ if (!rendered || isOpenSection(section)) {
87
89
  count += 1;
88
90
  }
89
91
  }
package/commands/probe.js CHANGED
@@ -57,7 +57,10 @@ function normalizeBash(cmd, label) {
57
57
  return cmd.split(l + '/').join('').split(l).join('.');
58
58
  }
59
59
 
60
- // Probe is read-only: no write/edit file ops.
60
+ // No write/edit file ops, and the `..` guard only covers args.path. The
61
+ // `bash` op still executes whatever command the model sends, verbatim, on
62
+ // the remote ai-computer — that is the production relay contract and the
63
+ // table must stay in lockstep with it, so this probe is NOT read-only.
61
64
  function fileOpCommand(args, label) {
62
65
  const op = String(args.type || '').toLowerCase();
63
66
  const raw = normalizePath(String(args.path || '') || '.', label);
@@ -116,7 +119,9 @@ function atrisCliCommand(args) {
116
119
  }
117
120
 
118
121
  function atrisCliResult(command, term) {
119
- const code = term.exit_code || 0;
122
+ // a terminal response without a numeric exit_code is a broken endpoint,
123
+ // not a success — never let it masquerade as ok with empty stdout
124
+ const code = typeof term.exit_code === 'number' ? term.exit_code : -1;
120
125
  const out = {
121
126
  schema: 'atris.local_cli_result.v1',
122
127
  status: code === 0 ? 'ok' : 'error',
@@ -124,7 +129,11 @@ function atrisCliResult(command, term) {
124
129
  stdout: String(term.stdout || '').slice(0, 12000),
125
130
  exit_code: code,
126
131
  };
127
- if (code !== 0) out.error = String(term.stderr || term.stdout || 'command failed').slice(0, 2000);
132
+ if (code !== 0) {
133
+ out.error = typeof term.exit_code === 'number'
134
+ ? String(term.stderr || term.stdout || 'command failed').slice(0, 2000)
135
+ : 'terminal endpoint returned no exit_code';
136
+ }
128
137
  return out;
129
138
  }
130
139
 
@@ -154,7 +163,10 @@ function postJson(urlString, token, payload, timeoutMs) {
154
163
  reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`));
155
164
  return;
156
165
  }
157
- try { resolve(data ? JSON.parse(data) : {}); } catch (e) { resolve({}); }
166
+ if (!data) { resolve({}); return; }
167
+ try { resolve(JSON.parse(data)); } catch (e) {
168
+ reject(new Error(`invalid JSON from ${url.pathname}: ${data.slice(0, 120)}`));
169
+ }
158
170
  });
159
171
  });
160
172
  req.on('timeout', () => { req.destroy(new Error('request timeout')); });
@@ -188,35 +200,46 @@ function parseArgs(argv) {
188
200
  return a;
189
201
  }
190
202
 
191
- async function probeCommand(argv) {
192
- const a = parseArgs(argv || []);
203
+ // Core /atris2/turn client: one streamed turn over the full local tool relay.
204
+ // Shared by `atris probe` (the instrument) and `atris mission run --runner atris2`
205
+ // (the worker). Transport-level outcome only — callers apply their own
206
+ // pass/fail policy on top of the returned fields.
207
+ async function runAtris2Turn(opts = {}) {
208
+ const {
209
+ prompt,
210
+ model = 'atris:fast',
211
+ business = null,
212
+ memberId = null,
213
+ memberSlug = null,
214
+ maxTurns = 8,
215
+ idleMs = 180000,
216
+ connectTimeoutMs = 60000,
217
+ signal = null,
218
+ } = opts;
219
+ const t0 = Date.now();
220
+ const out = { ok: false, text: '', engine: null, tools_run: 0, cli_ops: [], unsupported: [], error: null, duration_ms: 0 };
193
221
  const creds = loadCredentials();
194
222
  if (!creds || !creds.token) {
195
- console.error('✗ Not logged in. Run: atris login');
196
- return 1;
223
+ out.error = 'not-logged-in';
224
+ out.duration_ms = Date.now() - t0;
225
+ return out;
197
226
  }
198
227
  const token = creds.token;
199
228
  const base = getApiBaseUrl();
200
- const label = workspaceLabel(a.business);
201
- const where = a.business ? `business ${String(a.business).slice(0, 8)}` : 'personal';
202
- const member = a.memberSlug || a.memberId;
203
- const prompt = a.message || (a.calendar
204
- ? "What's on my calendar today? Use your tools."
205
- : 'Read the first 5 lines of any markdown file in this workspace and quote one real line from it. Use your file tools.');
229
+ const label = workspaceLabel(business);
206
230
 
207
231
  const body = {
208
- message: prompt, model: a.model, max_turns: 8,
232
+ message: prompt, model, max_turns: maxTurns,
209
233
  verify_command: 'true', local_executor: true, workspace_path: label,
210
234
  };
211
- if (a.memberId) body.member_id = a.memberId;
212
- if (a.memberSlug) body.member_slug = a.memberSlug;
235
+ if (memberId) body.member_id = memberId;
236
+ if (memberSlug) body.member_slug = memberSlug;
213
237
 
214
- const t0 = Date.now();
215
238
  let toolsRun = 0;
216
- let cliCalendarOps = 0;
217
239
  let resultText = '';
218
240
  let err = null;
219
241
  let engine = null;
242
+ const cliOps = [];
220
243
  const unsupported = [];
221
244
 
222
245
  try {
@@ -235,7 +258,11 @@ async function probeCommand(argv) {
235
258
  Accept: 'text/event-stream',
236
259
  Authorization: `Bearer ${token}`,
237
260
  },
261
+ // connect/first-byte guard; cleared once headers arrive so the
262
+ // idle timer below is the only judge of a flowing stream
263
+ timeout: connectTimeoutMs,
238
264
  }, (res) => {
265
+ req.setTimeout(0);
239
266
  if (res.statusCode < 200 || res.statusCode >= 300) {
240
267
  let data = '';
241
268
  res.on('data', (c) => data += c);
@@ -244,10 +271,9 @@ async function probeCommand(argv) {
244
271
  }
245
272
  let buffer = '';
246
273
  let idleTimer = null;
247
- const IDLE_MS = 180000;
248
274
  const resetIdle = () => {
249
275
  if (idleTimer) clearTimeout(idleTimer);
250
- idleTimer = setTimeout(() => { req.destroy(); reject(new Error(`stream stalled: no events for ${IDLE_MS / 1000}s`)); }, IDLE_MS);
276
+ idleTimer = setTimeout(() => { req.destroy(); reject(new Error(`stream stalled: no events for ${idleMs / 1000}s`)); }, idleMs);
251
277
  };
252
278
  resetIdle();
253
279
 
@@ -273,11 +299,16 @@ async function probeCommand(argv) {
273
299
  out = { status: 'error', error: 'unsupported op or unsafe path' };
274
300
  unsupported.push(`file_op:${op}`);
275
301
  } else {
276
- const term = await runTerminal(base, token, cmd, a.business);
277
- const ok = (term.exit_code || 0) === 0;
302
+ const term = await runTerminal(base, token, cmd, business);
303
+ const ok = term.exit_code === 0;
278
304
  out = ok
279
305
  ? { status: 'ok', stdout: String(term.stdout || '').slice(0, 12000) }
280
- : { status: 'error', error: String(term.stderr || 'command failed').slice(0, 2000) };
306
+ : {
307
+ status: 'error',
308
+ error: typeof term.exit_code === 'number'
309
+ ? String(term.stderr || 'command failed').slice(0, 2000)
310
+ : 'terminal endpoint returned no exit_code',
311
+ };
281
312
  }
282
313
  } else if (name === 'local_atris_cli_op') {
283
314
  const cmd = atrisCliCommand(args);
@@ -285,9 +316,9 @@ async function probeCommand(argv) {
285
316
  out = { status: 'error', error: `unsupported atris cli op: ${op || '?'}` };
286
317
  unsupported.push(`cli_op:${op || '?'}`);
287
318
  } else {
288
- const term = await runTerminal(base, token, cmd, a.business);
319
+ const term = await runTerminal(base, token, cmd, business);
289
320
  out = atrisCliResult(cmd, term);
290
- if (op.startsWith('calendar')) cliCalendarOps += 1;
321
+ cliOps.push(op || '?');
291
322
  }
292
323
  } else {
293
324
  out = { status: 'error', error: `unsupported relayed tool: ${name || '?'}` };
@@ -321,7 +352,9 @@ async function probeCommand(argv) {
321
352
  });
322
353
  res.on('error', (e) => { if (idleTimer) clearTimeout(idleTimer); reject(e); });
323
354
  });
355
+ req.on('timeout', () => { req.destroy(new Error(`no response headers within ${Math.round(connectTimeoutMs / 1000)}s`)); });
324
356
  req.on('error', reject);
357
+ if (signal) signal.addEventListener('abort', () => { req.destroy(new Error('aborted')); }, { once: true });
325
358
  req.write(postData);
326
359
  req.end();
327
360
  });
@@ -329,6 +362,42 @@ async function probeCommand(argv) {
329
362
  if (!err) err = `${e.name || 'Error'}: ${e.message || e}`;
330
363
  }
331
364
 
365
+ out.text = resultText;
366
+ out.engine = engine;
367
+ out.tools_run = toolsRun;
368
+ out.cli_ops = cliOps;
369
+ out.unsupported = unsupported;
370
+ out.error = err;
371
+ out.ok = err === null;
372
+ out.duration_ms = Date.now() - t0;
373
+ return out;
374
+ }
375
+
376
+ async function probeCommand(argv) {
377
+ const a = parseArgs(argv || []);
378
+ const creds = loadCredentials();
379
+ if (!creds || !creds.token) {
380
+ console.error('✗ Not logged in. Run: atris login');
381
+ return 1;
382
+ }
383
+ const where = a.business ? `business ${String(a.business).slice(0, 8)}` : 'personal';
384
+ const member = a.memberSlug || a.memberId;
385
+ const prompt = a.message || (a.calendar
386
+ ? "What's on my calendar today? Use your tools."
387
+ : 'Read the first 5 lines of any markdown file in this workspace and quote one real line from it. Use your file tools.');
388
+
389
+ const t0 = Date.now();
390
+ const turn = await runAtris2Turn({
391
+ prompt, model: a.model, business: a.business,
392
+ memberId: a.memberId, memberSlug: a.memberSlug, maxTurns: 8,
393
+ });
394
+ const toolsRun = turn.tools_run;
395
+ const cliCalendarOps = turn.cli_ops.filter((op) => String(op).startsWith('calendar')).length;
396
+ const resultText = turn.text;
397
+ const err = turn.error;
398
+ const engine = turn.engine;
399
+ const unsupported = turn.unsupported;
400
+
332
401
  // Name the dead-end (first match wins); null = converged.
333
402
  let deadEnd = null;
334
403
  if (err) {
@@ -363,4 +432,13 @@ async function probeCommand(argv) {
363
432
  return ok ? 0 : 1;
364
433
  }
365
434
 
366
- module.exports = { probeCommand };
435
+ module.exports = {
436
+ probeCommand,
437
+ runAtris2Turn,
438
+ // exported for tests
439
+ normalizePath,
440
+ normalizeBash,
441
+ fileOpCommand,
442
+ atrisCliCommand,
443
+ atrisCliResult,
444
+ };