atris 3.64.0 → 3.66.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/README.md +4 -0
- package/atris/skills/engines/SKILL.md +2 -2
- package/bin/atris.js +45 -2
- package/commands/agent-spawn.js +1 -0
- package/commands/autoland.js +62 -0
- package/commands/brain.js +4 -0
- package/commands/engine.js +203 -63
- package/commands/hire.js +189 -0
- package/commands/link.js +367 -0
- package/commands/loops.js +4 -1
- package/commands/mail.js +163 -0
- package/commands/member.js +8 -1
- package/commands/mission.js +3 -2
- package/commands/orb.js +1 -1
- package/commands/stream.js +22 -1
- package/commands/team.js +355 -67
- package/commands/xp.js +2 -0
- package/commands/youtube.js +1 -1
- package/lib/auto-accept-certified.js +172 -18
- package/lib/business-target.js +131 -0
- package/lib/cleanup-summary.js +129 -0
- package/lib/engine-ask.js +3 -2
- package/lib/engine-processes.js +430 -0
- package/lib/engine-registry.js +219 -18
- package/lib/idle-review-sweep.js +130 -0
- package/lib/known-commands.js +2 -2
- package/lib/land-green.js +18 -4
- package/lib/member-identity.js +56 -0
- package/lib/member-result.js +303 -0
- package/lib/roster-models.js +12 -1
- package/lib/roster-stale.js +241 -0
- package/lib/runner-command.js +4 -4
- package/lib/task-list-keeper.js +25 -4
- package/lib/team-lineup.js +10 -71
- package/lib/team-presence.js +224 -0
- package/lib/text-table.js +81 -0
- package/lib/trust-tiers.js +131 -14
- package/lib/verify-worktree-rewrite.js +115 -0
- package/lib/voice-gate.js +2 -0
- package/package.json +1 -1
- package/scripts/det/ytnotes +1 -1
package/commands/mail.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// atris mail inboxes | read <inbox> | send <inbox> --to --subject --body
|
|
4
|
+
// Your agents' own email inboxes (the @atrismail.com ones), not your Gmail.
|
|
5
|
+
// For Gmail use `atris gmail`. Backend: /api/business/{id}/mail/...
|
|
6
|
+
|
|
7
|
+
const readline = require('readline');
|
|
8
|
+
const { apiRequestJson } = require('../utils/api');
|
|
9
|
+
const { NOT_LOGGED_IN } = require('../lib/developer-api');
|
|
10
|
+
const {
|
|
11
|
+
parseArgs, businessOrSay, loginToken, errorText, clip,
|
|
12
|
+
} = require('../lib/business-target');
|
|
13
|
+
|
|
14
|
+
// The only flags each subcommand takes (plus --help, --json, --business).
|
|
15
|
+
const MAIL_FLAGS = {
|
|
16
|
+
inboxes: {},
|
|
17
|
+
read: { values: ['limit'] },
|
|
18
|
+
send: { switches: ['yes', 'dry_run'], values: ['to', 'subject', 'body'] },
|
|
19
|
+
};
|
|
20
|
+
const ALL_MAIL_FLAGS = { switches: ['yes', 'dry_run'], values: ['limit', 'to', 'subject', 'body'] };
|
|
21
|
+
|
|
22
|
+
function mailHelp(log = console.log) {
|
|
23
|
+
log(`usage: atris mail inboxes [--business <slug>] [--json]
|
|
24
|
+
atris mail read <inbox> [--limit 10] [--json]
|
|
25
|
+
atris mail send <inbox> --to <address> --subject "<subject>" --body "<text>" [--yes] [--dry-run]
|
|
26
|
+
|
|
27
|
+
your agents' own email inboxes (not your Gmail; for that use atris gmail).
|
|
28
|
+
<inbox> is the address, the part before the @, or the inbox id.
|
|
29
|
+
|
|
30
|
+
inboxes list every agent inbox in this business
|
|
31
|
+
read newest messages first (--limit 1 to 200, default 10)
|
|
32
|
+
send send one email from an agent inbox. asks before sending unless --yes.
|
|
33
|
+
--dry-run prints the email and sends nothing.
|
|
34
|
+
unknown flags are refused, so a typo never sends anything.`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toList(value) {
|
|
38
|
+
if (Array.isArray(value)) return value.map(String);
|
|
39
|
+
if (typeof value === 'string' && value.trim().startsWith('[')) {
|
|
40
|
+
try { return JSON.parse(value).map(String); } catch { /* fall through */ }
|
|
41
|
+
}
|
|
42
|
+
return value ? [String(value)] : [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function renderInboxes(rows = []) {
|
|
46
|
+
if (!rows.length) return 'No agent inboxes yet. Hiring an agent makes one: atris hire "<name>" --job "<idea>"';
|
|
47
|
+
return rows.map((r) => `${r.email_address}${r.display_name ? ` (${r.display_name})` : ''}`).join('\n');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderMessages(inbox, rows = []) {
|
|
51
|
+
if (!rows.length) return `No mail in ${inbox.email_address} yet.`;
|
|
52
|
+
const lines = [];
|
|
53
|
+
for (const m of rows) {
|
|
54
|
+
const when = String(m.created_at || '').slice(0, 16).replace('T', ' ');
|
|
55
|
+
const out = m.direction === 'outbound';
|
|
56
|
+
const who = out ? `to ${toList(m.to_addresses).join(', ')}` : `from ${m.from_address || '?'}`;
|
|
57
|
+
lines.push(`${when} ${out ? 'sent' : 'got '} ${who}`);
|
|
58
|
+
lines.push(` ${clip(m.subject || '(no subject)', 100)}`);
|
|
59
|
+
const body = clip(m.body_text || '', 160);
|
|
60
|
+
if (body) lines.push(` ${body}`);
|
|
61
|
+
}
|
|
62
|
+
return lines.join('\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// address, local part, or id -> the inbox row
|
|
66
|
+
function findInbox(rows, wanted) {
|
|
67
|
+
const w = String(wanted || '').trim().toLowerCase();
|
|
68
|
+
if (!w) return null;
|
|
69
|
+
return rows.find((r) => String(r.id).toLowerCase() === w)
|
|
70
|
+
|| rows.find((r) => String(r.email_address || '').toLowerCase() === w)
|
|
71
|
+
|| rows.find((r) => String(r.email_address || '').toLowerCase().split('@')[0] === w)
|
|
72
|
+
|| null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function askYesNo(question) {
|
|
76
|
+
if (!process.stdin.isTTY) return Promise.resolve(false);
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
79
|
+
rl.question(question, (answer) => {
|
|
80
|
+
rl.close();
|
|
81
|
+
resolve(/^y(es)?$/i.test(String(answer || '').trim()));
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function mailCommand(args = [], deps = {}) {
|
|
87
|
+
const out = { log: deps.log || console.log, err: deps.err || console.error };
|
|
88
|
+
const { flags, pos } = parseArgs(args, ALL_MAIL_FLAGS);
|
|
89
|
+
const sub = pos[0];
|
|
90
|
+
if (flags.help || !sub || sub === 'help') { mailHelp(out.log); return sub || flags.help ? 0 : 1; }
|
|
91
|
+
if (!MAIL_FLAGS[sub]) {
|
|
92
|
+
out.err(`unknown mail command: ${sub}. try: atris mail inboxes | read | send`);
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
// Checked against this subcommand's own flags, before any request: an
|
|
96
|
+
// unknown or mistyped flag on send must never turn into a real email.
|
|
97
|
+
const { error } = parseArgs(args, MAIL_FLAGS[sub]);
|
|
98
|
+
if (error) { out.err(`${error}. see: atris mail --help`); return 1; }
|
|
99
|
+
const words = sub === 'inboxes' ? 1 : 2;
|
|
100
|
+
if (pos.length > words) {
|
|
101
|
+
out.err(`atris mail ${sub} got an extra word: "${pos[words]}". put text in quotes, like --body "hi there"`);
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const business = businessOrSay(flags, deps, out.err);
|
|
106
|
+
if (!business) return 1;
|
|
107
|
+
const token = await loginToken(deps);
|
|
108
|
+
if (!token) { out.err(NOT_LOGGED_IN); return 1; }
|
|
109
|
+
const request = deps.apiRequestJson || apiRequestJson;
|
|
110
|
+
const base = `/business/${encodeURIComponent(business.id)}/mail`;
|
|
111
|
+
|
|
112
|
+
const listed = await request(`${base}/inboxes`, { method: 'GET', token });
|
|
113
|
+
if (!listed || !listed.ok) { out.err(`Could not load inboxes: ${errorText(listed)}`); return 1; }
|
|
114
|
+
const inboxes = Array.isArray(listed.data) ? listed.data : [];
|
|
115
|
+
|
|
116
|
+
if (sub === 'inboxes') {
|
|
117
|
+
out.log(flags.json ? JSON.stringify(inboxes) : renderInboxes(inboxes));
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!pos[1]) { out.err(`which inbox? atris mail ${sub} <inbox>`); return 1; }
|
|
122
|
+
const inbox = findInbox(inboxes, pos[1]);
|
|
123
|
+
if (!inbox) { out.err(`No inbox matches "${pos[1]}". See them all: atris mail inboxes`); return 1; }
|
|
124
|
+
|
|
125
|
+
if (sub === 'read') {
|
|
126
|
+
const limit = flags.limit == null ? 10 : Number(flags.limit);
|
|
127
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 200) { out.err('--limit must be a number from 1 to 200'); return 1; }
|
|
128
|
+
const res = await request(`${base}/inboxes/${encodeURIComponent(inbox.id)}/messages?limit=${limit}`, { method: 'GET', token });
|
|
129
|
+
if (!res || !res.ok) { out.err(`Could not read ${inbox.email_address}: ${errorText(res)}`); return 1; }
|
|
130
|
+
const rows = Array.isArray(res.data) ? res.data : [];
|
|
131
|
+
out.log(flags.json ? JSON.stringify(rows) : renderMessages(inbox, rows));
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// send
|
|
136
|
+
const to = typeof flags.to === 'string' ? flags.to.trim() : '';
|
|
137
|
+
const subject = typeof flags.subject === 'string' ? flags.subject : '';
|
|
138
|
+
const text = typeof flags.body === 'string' ? flags.body : '';
|
|
139
|
+
if (!to || !subject || !text) { out.err('send needs --to, --subject, and --body'); return 1; }
|
|
140
|
+
const preview = `From: ${inbox.email_address}\nTo: ${to}\nSubject: ${subject}\n\n${text}\n`;
|
|
141
|
+
if (flags.dry_run === true) {
|
|
142
|
+
out.log(`${preview}\nDry run: nothing was sent.`);
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
// Only a bare --yes (or -y) skips the question; the parser refuses --yes=<anything>.
|
|
146
|
+
if (flags.yes !== true) {
|
|
147
|
+
out.log(preview);
|
|
148
|
+
const ok = await (deps.confirm || askYesNo)('Send this email? (y/N) ');
|
|
149
|
+
if (!ok) {
|
|
150
|
+
out.log(process.stdin.isTTY || deps.confirm ? 'Not sent.' : 'Not sent. Add --yes to send without asking.');
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const res = await request(`${base}/inboxes/${encodeURIComponent(inbox.id)}/messages/send`, {
|
|
155
|
+
method: 'POST', token, body: { to, subject, text }, retries: 0, timeoutMs: 30000,
|
|
156
|
+
});
|
|
157
|
+
if (!res || !res.ok) { out.err(`Not sent: ${errorText(res)}`); return 1; }
|
|
158
|
+
if (flags.json) out.log(JSON.stringify(res.data));
|
|
159
|
+
else out.log(`Sent from ${inbox.email_address} to ${to}.`);
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = { mailCommand, findInbox, renderInboxes, renderMessages };
|
package/commands/member.js
CHANGED
|
@@ -9412,7 +9412,7 @@ async function memberCommand(subcommand, ...args) {
|
|
|
9412
9412
|
// Subcommands that take a member name as args[0] otherwise treat `--help` as
|
|
9413
9413
|
// a name and error with "Member '--help' not found". `create`/`new` handle
|
|
9414
9414
|
// help themselves (with subcommand-specific usage), leave those alone.
|
|
9415
|
-
const HELP_AWARE_SUBCOMMANDS = new Set(['create', 'new', 'run', 'install']);
|
|
9415
|
+
const HELP_AWARE_SUBCOMMANDS = new Set(['create', 'new', 'run', 'install', 'result']);
|
|
9416
9416
|
if (!HELP_AWARE_SUBCOMMANDS.has(subcommand) && (args[0] === '-h' || args[0] === '--help')) {
|
|
9417
9417
|
subcommand = undefined;
|
|
9418
9418
|
}
|
|
@@ -9449,6 +9449,11 @@ async function memberCommand(subcommand, ...args) {
|
|
|
9449
9449
|
return memberWake(args[0], ...args.slice(1));
|
|
9450
9450
|
case 'run':
|
|
9451
9451
|
return memberRun(args[0], ...args.slice(1));
|
|
9452
|
+
case 'result': {
|
|
9453
|
+
const code = require('../lib/member-result').memberResultCommand(args);
|
|
9454
|
+
if (code) process.exitCode = code;
|
|
9455
|
+
return undefined;
|
|
9456
|
+
}
|
|
9452
9457
|
case 'ping':
|
|
9453
9458
|
return memberPing(args[0], ...args.slice(1));
|
|
9454
9459
|
case 'loop':
|
|
@@ -9498,6 +9503,7 @@ async function memberCommand(subcommand, ...args) {
|
|
|
9498
9503
|
console.log(' goal-from-score <name> Create/reuse an active goal from Team score evidence');
|
|
9499
9504
|
console.log(' wake <name> Read Mission state and decide tick/wait/ask/stop');
|
|
9500
9505
|
console.log(' run <name> ["..."] Start a budgeted member mission, or run its active Mission Runtime');
|
|
9506
|
+
console.log(' result <name> Newest run result: run record, receipt, artifacts, number [--since <iso>] [--json]');
|
|
9501
9507
|
console.log(' loop <name|--all> Repeat wake on a bounded cadence with a no-overlap lease');
|
|
9502
9508
|
console.log(' tick <name> Propose the next bounded experiment');
|
|
9503
9509
|
console.log(' review <name> <id> Accept/discard an experiment with proof');
|
|
@@ -9533,6 +9539,7 @@ async function memberCommand(subcommand, ...args) {
|
|
|
9533
9539
|
console.log(' atris member wake growth --json');
|
|
9534
9540
|
console.log(' atris member run growth "improve onboarding proof" --verify "npm test" --minutes 30 --json');
|
|
9535
9541
|
console.log(' atris member run growth --mission <mission-id> --max-ticks 1 --json');
|
|
9542
|
+
console.log(' atris member result growth --json');
|
|
9536
9543
|
console.log(' atris member wake growth --execute --confirm-autonomy-policy');
|
|
9537
9544
|
console.log(' atris member supervisor recommendations --json');
|
|
9538
9545
|
console.log(' atris member objective-generator proposals --json');
|
package/commands/mission.js
CHANGED
|
@@ -7,6 +7,7 @@ const readline = require('readline');
|
|
|
7
7
|
const { spawn, spawnSync } = require('child_process');
|
|
8
8
|
const { hasFlag, readFlag, readIntFlag } = require('../lib/arg-parser');
|
|
9
9
|
const { memberProcessPrompt } = require('../lib/member-context');
|
|
10
|
+
const { memberIdentityLine, missionObjectiveLine } = require('../lib/member-identity');
|
|
10
11
|
const {
|
|
11
12
|
compactErrorPayload,
|
|
12
13
|
compactSuccessPayload,
|
|
@@ -8634,7 +8635,7 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = [], cwd =
|
|
|
8634
8635
|
`# Mission Tick ${tickIndex}/${maxTicks}`,
|
|
8635
8636
|
...pingLines,
|
|
8636
8637
|
``,
|
|
8637
|
-
|
|
8638
|
+
missionObjectiveLine(mission.objective),
|
|
8638
8639
|
`**Owner:** ${mission.owner}`,
|
|
8639
8640
|
`**Lane:** ${frozen.lane}`,
|
|
8640
8641
|
`**Cadence:** ${mission.cadence}`,
|
|
@@ -8648,7 +8649,7 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = [], cwd =
|
|
|
8648
8649
|
``,
|
|
8649
8650
|
`## Your task`,
|
|
8650
8651
|
`Do ONE increment of work toward the stop condition. ONE. No more.`,
|
|
8651
|
-
`-
|
|
8652
|
+
`- ${memberIdentityLine(mission.owner)} Read atris/team/${mission.owner}/MEMBER.md (and SOUL.md if present) before acting, work in that identity, inside its scope and stop rules. After your work, append what you did and what you learned to atris/team/${mission.owner}/logs/<today's date>.md.`,
|
|
8652
8653
|
`- FIRST: inspect current mission/task state before acting. Read the relevant files, run \`atris mission status ${mission.id}\`, \`git status\`, or \`atris task list\` as needed so you know what's already done.`,
|
|
8653
8654
|
`- Pick the smallest concrete action that moves the mission forward.`,
|
|
8654
8655
|
`- Before acting, state your single next move in one sentence.`,
|
package/commands/orb.js
CHANGED
|
@@ -42,7 +42,7 @@ function parseEngine(args) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function engineInvocation(engine, prompt) {
|
|
45
|
-
if (engine === 'codex') return { command: 'codex', args: ['exec', prompt] };
|
|
45
|
+
if (engine === 'codex') return { command: 'codex', args: ['exec', '--skip-git-repo-check', prompt] };
|
|
46
46
|
if (engine === 'fast') return { command: 'ax', args: ['--fast', '--print', prompt] };
|
|
47
47
|
return { command: 'claude', args: ['-p', prompt] };
|
|
48
48
|
}
|
package/commands/stream.js
CHANGED
|
@@ -589,12 +589,33 @@ function worktreeTask(root, deps) {
|
|
|
589
589
|
return worktreeConfig(root, deps).task;
|
|
590
590
|
}
|
|
591
591
|
|
|
592
|
+
// The branches a side copy starts from. A commit reachable from any of them
|
|
593
|
+
// was inherited, not made in the copy.
|
|
594
|
+
const WORKTREE_BASE_REFS = Object.freeze([
|
|
595
|
+
'refs/remotes/origin/HEAD',
|
|
596
|
+
'refs/remotes/origin/main',
|
|
597
|
+
'refs/remotes/origin/master',
|
|
598
|
+
'refs/heads/main',
|
|
599
|
+
'refs/heads/master',
|
|
600
|
+
]);
|
|
601
|
+
|
|
602
|
+
function worktreeBaseRefs(root, deps) {
|
|
603
|
+
const result = runGit(root, ['for-each-ref', '--format=%(refname)', ...WORKTREE_BASE_REFS], deps);
|
|
604
|
+
if (result.status !== 0) return [];
|
|
605
|
+
return String(result.stdout || '').split(/\r?\n/).map((line) => line.trim()).filter((ref) => WORKTREE_BASE_REFS.includes(ref));
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Only a commit the copy added on top of its base counts. A fresh copy cut
|
|
609
|
+
// from someone else's commit has none, so its owner shows no work. A repo
|
|
610
|
+
// with no main, master, or origin default keeps the newest commit.
|
|
592
611
|
function collectWorktreeActivityEvents(root, deps, events) {
|
|
593
612
|
const roots = collectWorkspaceRoots(root, deps);
|
|
594
613
|
for (const item of roots) {
|
|
595
614
|
const owner = worktreeOwner(item.root, deps);
|
|
596
615
|
if (!owner) continue;
|
|
597
|
-
const
|
|
616
|
+
const bases = worktreeBaseRefs(item.root, deps);
|
|
617
|
+
const range = bases.length ? ['HEAD', '--not', ...bases] : [];
|
|
618
|
+
const log = runGit(item.root, ['log', '-1', '--format=%cI%x09%an%x09%s', ...range], deps);
|
|
598
619
|
if (log.status !== 0 || !String(log.stdout || '').trim()) continue;
|
|
599
620
|
const [ts, author, subject] = String(log.stdout || '').trim().split('\t');
|
|
600
621
|
pushEvent(events, {
|