atris 3.31.0 → 3.33.3
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 +46 -4
- package/atris/CLAUDE.md +8 -0
- package/atris.md +8 -0
- package/ax +458 -15
- package/bin/atris.js +200 -76
- package/commands/autoland.js +78 -12
- package/commands/autopilot-front.js +273 -0
- package/commands/engine.js +299 -0
- package/commands/init.js +21 -0
- package/commands/integrations.js +147 -0
- package/commands/member.js +85 -0
- package/commands/mission.js +500 -43
- package/commands/run-front.js +144 -0
- package/commands/run.js +9 -6
- package/commands/sign.js +90 -0
- package/commands/task.js +380 -20
- package/commands/truth.js +72 -11
- package/lib/autoland.js +133 -25
- package/lib/fleet.js +354 -0
- package/lib/inspect-fields.js +174 -0
- package/lib/operator-next.js +7 -0
- package/lib/pulse.js +4 -3
- package/lib/runner-command.js +54 -11
- package/lib/task-db.js +57 -2
- package/package.json +3 -2
- package/scripts/agent_worktree.py +72 -0
package/commands/integrations.js
CHANGED
|
@@ -124,6 +124,32 @@ async function gmailRead(messageId) {
|
|
|
124
124
|
console.log(msg.body || msg.snippet || '(no body)');
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
async function gmailArchive(messageIds) {
|
|
128
|
+
const ids = (messageIds || []).map(id => String(id || '').trim()).filter(Boolean);
|
|
129
|
+
if (!ids.length) {
|
|
130
|
+
console.error('Usage: atris gmail archive <message_id> [<message_id> ...]');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const token = await getAuthToken();
|
|
135
|
+
|
|
136
|
+
console.log(`Archiving ${ids.length} message${ids.length === 1 ? '' : 's'}...`);
|
|
137
|
+
|
|
138
|
+
const result = await apiRequestJson('/integrations/gmail/messages/batch-archive', {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
token,
|
|
141
|
+
body: { message_ids: ids },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!result.ok) {
|
|
145
|
+
console.error(`Error: ${result.error || 'Failed to archive'}`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const archived = result.data?.archived ?? result.data?.count ?? ids.length;
|
|
150
|
+
console.log(`Archived ${archived} message${archived === 1 ? '' : 's'}. They stay searchable in All Mail.`);
|
|
151
|
+
}
|
|
152
|
+
|
|
127
153
|
async function gmailCommand(subcommand, ...args) {
|
|
128
154
|
switch (subcommand) {
|
|
129
155
|
case 'inbox':
|
|
@@ -133,10 +159,14 @@ async function gmailCommand(subcommand, ...args) {
|
|
|
133
159
|
case 'read':
|
|
134
160
|
await gmailRead(args[0]);
|
|
135
161
|
break;
|
|
162
|
+
case 'archive':
|
|
163
|
+
await gmailArchive(args);
|
|
164
|
+
break;
|
|
136
165
|
default:
|
|
137
166
|
console.log('Gmail commands:');
|
|
138
167
|
console.log(' atris gmail inbox - List recent emails');
|
|
139
168
|
console.log(' atris gmail read <id> - Read specific email');
|
|
169
|
+
console.log(' atris gmail archive <id> [...] - Archive messages (reversible, All Mail keeps them)');
|
|
140
170
|
}
|
|
141
171
|
}
|
|
142
172
|
|
|
@@ -474,6 +504,58 @@ async function calendarDate(dateText) {
|
|
|
474
504
|
await calendarRange(`Events on ${dateText}`, { timeMin: start.toISOString(), timeMax: end.toISOString() });
|
|
475
505
|
}
|
|
476
506
|
|
|
507
|
+
async function calendarAdd(args) {
|
|
508
|
+
// atris calendar add "Title" --date YYYY-MM-DD --from HH:MM --to HH:MM
|
|
509
|
+
// or: --start <ISO> --end <ISO>; optional --location, --description
|
|
510
|
+
const rest = [...(args || [])];
|
|
511
|
+
const flags = {};
|
|
512
|
+
const positional = [];
|
|
513
|
+
for (let i = 0; i < rest.length; i++) {
|
|
514
|
+
const arg = String(rest[i] || '');
|
|
515
|
+
if (arg.startsWith('--')) {
|
|
516
|
+
flags[arg.slice(2)] = String(rest[i + 1] || '');
|
|
517
|
+
i += 1;
|
|
518
|
+
} else {
|
|
519
|
+
positional.push(arg);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const summary = positional.join(' ').trim();
|
|
523
|
+
let start = String(flags.start || '').trim();
|
|
524
|
+
let end = String(flags.end || '').trim();
|
|
525
|
+
if (!start && flags.date && flags.from) start = `${flags.date}T${flags.from}:00`;
|
|
526
|
+
if (!end && flags.date && flags.to) end = `${flags.date}T${flags.to}:00`;
|
|
527
|
+
if (!summary || !start || !end) {
|
|
528
|
+
console.error('Usage: atris calendar add "Title" --date YYYY-MM-DD --from HH:MM --to HH:MM');
|
|
529
|
+
console.error(' or: atris calendar add "Title" --start <ISO datetime> --end <ISO datetime>');
|
|
530
|
+
process.exit(1);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const token = await getAuthToken();
|
|
534
|
+
|
|
535
|
+
console.log(`Creating event: ${summary} (${start} to ${end})...`);
|
|
536
|
+
|
|
537
|
+
const body = { summary, start, end };
|
|
538
|
+
if (flags.location) body.location = flags.location;
|
|
539
|
+
if (flags.description) body.description = flags.description;
|
|
540
|
+
if (flags.timezone) body.timezone = flags.timezone;
|
|
541
|
+
|
|
542
|
+
const result = await apiRequestJson('/integrations/google-calendar/events', {
|
|
543
|
+
method: 'POST',
|
|
544
|
+
token,
|
|
545
|
+
body,
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
if (!result.ok) {
|
|
549
|
+
console.error(`Error: ${result.error || 'Failed to create event'}`);
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const created = result.data || {};
|
|
554
|
+
console.log(`Created: ${created.summary || summary}`);
|
|
555
|
+
if (created.htmlLink) console.log(`Link: ${created.htmlLink}`);
|
|
556
|
+
console.log(`Verified on calendar: ${created.verified ? 'yes' : 'no'}`);
|
|
557
|
+
}
|
|
558
|
+
|
|
477
559
|
async function calendarCommand(subcommand, ...args) {
|
|
478
560
|
switch (subcommand) {
|
|
479
561
|
case 'today':
|
|
@@ -492,6 +574,10 @@ async function calendarCommand(subcommand, ...args) {
|
|
|
492
574
|
case 'search':
|
|
493
575
|
await calendarSearch(args);
|
|
494
576
|
break;
|
|
577
|
+
case 'add':
|
|
578
|
+
case 'create':
|
|
579
|
+
await calendarAdd(args);
|
|
580
|
+
break;
|
|
495
581
|
default:
|
|
496
582
|
console.log('Calendar commands:');
|
|
497
583
|
console.log(' atris calendar today - Show today\'s events');
|
|
@@ -499,6 +585,7 @@ async function calendarCommand(subcommand, ...args) {
|
|
|
499
585
|
console.log(' atris calendar week - Show this week\'s events');
|
|
500
586
|
console.log(' atris calendar date YYYY-MM-DD - Show events on a date');
|
|
501
587
|
console.log(' atris calendar search <query> [--days 30] [--limit 20] [--timeout-ms 10000] [--json] - Search upcoming events');
|
|
588
|
+
console.log(' atris calendar add \"Title\" --date YYYY-MM-DD --from HH:MM --to HH:MM - Create an event');
|
|
502
589
|
}
|
|
503
590
|
}
|
|
504
591
|
|
|
@@ -737,6 +824,58 @@ async function slackSearch(query, args = []) {
|
|
|
737
824
|
formatSlackMessages(messages);
|
|
738
825
|
}
|
|
739
826
|
|
|
827
|
+
async function slackSend(channel, textParts) {
|
|
828
|
+
const target = String(channel || '').trim();
|
|
829
|
+
const text = (textParts || []).join(' ').trim();
|
|
830
|
+
if (!target || !text) {
|
|
831
|
+
console.error('Usage: atris slack send <channel> "<message>"');
|
|
832
|
+
process.exit(1);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const token = await getAuthToken();
|
|
836
|
+
|
|
837
|
+
console.log(`Sending to ${target}...`);
|
|
838
|
+
|
|
839
|
+
const result = await apiRequestJson('/integrations/slack/me/send', {
|
|
840
|
+
method: 'POST',
|
|
841
|
+
token,
|
|
842
|
+
body: { channel: target, text },
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
if (!result.ok) {
|
|
846
|
+
console.error(`Error: ${result.error || 'Failed to send'}`);
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
console.log(`Sent to ${target} as you.`);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
async function slackDm(userId, textParts) {
|
|
854
|
+
const target = String(userId || '').trim();
|
|
855
|
+
const text = (textParts || []).join(' ').trim();
|
|
856
|
+
if (!target || !text) {
|
|
857
|
+
console.error('Usage: atris slack dm <slack_user_id> "<message>"');
|
|
858
|
+
process.exit(1);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const token = await getAuthToken();
|
|
862
|
+
|
|
863
|
+
console.log(`Sending DM to ${target}...`);
|
|
864
|
+
|
|
865
|
+
const result = await apiRequestJson('/integrations/slack/me/dm', {
|
|
866
|
+
method: 'POST',
|
|
867
|
+
token,
|
|
868
|
+
body: { slack_user_id: target, text },
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
if (!result.ok) {
|
|
872
|
+
console.error(`Error: ${result.error || 'Failed to send DM'}`);
|
|
873
|
+
process.exit(1);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
console.log(`DM sent to ${target} as you.`);
|
|
877
|
+
}
|
|
878
|
+
|
|
740
879
|
async function slackCommand(subcommand, ...args) {
|
|
741
880
|
switch (subcommand) {
|
|
742
881
|
case 'channels':
|
|
@@ -753,12 +892,20 @@ async function slackCommand(subcommand, ...args) {
|
|
|
753
892
|
case 'search':
|
|
754
893
|
await slackSearch(argsWithoutLimit(args).join(' '), args);
|
|
755
894
|
break;
|
|
895
|
+
case 'send':
|
|
896
|
+
await slackSend(args[0], args.slice(1));
|
|
897
|
+
break;
|
|
898
|
+
case 'dm':
|
|
899
|
+
await slackDm(args[0], args.slice(1));
|
|
900
|
+
break;
|
|
756
901
|
default:
|
|
757
902
|
console.log('Slack commands:');
|
|
758
903
|
console.log(' atris slack channels - List Slack channels');
|
|
759
904
|
console.log(' atris slack dms - List Slack DMs');
|
|
760
905
|
console.log(' atris slack messages <channel> - Read recent messages');
|
|
761
906
|
console.log(' atris slack search <query> - Search Slack messages');
|
|
907
|
+
console.log(' atris slack send <channel> "<message>" - Send a message as you');
|
|
908
|
+
console.log(' atris slack dm <user_id> "<message>" - DM someone as you');
|
|
762
909
|
}
|
|
763
910
|
}
|
|
764
911
|
|
package/commands/member.js
CHANGED
|
@@ -611,6 +611,89 @@ function startMemberRunMission(name, missionText, args = []) {
|
|
|
611
611
|
}
|
|
612
612
|
}
|
|
613
613
|
|
|
614
|
+
// atris member ping <name> "<message>" — talk to a busy member mid-run.
|
|
615
|
+
// Two delivery lanes: the member's most recently touched live mission
|
|
616
|
+
// (including --worktree missions in sibling checkouts), whose next tick
|
|
617
|
+
// consumes the note, and the member's claimed task dialogue, which task-loop
|
|
618
|
+
// agents re-read every step without ever ticking a mission. The ping lands on
|
|
619
|
+
// every lane that exists and errors only when neither does.
|
|
620
|
+
function pingClaimedTaskDialogue(name, text, from) {
|
|
621
|
+
let taskDb;
|
|
622
|
+
try {
|
|
623
|
+
taskDb = require('../lib/task-db');
|
|
624
|
+
} catch {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
const db = taskDb.open();
|
|
629
|
+
const local = taskDb.listTasks(db, { workspaceRoot: taskDb.workspaceRoot(), status: 'claimed', claimedBy: name });
|
|
630
|
+
const pool = local.length ? local : taskDb.listTasks(db, { status: 'claimed', claimedBy: name });
|
|
631
|
+
if (!pool.length) return null;
|
|
632
|
+
const task = [...pool].sort((a, b) => Number(b.updated_at || 0) - Number(a.updated_at || 0))[0];
|
|
633
|
+
const result = taskDb.noteTask(db, { id: task.id, actor: from, content: text });
|
|
634
|
+
if (!result.noted) return null;
|
|
635
|
+
return { task_id: task.id, title: task.title };
|
|
636
|
+
} catch {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function memberPing(name, ...args) {
|
|
642
|
+
if (!name || name === '--help' || name === '-h') {
|
|
643
|
+
console.log('Usage: atris member ping <name> "<message>" [--from <who>] [--json]');
|
|
644
|
+
console.log('Leaves the note on the member\'s active mission and claimed task dialogue; whichever loop the member runs reads it next.');
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const asJson = args.includes('--json');
|
|
648
|
+
const rest = args.filter((a) => a !== '--json');
|
|
649
|
+
let from = process.env.USER || 'operator';
|
|
650
|
+
const fromIdx = rest.indexOf('--from');
|
|
651
|
+
if (fromIdx !== -1) {
|
|
652
|
+
from = rest[fromIdx + 1] || from;
|
|
653
|
+
rest.splice(fromIdx, 2);
|
|
654
|
+
}
|
|
655
|
+
const text = rest.filter((a) => !a.startsWith('--')).join(' ').trim();
|
|
656
|
+
if (!text) {
|
|
657
|
+
console.error('atris member ping: message required');
|
|
658
|
+
process.exit(2);
|
|
659
|
+
}
|
|
660
|
+
const missionMod = require('./mission');
|
|
661
|
+
const terminal = new Set(['complete', 'stopped', 'failed']);
|
|
662
|
+
const candidates = [
|
|
663
|
+
...missionMod.listMissions(process.cwd()),
|
|
664
|
+
...missionMod.listWorktreeRollupMissions(process.cwd()),
|
|
665
|
+
]
|
|
666
|
+
.filter((m) => m && m.owner === name && !terminal.has(m.status))
|
|
667
|
+
.sort((a, b) => String(b.updated_at || b.created_at || '').localeCompare(String(a.updated_at || a.created_at || '')));
|
|
668
|
+
|
|
669
|
+
const taskNote = pingClaimedTaskDialogue(name, text, from);
|
|
670
|
+
|
|
671
|
+
if (!candidates.length && !taskNote) {
|
|
672
|
+
console.error(`atris member ping: no live mission or claimed task for "${name}". Start one: atris member run ${name} "<objective>"`);
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const mission = candidates.length
|
|
677
|
+
? missionMod.pingMission([candidates[0].id, text, '--from', from], { silent: true })
|
|
678
|
+
: null;
|
|
679
|
+
const pendingPings = mission ? (mission.pings || []).filter((p) => p && !p.consumed_at).length : null;
|
|
680
|
+
|
|
681
|
+
if (asJson) {
|
|
682
|
+
console.log(JSON.stringify({
|
|
683
|
+
ok: true,
|
|
684
|
+
action: 'member_ping',
|
|
685
|
+
member: name,
|
|
686
|
+
mission_id: mission ? mission.id : null,
|
|
687
|
+
pending_pings: pendingPings,
|
|
688
|
+
task_id: taskNote ? taskNote.task_id : null,
|
|
689
|
+
}));
|
|
690
|
+
} else {
|
|
691
|
+
if (mission) console.log(`pinged ${mission.id} — the next tick reads it (${pendingPings} unread).`);
|
|
692
|
+
if (taskNote) console.log(`pinged task ${taskNote.task_id} dialogue — ${name} reads it on the next step.`);
|
|
693
|
+
}
|
|
694
|
+
return { mission, task_note: taskNote };
|
|
695
|
+
}
|
|
696
|
+
|
|
614
697
|
function memberRun(name, ...args) {
|
|
615
698
|
if (!name || name === '--help' || name === '-h' || hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
616
699
|
console.log('Usage: atris member run <name> ["mission text"] [--minutes N|--hours N] [--json]');
|
|
@@ -8063,6 +8146,8 @@ async function memberCommand(subcommand, ...args) {
|
|
|
8063
8146
|
return memberWake(args[0], ...args.slice(1));
|
|
8064
8147
|
case 'run':
|
|
8065
8148
|
return memberRun(args[0], ...args.slice(1));
|
|
8149
|
+
case 'ping':
|
|
8150
|
+
return memberPing(args[0], ...args.slice(1));
|
|
8066
8151
|
case 'loop':
|
|
8067
8152
|
return memberLoop(args[0], ...args.slice(1));
|
|
8068
8153
|
case 'alive':
|