atris 3.57.4 → 3.58.6
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 +12 -1
- package/atris/skills/clawhub/member-runtime/SKILL.md +9 -9
- package/atris/skills/create-member/SKILL.md +16 -13
- package/atris/skills/design/SKILL.md +27 -4
- package/atris/skills/engines/SKILL.md +55 -12
- package/atris/skills/x-search/SKILL.md +8 -6
- package/atris/skills/youtube/SKILL.md +66 -21
- package/atris/team/brainstormer/MEMBER.md +2 -2
- package/atris/team/executor/MEMBER.md +3 -3
- package/atris/team/launcher/MEMBER.md +4 -4
- package/atris/team/navigator/MEMBER.md +3 -3
- package/atris/team/researcher/MEMBER.md +2 -2
- package/atris/team/validator/MEMBER.md +1 -1
- package/atris.md +14 -0
- package/ax +153 -23
- package/bin/atris.js +78 -30
- package/commands/aeo.js +4 -4
- package/commands/agents.js +1 -1
- package/commands/align.js +12 -12
- package/commands/analytics.js +3 -3
- package/commands/app.js +5 -5
- package/commands/ask.js +175 -0
- package/commands/auth.js +59 -31
- package/commands/autoland.js +29 -5
- package/commands/autopilot-front.js +6 -6
- package/commands/autopilot.js +90 -90
- package/commands/avail.js +5 -5
- package/commands/bench.js +63 -1
- package/commands/browse.js +2 -2
- package/commands/business.js +12 -12
- package/commands/card.js +2 -2
- package/commands/clean.js +13 -13
- package/commands/close.js +3 -3
- package/commands/compile.js +13 -13
- package/commands/computer.js +94 -27
- package/commands/console.js +5 -5
- package/commands/decide.js +1 -1
- package/commands/deck.js +9 -9
- package/commands/drive.js +4 -4
- package/commands/engine.js +173 -19
- package/commands/errors.js +3 -3
- package/commands/experiments.js +18 -1
- package/commands/feed.js +2 -2
- package/commands/feedback.js +4 -4
- package/commands/fleet.js +12 -12
- package/commands/game.js +1 -1
- package/commands/guide.js +49 -0
- package/commands/improve.js +19 -17
- package/commands/init.js +67 -19
- package/commands/interview.js +7 -7
- package/commands/land.js +40 -15
- package/commands/learn.js +216 -50
- package/commands/lesson.js +2 -2
- package/commands/log.js +1 -1
- package/commands/loops.js +4 -4
- package/commands/member.js +75 -50
- package/commands/mission.js +90 -82
- package/commands/one-lap.js +1 -1
- package/commands/pack.js +74 -8
- package/commands/plugin.js +6 -6
- package/commands/probe.js +9 -9
- package/commands/pull.js +35 -35
- package/commands/pulse.js +1 -1
- package/commands/push.js +21 -21
- package/commands/rainmaker.js +1 -1
- package/commands/recap.js +7 -7
- package/commands/reel.js +2 -2
- package/commands/release.js +3 -3
- package/commands/report.js +1 -1
- package/commands/review.js +5 -5
- package/commands/revisions.js +1 -1
- package/commands/run.js +1 -1
- package/commands/scout.js +3 -3
- package/commands/security-review.js +7 -7
- package/commands/serve.js +9 -9
- package/commands/setup.js +1 -1
- package/commands/sign.js +4 -4
- package/commands/signup.js +4 -4
- package/commands/site.js +8 -3
- package/commands/skill.js +6 -6
- package/commands/slop.js +18 -18
- package/commands/social.js +3 -3
- package/commands/soul.js +14 -14
- package/commands/spaceship.js +1 -1
- package/commands/status.js +2 -2
- package/commands/strings.js +13 -13
- package/commands/study.js +4 -4
- package/commands/sync.js +8 -8
- package/commands/task.js +50 -25
- package/commands/teach.js +2 -2
- package/commands/team.js +1 -1
- package/commands/theme.js +2 -2
- package/commands/tree.js +34 -0
- package/commands/truth.js +7 -7
- package/commands/verify.js +12 -12
- package/commands/visualize.js +2 -2
- package/commands/workflow.js +101 -48
- package/commands/worktree.js +1 -1
- package/commands/write.js +9 -9
- package/commands/x-search.js +93 -22
- package/commands/xp.js +6 -3
- package/commands/youtube.js +1019 -123
- package/lib/accept-verify-gate.js +3 -3
- package/lib/activity-stream.js +1 -1
- package/lib/auto-accept-certified.js +28 -12
- package/lib/autoland.js +6 -6
- package/lib/bench/engines.js +59 -8
- package/lib/bench/report.js +1 -1
- package/lib/bench/runner.js +204 -5
- package/lib/bench/tree-render.js +76 -0
- package/lib/card.js +1 -1
- package/lib/chat-log-scan.js +1 -1
- package/lib/claude-boot-block.js +2 -0
- package/lib/deck-compose.js +1 -1
- package/lib/deck-history.js +2 -2
- package/lib/deck-layout.js +1 -1
- package/lib/deck-review.js +5 -5
- package/lib/deck-schema.js +1 -1
- package/lib/default-verifier.js +1 -1
- package/lib/developer-api.js +3 -0
- package/lib/engine-ask.js +45 -7
- package/lib/engine-registry.js +15 -2
- package/lib/file-ops.js +1 -1
- package/lib/fleet.js +71 -35
- package/lib/html-render.js +1 -1
- package/lib/intents.js +172 -0
- package/lib/journal.js +1 -1
- package/lib/known-commands.js +2 -2
- package/lib/land-green.js +364 -0
- package/lib/lesson-contradiction.js +2 -2
- package/lib/lesson-preflight.js +1 -1
- package/lib/manifest.js +5 -5
- package/lib/member-alive.js +9 -5
- package/lib/memory-view.js +3 -3
- package/lib/mission-protected-lane.js +1 -1
- package/lib/mission-root.js +2 -2
- package/lib/orb-context.js +10 -10
- package/lib/pack-capabilities.js +861 -3
- package/lib/permission-grants.js +2 -2
- package/lib/policy-lessons.js +6 -6
- package/lib/pulse.js +8 -8
- package/lib/receipt-evidence.js +1 -1
- package/lib/reel.js +1 -1
- package/lib/review-integrity.js +1 -1
- package/lib/runner-command.js +35 -5
- package/lib/scorecard.js +4 -4
- package/lib/security-scan.js +5 -5
- package/lib/self-drive.js +1 -1
- package/lib/site-publish.js +372 -0
- package/lib/site.js +1 -1
- package/lib/slides-deck.js +2 -2
- package/lib/state-detection.js +2 -2
- package/lib/sync-telemetry.js +2 -2
- package/lib/task-db.js +17 -10
- package/lib/task-explanation.js +1 -1
- package/lib/task-proof.js +2 -2
- package/lib/task-receipt.js +3 -3
- package/lib/theme.js +12 -12
- package/lib/todo-fallback.js +3 -3
- package/lib/todo-sections.js +1 -1
- package/lib/tree-hash.js +79 -0
- package/lib/usage.js +1 -1
- package/lib/wish-delegate.js +1 -1
- package/lib/workspace-safety.js +4 -4
- package/lib/workspace-scaffold.js +3 -3
- package/package.json +1 -1
- package/scripts/det/concierge-bakeoff.sh +26 -0
- package/scripts/det/ytnotes +82 -5
- package/scripts/det/ytquote-repair.js +0 -1
- package/scripts/det/ytrail-race.js +0 -5
- package/scripts/member-operate.mjs +52 -14
- package/scripts/outbound-artifact-gate.js +0 -3
- package/utils/auth.js +47 -9
- package/utils/config.js +0 -1
- package/scripts/det/checklist-score.js +0 -191
package/commands/ask.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* atris ask: chat with your personal Atris computer.
|
|
3
|
+
*
|
|
4
|
+
* Same brain as texting: your memory, calendar, email, and tools.
|
|
5
|
+
* One-shot: atris ask what should I do today
|
|
6
|
+
* Interactive: atris ask
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const readline = require('readline');
|
|
10
|
+
const { getApiBaseUrl, httpRequest, apiRequestJson } = require('../utils/api');
|
|
11
|
+
const auth = require('../utils/auth');
|
|
12
|
+
|
|
13
|
+
const TIMEOUT_MS = 300000;
|
|
14
|
+
|
|
15
|
+
function stripEvents(text) {
|
|
16
|
+
// Drop inline [[EVENT]]{...} tool chatter; keep only the spoken reply.
|
|
17
|
+
// Tool names are surfaced dimmed in the terminal (texts never see them).
|
|
18
|
+
if (typeof text !== 'string') return text;
|
|
19
|
+
const parts = [];
|
|
20
|
+
const toolNames = [];
|
|
21
|
+
for (let chunk of text.split('[[EVENT]]')) {
|
|
22
|
+
const trimmed = chunk.trimStart();
|
|
23
|
+
if (trimmed.startsWith('{')) {
|
|
24
|
+
let depth = 0;
|
|
25
|
+
let inString = false;
|
|
26
|
+
let escaped = false;
|
|
27
|
+
let end = -1;
|
|
28
|
+
for (let i = 0; i < trimmed.length; i += 1) {
|
|
29
|
+
const ch = trimmed[i];
|
|
30
|
+
if (escaped) { escaped = false; continue; }
|
|
31
|
+
if (ch === '\\') { escaped = true; continue; }
|
|
32
|
+
if (ch === '"') inString = !inString;
|
|
33
|
+
if (inString) continue;
|
|
34
|
+
if (ch === '{') depth += 1;
|
|
35
|
+
if (ch === '}') { depth -= 1; if (depth === 0) { end = i + 1; break; } }
|
|
36
|
+
}
|
|
37
|
+
if (end > 0) {
|
|
38
|
+
try {
|
|
39
|
+
const evt = JSON.parse(trimmed.slice(0, end));
|
|
40
|
+
if (evt.type === 'tool_call' && evt.tool) toolNames.push(evt.tool);
|
|
41
|
+
} catch (_) { /* not JSON, keep going */ }
|
|
42
|
+
chunk = trimmed.slice(end);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
parts.push(chunk);
|
|
46
|
+
}
|
|
47
|
+
const reply = parts.join('').trim();
|
|
48
|
+
if (toolNames.length && process.stdout.isTTY) {
|
|
49
|
+
process.stdout.write(`\x1b[2m[⚙ ${[...new Set(toolNames)].join(', ')}]\x1b[0m\n`);
|
|
50
|
+
}
|
|
51
|
+
return reply;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function startSpinner(label) {
|
|
55
|
+
if (!process.stdout.isTTY) return { stop: () => {} };
|
|
56
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
57
|
+
const started = Date.now();
|
|
58
|
+
let i = 0;
|
|
59
|
+
const timer = setInterval(() => {
|
|
60
|
+
const secs = Math.floor((Date.now() - started) / 1000);
|
|
61
|
+
process.stdout.write(`\r${frames[i % frames.length]} ${label}${secs > 2 ? ` · ${secs}s` : ''} `);
|
|
62
|
+
i += 1;
|
|
63
|
+
}, 90);
|
|
64
|
+
return {
|
|
65
|
+
stop: () => {
|
|
66
|
+
clearInterval(timer);
|
|
67
|
+
process.stdout.write('\r\x1b[2K');
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function typeOut(text) {
|
|
73
|
+
if (!process.stdout.isTTY || text.length > 4000) {
|
|
74
|
+
process.stdout.write(`${text}\n`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
for (const ch of text) {
|
|
78
|
+
process.stdout.write(ch);
|
|
79
|
+
// eslint-disable-next-line no-await-in-loop
|
|
80
|
+
await new Promise((r) => setTimeout(r, ch === '\n' ? 12 : 3));
|
|
81
|
+
}
|
|
82
|
+
process.stdout.write('\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function sendMessage(token, message) {
|
|
86
|
+
const res = await httpRequest(`${getApiBaseUrl()}/message/incoming`, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: {
|
|
89
|
+
Authorization: `Bearer ${token}`,
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify({ message, source: 'cli' }),
|
|
93
|
+
timeoutMs: TIMEOUT_MS,
|
|
94
|
+
});
|
|
95
|
+
if (res.status !== 200) {
|
|
96
|
+
let detail = res.body.toString('utf8').slice(0, 200);
|
|
97
|
+
try { detail = JSON.parse(detail).detail || detail; } catch (_) { /* raw */ }
|
|
98
|
+
throw new Error(`HTTP ${res.status}: ${detail}`);
|
|
99
|
+
}
|
|
100
|
+
return JSON.parse(res.body.toString('utf8'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function askAtris(args) {
|
|
104
|
+
const ensured = await auth.ensureValidCredentials(apiRequestJson);
|
|
105
|
+
if (ensured.error === 'not_logged_in' || !ensured.credentials?.token) {
|
|
106
|
+
console.error('✗ Not logged in. Run "atris login" first.');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
if (ensured.error) {
|
|
110
|
+
console.error(`✗ Authentication failed: ${ensured.detail || ensured.error}. Run "atris login".`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
const { token } = ensured.credentials;
|
|
114
|
+
|
|
115
|
+
const oneShot = (args || []).join(' ').trim();
|
|
116
|
+
if (oneShot === '-h' || oneShot === '--help' || oneShot === 'help') {
|
|
117
|
+
console.log('Usage: atris ask ["message"]');
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log(' Chat with your personal Atris computer: memory, calendar, email, tools.');
|
|
120
|
+
console.log('');
|
|
121
|
+
console.log(' atris ask Interactive chat');
|
|
122
|
+
console.log(' atris ask what is on today One-shot question');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (oneShot) {
|
|
127
|
+
const spinner = startSpinner('thinking');
|
|
128
|
+
try {
|
|
129
|
+
const body = await sendMessage(token, oneShot);
|
|
130
|
+
spinner.stop();
|
|
131
|
+
await typeOut(stripEvents(body.response) || JSON.stringify(body));
|
|
132
|
+
} catch (err) {
|
|
133
|
+
spinner.stop();
|
|
134
|
+
console.error(`✗ ${err.message}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log('Chatting with your computer. Ctrl-C or "bye" to leave.\n');
|
|
141
|
+
await new Promise((resolve) => {
|
|
142
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
143
|
+
let busy = false;
|
|
144
|
+
let closed = false;
|
|
145
|
+
const prompt = () => { if (!closed) rl.question('you › ', onLine); };
|
|
146
|
+
async function onLine(line) {
|
|
147
|
+
const message = line.trim();
|
|
148
|
+
if (!message) { prompt(); return; }
|
|
149
|
+
if (['bye', 'exit', 'quit'].includes(message.toLowerCase())) { rl.close(); return; }
|
|
150
|
+
busy = true;
|
|
151
|
+
const spinner = startSpinner('thinking');
|
|
152
|
+
try {
|
|
153
|
+
const body = await sendMessage(token, message);
|
|
154
|
+
spinner.stop();
|
|
155
|
+
process.stdout.write('\natris › ');
|
|
156
|
+
await typeOut(stripEvents(body.response) || JSON.stringify(body));
|
|
157
|
+
process.stdout.write('\n');
|
|
158
|
+
} catch (err) {
|
|
159
|
+
spinner.stop();
|
|
160
|
+
console.error(`✗ ${err.message}\n`);
|
|
161
|
+
}
|
|
162
|
+
busy = false;
|
|
163
|
+
if (closed) { console.log('\nbye.'); resolve(); return; }
|
|
164
|
+
prompt();
|
|
165
|
+
}
|
|
166
|
+
rl.on('close', () => {
|
|
167
|
+
closed = true;
|
|
168
|
+
// Let an in-flight answer finish printing before we leave.
|
|
169
|
+
if (!busy) { console.log('\nbye.'); resolve(); }
|
|
170
|
+
});
|
|
171
|
+
prompt();
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = { askAtris };
|
package/commands/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { AGENT_TOKEN_EXPIRED_DETAIL, loadCredentials, saveCredentials, deleteCredentials, getCredentialsPath, openBrowser, promptUser, displayAccountSummary, ensureValidCredentials, loadProfile, listProfiles, profileNameFromEmail, deleteProfile, saveProfile, getTokenExpiryEpochSeconds, getTerminalSessionId, setSessionProfile, getSessionProfile, clearSessionProfile, cleanStaleSessions, getSessionsDir } = require('../utils/auth');
|
|
1
|
+
const { AGENT_TOKEN_EXPIRED_DETAIL, decodeJwtClaims, loadCredentials, saveCredentials, deleteCredentials, getCredentialsPath, openBrowser, promptUser, displayAccountSummary, ensureValidCredentials, loadProfile, listProfiles, profileNameFromEmail, deleteProfile, saveProfile, getTokenExpiryEpochSeconds, getTerminalSessionId, setSessionProfile, getSessionProfile, clearSessionProfile, cleanStaleSessions, getSessionsDir } = require('../utils/auth');
|
|
2
2
|
const { getAppBaseUrl, apiRequestJson } = require('../utils/api');
|
|
3
3
|
const { isNonInteractive, wantsJson } = require('../lib/noninteractive');
|
|
4
4
|
const { hasFlag, readFlag } = require('../lib/arg-parser');
|
|
@@ -85,20 +85,45 @@ function extractAgentTokenMeta(data, requested, token) {
|
|
|
85
85
|
return { scopes, dailyCreditCap, expiresAt };
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function isAgentAccessToken(token) {
|
|
89
|
+
return decodeJwtClaims(token)?.type === 'agent_access';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function scopedTokenCandidate(credentials = {}) {
|
|
93
|
+
if (credentials.agent_token) return credentials.agent_token;
|
|
94
|
+
if (
|
|
95
|
+
credentials.source === 'env'
|
|
96
|
+
|| credentials.source === 'agent_token_file'
|
|
97
|
+
|| isAgentAccessToken(credentials.token)
|
|
98
|
+
) {
|
|
99
|
+
return firstNonEmptyString(credentials.token);
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function canMintFromLogin(credentials = {}) {
|
|
105
|
+
const login = firstNonEmptyString(credentials.token);
|
|
106
|
+
const refresh = firstNonEmptyString(credentials.refresh_token);
|
|
107
|
+
if (isAgentAccessToken(login) && !refresh) return false;
|
|
108
|
+
return Boolean(login || refresh);
|
|
109
|
+
}
|
|
110
|
+
|
|
88
111
|
function persistMintedAgentToken(credentials, token, extras = {}) {
|
|
112
|
+
if (isAgentAccessToken(credentials.token)) {
|
|
113
|
+
throw new Error('Refusing to save a scoped agent token as the login token; keep it under agent_token');
|
|
114
|
+
}
|
|
89
115
|
const next = {
|
|
90
|
-
|
|
116
|
+
...credentials,
|
|
91
117
|
refresh_token: extras.refresh_token || credentials.refresh_token || null,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
saved_at: extras.saved_at || new Date().toISOString(),
|
|
118
|
+
agent_token: token,
|
|
119
|
+
agent_token_scopes: extras.scopes || [],
|
|
120
|
+
agent_token_expires_at: extras.expiresAt || null,
|
|
96
121
|
};
|
|
97
122
|
if (credentials.source_profile) {
|
|
98
123
|
saveProfile(credentials.source_profile, next);
|
|
99
124
|
return next;
|
|
100
125
|
}
|
|
101
|
-
saveCredentials(next.token, next.refresh_token, next.email, next.user_id, next.provider);
|
|
126
|
+
saveCredentials(next.token, next.refresh_token, next.email, next.user_id, next.provider, next);
|
|
102
127
|
return next;
|
|
103
128
|
}
|
|
104
129
|
|
|
@@ -139,10 +164,10 @@ async function mintScopedAgentToken(requested = {}, deps = {}) {
|
|
|
139
164
|
? requested.dailyCreditCap
|
|
140
165
|
: DEFAULT_DAILY_CREDIT_CAP;
|
|
141
166
|
|
|
142
|
-
const credentials = load() || {};
|
|
167
|
+
const credentials = await load(api) || {};
|
|
143
168
|
const accessToken = firstNonEmptyString(credentials.token);
|
|
144
169
|
const refreshToken = firstNonEmptyString(credentials.refresh_token);
|
|
145
|
-
if (!
|
|
170
|
+
if (!canMintFromLogin(credentials)) {
|
|
146
171
|
return { ok: false, code: 'not_logged_in', error: NO_STORED_JWT_MESSAGE };
|
|
147
172
|
}
|
|
148
173
|
|
|
@@ -150,7 +175,7 @@ async function mintScopedAgentToken(requested = {}, deps = {}) {
|
|
|
150
175
|
scopes,
|
|
151
176
|
daily_credit_cap: dailyCreditCap,
|
|
152
177
|
};
|
|
153
|
-
let authToken = accessToken || refreshToken;
|
|
178
|
+
let authToken = isAgentAccessToken(accessToken) ? refreshToken : (accessToken || refreshToken);
|
|
154
179
|
let result = await postAgentToken(api, authToken, body);
|
|
155
180
|
if (!result.ok && result.status === 401 && refreshToken && authToken !== refreshToken) {
|
|
156
181
|
authToken = refreshToken;
|
|
@@ -172,7 +197,9 @@ async function mintScopedAgentToken(requested = {}, deps = {}) {
|
|
|
172
197
|
return { ok: false, code: 'missing_token', error: 'backend did not return an agent token' };
|
|
173
198
|
}
|
|
174
199
|
|
|
200
|
+
const meta = extractAgentTokenMeta(result.data, { scopes, dailyCreditCap }, minted);
|
|
175
201
|
persist(credentials, minted, {
|
|
202
|
+
...meta,
|
|
176
203
|
refresh_token: firstNonEmptyString(result.data && result.data.refresh_token) || refreshToken,
|
|
177
204
|
saved_at: now(),
|
|
178
205
|
});
|
|
@@ -180,7 +207,8 @@ async function mintScopedAgentToken(requested = {}, deps = {}) {
|
|
|
180
207
|
return {
|
|
181
208
|
ok: true,
|
|
182
209
|
token: minted,
|
|
183
|
-
meta
|
|
210
|
+
meta,
|
|
211
|
+
storedIn: credentials.source_profile ? `profile ${credentials.source_profile}, agent_token` : '~/.atris/credentials.json, agent_token',
|
|
184
212
|
};
|
|
185
213
|
}
|
|
186
214
|
|
|
@@ -191,24 +219,21 @@ async function ensureBilledCommandAuth(scope, deps = {}) {
|
|
|
191
219
|
}
|
|
192
220
|
|
|
193
221
|
const api = deps.apiRequestJson || apiRequestJson;
|
|
194
|
-
const ensureFn = deps.ensureValidCredentials || ensureValidCredentials;
|
|
195
222
|
const load = deps.loadCredentials || loadCredentials;
|
|
196
223
|
const mint = deps.mintScopedAgentToken || mintScopedAgentToken;
|
|
197
224
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
225
|
+
const ensured = !deps.forceMint && deps.ensureValidCredentials
|
|
226
|
+
? await deps.ensureValidCredentials(api) : null;
|
|
227
|
+
const credentials = ensured?.credentials || await load(api) || {};
|
|
228
|
+
const candidate = scopedTokenCandidate(credentials);
|
|
229
|
+
const claims = decodeJwtClaims(candidate);
|
|
230
|
+
const scopes = claims?.scopes || credentials.agent_token_scopes || [];
|
|
231
|
+
const expiry = claims?.exp ? claims.exp * 1000 : Date.parse(credentials.agent_token_expires_at);
|
|
232
|
+
if (!deps.forceMint && candidate && Array.isArray(scopes) && scopes.includes(wanted) && Number.isFinite(expiry) && expiry > Date.now()) {
|
|
233
|
+
return { ok: true, token: candidate, minted: false, credentials };
|
|
208
234
|
}
|
|
209
235
|
|
|
210
|
-
|
|
211
|
-
if (!firstNonEmptyString(credentials.token, credentials.refresh_token)) {
|
|
236
|
+
if (!canMintFromLogin(credentials)) {
|
|
212
237
|
return { ok: false, error: NO_STORED_JWT_MESSAGE };
|
|
213
238
|
}
|
|
214
239
|
|
|
@@ -267,6 +292,7 @@ async function mintAgentToken(args = [], deps = {}) {
|
|
|
267
292
|
output(JSON.stringify({
|
|
268
293
|
ok: true,
|
|
269
294
|
minted: true,
|
|
295
|
+
stored_in: minted.storedIn,
|
|
270
296
|
scopes: minted.meta.scopes,
|
|
271
297
|
daily_credit_cap: minted.meta.dailyCreditCap,
|
|
272
298
|
expires_at: minted.meta.expiresAt,
|
|
@@ -275,6 +301,7 @@ async function mintAgentToken(args = [], deps = {}) {
|
|
|
275
301
|
}
|
|
276
302
|
|
|
277
303
|
printAgentTokenMint(minted.meta, output);
|
|
304
|
+
output(`stored in ${minted.storedIn}`);
|
|
278
305
|
return 0;
|
|
279
306
|
}
|
|
280
307
|
|
|
@@ -565,7 +592,7 @@ async function switchAccount() {
|
|
|
565
592
|
const profile = loadProfile(name);
|
|
566
593
|
const email = profile?.email || 'unknown';
|
|
567
594
|
const marker = name === currentName ? ' ← active' : '';
|
|
568
|
-
console.log(` ${i + 1}. ${name}
|
|
595
|
+
console.log(` ${i + 1}. ${name}, ${email}${marker}`);
|
|
569
596
|
});
|
|
570
597
|
console.log(` ${profiles.length + 1}. Add new account`);
|
|
571
598
|
console.log(` ${profiles.length + 2}. Cancel`);
|
|
@@ -620,13 +647,13 @@ function activateProfile(name, currentName, { global = false } = {}) {
|
|
|
620
647
|
}
|
|
621
648
|
|
|
622
649
|
if (global || !getTerminalSessionId()) {
|
|
623
|
-
// Global switch
|
|
650
|
+
// Global switch, write to credentials.json (affects all terminals)
|
|
624
651
|
const credentialsPath = getCredentialsPath();
|
|
625
652
|
fs.writeFileSync(credentialsPath, JSON.stringify(profile, null, 2));
|
|
626
653
|
try { fs.chmodSync(credentialsPath, 0o600); } catch {}
|
|
627
|
-
console.log(`Switched to ${profile.email || name} (global
|
|
654
|
+
console.log(`Switched to ${profile.email || name} (global, all terminals)`);
|
|
628
655
|
} else {
|
|
629
|
-
// Per-terminal switch
|
|
656
|
+
// Per-terminal switch, write session file (only this terminal)
|
|
630
657
|
setSessionProfile(name);
|
|
631
658
|
console.log(`Switched to ${profile.email || name}`);
|
|
632
659
|
}
|
|
@@ -682,7 +709,7 @@ function useAccount() {
|
|
|
682
709
|
if (!process.stdout.isTTY) {
|
|
683
710
|
process.stdout.write(`export ATRIS_PROFILE=${match}\n`);
|
|
684
711
|
} else {
|
|
685
|
-
// Interactive
|
|
712
|
+
// Interactive, print instructions
|
|
686
713
|
console.log(`export ATRIS_PROFILE=${match}`);
|
|
687
714
|
console.log(`\n# Run this to activate ${email} in this terminal:`);
|
|
688
715
|
console.log(`# eval "$(atris use ${targetName})"`);
|
|
@@ -893,7 +920,7 @@ function switchSession() {
|
|
|
893
920
|
if (termId) {
|
|
894
921
|
setSessionProfile(name);
|
|
895
922
|
} else {
|
|
896
|
-
// No terminal ID
|
|
923
|
+
// No terminal ID, global fallback
|
|
897
924
|
const credentialsPath = getCredentialsPath();
|
|
898
925
|
fs.writeFileSync(credentialsPath, JSON.stringify(profile, null, 2));
|
|
899
926
|
try { fs.chmodSync(credentialsPath, 0o600); } catch {}
|
|
@@ -910,7 +937,7 @@ function shellInit() {
|
|
|
910
937
|
'# Atris per-terminal account switching',
|
|
911
938
|
'# Added by: eval "$(atris shell-init)"',
|
|
912
939
|
'_atris_session_id() {',
|
|
913
|
-
' # Detect terminal session ID
|
|
940
|
+
' # Detect terminal session ID, same priority as the Node.js code',
|
|
914
941
|
' local _sid="${TERM_SESSION_ID:-${ITERM_SESSION_ID:-${TMUX_PANE:-${WT_SESSION:-${WEZTERM_PANE:-}}}}}"',
|
|
915
942
|
' if [[ -n "$_sid" ]]; then echo "$_sid"; return; fi',
|
|
916
943
|
' # Fallback: TTY device (unique per macOS/Linux terminal tab)',
|
|
@@ -982,6 +1009,7 @@ module.exports = {
|
|
|
982
1009
|
shellInit,
|
|
983
1010
|
parseAgentTokenArgs,
|
|
984
1011
|
mintAgentToken,
|
|
1012
|
+
persistMintedAgentToken,
|
|
985
1013
|
ensureBilledCommandAuth,
|
|
986
1014
|
wantsAgentToken,
|
|
987
1015
|
};
|
package/commands/autoland.js
CHANGED
|
@@ -752,7 +752,7 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
|
|
|
752
752
|
// the landing gate reuse the live certification verifier result without
|
|
753
753
|
// persisting trust across heartbeats. Denied lanes and check-less proofs wait.
|
|
754
754
|
// No hardcoded --limit here: a fixed low cap (this used to be 12) silently
|
|
755
|
-
// undercounts a real backlog every single tick
|
|
755
|
+
// undercounts a real backlog every single tick: 12/hour forever even with
|
|
756
756
|
// 78 certified rows waiting. Let `atris task auto-accept-certified` apply
|
|
757
757
|
// its own default (12 without --all, a high safety cap under --all) so a
|
|
758
758
|
// policy with accept_all:true actually drains the full certified set.
|
|
@@ -763,7 +763,7 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
|
|
|
763
763
|
try {
|
|
764
764
|
const parsed = JSON.parse(accept.stdout);
|
|
765
765
|
const results = Array.isArray(parsed.results) ? parsed.results : [];
|
|
766
|
-
// A refused sweep (ok:false
|
|
766
|
+
// A refused sweep (ok:false, a guard tripped, policy race) carries no
|
|
767
767
|
// summary fields. Name the reason instead of leaving nulls that read as
|
|
768
768
|
// "no work": a blind heartbeat must say WHY it is blind.
|
|
769
769
|
if (parsed.ok === false) {
|
|
@@ -841,7 +841,7 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
|
|
|
841
841
|
}
|
|
842
842
|
|
|
843
843
|
// 3b. janitor, every tick: a mission left paused past 48h and a worktree
|
|
844
|
-
// already merged into base are the same rot pattern as an unlanded branch
|
|
844
|
+
// already merged into base are the same rot pattern as an unlanded branch,
|
|
845
845
|
// left alone they accumulate until a human runs `mission stop` and
|
|
846
846
|
// `worktree cleanup --apply` by hand, which is exactly the chore autoland
|
|
847
847
|
// exists to remove. Off switch: policy.janitor === false (default on, the
|
|
@@ -880,6 +880,30 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
|
|
|
880
880
|
recordJanitorState(state, { stopped: stoppedMissions, held: heldMissions, worktrees: receipt.worktrees_reaped });
|
|
881
881
|
}
|
|
882
882
|
|
|
883
|
+
// 3b2. green landing, every tick: one stale branch that merges clean and
|
|
884
|
+
// passes the full verify in a scratch checkout lands on base and is pushed.
|
|
885
|
+
// A red branch opens a close flag the same hour instead of waiting for the
|
|
886
|
+
// TTL reap to salvage it. Off by policy: green_landing:false.
|
|
887
|
+
if (policy.green_landing !== false) {
|
|
888
|
+
try {
|
|
889
|
+
const { landStaleGreenBranches, summaryLine } = require('../lib/land-green');
|
|
890
|
+
const green = landStaleGreenBranches(root, {
|
|
891
|
+
verifyCommand: policy.verify_command || undefined,
|
|
892
|
+
limit: Number(policy.green_landing_limit) > 0 ? Number(policy.green_landing_limit) : 1,
|
|
893
|
+
});
|
|
894
|
+
receipt.green_landing = {
|
|
895
|
+
candidates: green.candidates.length,
|
|
896
|
+
landed: green.landed,
|
|
897
|
+
red: green.red,
|
|
898
|
+
verdicts: green.verdicts.map((v) => ({ branch: v.branch, action: v.action, reason: v.reason || null, detail: v.detail || null, failing: v.verify ? v.verify.failing : undefined })),
|
|
899
|
+
summary: summaryLine(green),
|
|
900
|
+
};
|
|
901
|
+
state.green_landing = { at: green.at, ...receipt.green_landing };
|
|
902
|
+
} catch (err) {
|
|
903
|
+
receipt.green_landing_error = String((err && err.message) || err).slice(0, 200);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
883
907
|
// 3c. landing sweep, every tick: merged branch residue is cleared,
|
|
884
908
|
// TTL-expired work is salvaged before deletion, and 48h-stale active work
|
|
885
909
|
// is carried into the digest instead of silently aging forever.
|
|
@@ -952,7 +976,7 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
|
|
|
952
976
|
}
|
|
953
977
|
}
|
|
954
978
|
|
|
955
|
-
// 5b. daily keep/revert experiment
|
|
979
|
+
// 5b. daily keep/revert experiment, self-gated inside experiments daily;
|
|
956
980
|
// hourly ticks are harmless no-ops after the first run each day.
|
|
957
981
|
if (policy.daily_experiment !== false) {
|
|
958
982
|
const daily = runOwnCli(root, ['experiments', 'daily', '--json']);
|
|
@@ -1021,7 +1045,7 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
|
|
|
1021
1045
|
// Deltas alone ("0 certified, 0 landed") are true and useless: they read
|
|
1022
1046
|
// as "nothing to do" whether the queue is empty or 19-deep and wedged.
|
|
1023
1047
|
// Always print the standing backlog, and name work that cleared every
|
|
1024
|
-
// gate and still did not land
|
|
1048
|
+
// gate and still did not land. That is the shape of a stuck loop.
|
|
1025
1049
|
const stuckNote = stuckBacklogNote(receipt);
|
|
1026
1050
|
const summary = `autoland tick: ${receipt.reviews_certified ?? 0} reviews certified, ${receipt.landed.length} landed${receipt.landed.length ? ` (${receipt.landed.join(', ')})` : ''}, ${receipt.alarms} alarms${stuckNote}, digest ${digestTickStatus(receipt)}${reapNote}${janitorNote}${heldNote}${wishNote}${engineSweepNote}${receiptNote}`;
|
|
1027
1051
|
const validationLine = engineAnswerValidationLine(receipt);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// atris autopilot: point it at the workspace and it keeps going.
|
|
2
|
-
// Each leg picks the most logical work
|
|
3
|
-
// member choosing useful work, then a self-chosen mission
|
|
2
|
+
// Each leg picks the most logical work, a moving mission first, then a
|
|
3
|
+
// member choosing useful work, then a self-chosen mission, and drives it
|
|
4
4
|
// through the mission runtime. It loops until stopped:
|
|
5
5
|
// atris autopilot stop (from any terminal) or Ctrl-C here.
|
|
6
6
|
// The old suggest→justify→execute loop lives on behind `atris autopilot --legacy`.
|
|
@@ -57,7 +57,7 @@ function positiveNumber(value) {
|
|
|
57
57
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// `--iterations=N` or `--iterations N
|
|
60
|
+
// `--iterations=N` or `--iterations N`, the leg cap legacy callers pass.
|
|
61
61
|
function maxLegsFlag(args) {
|
|
62
62
|
const eq = args.find((a) => typeof a === 'string' && a.startsWith('--iterations='));
|
|
63
63
|
if (eq) return positiveNumber(eq.split('=')[1]);
|
|
@@ -187,7 +187,7 @@ function autopilotStatus(root = process.cwd()) {
|
|
|
187
187
|
const state = readState(root);
|
|
188
188
|
if (!state) { console.log('Autopilot is not running.'); return 0; }
|
|
189
189
|
const alive = pidAlive(state.pid);
|
|
190
|
-
console.log(`Autopilot ${alive ? 'running' : 'not running (stale state)'}
|
|
190
|
+
console.log(`Autopilot ${alive ? 'running' : 'not running (stale state)'}, pid ${state.pid}, started ${state.started_at}, legs ${state.legs || 0}`);
|
|
191
191
|
if (state.current_leg) console.log(`Current leg: ${state.current_leg}`);
|
|
192
192
|
if (!alive) clearState(root);
|
|
193
193
|
return 0;
|
|
@@ -276,7 +276,7 @@ async function autopilotFront(args = []) {
|
|
|
276
276
|
|
|
277
277
|
const current = { child: null };
|
|
278
278
|
// `autopilot stop` SIGTERMs this pid directly, so the farewell must print
|
|
279
|
-
// here
|
|
279
|
+
// here, the signal always beats the loop's own stop-file check.
|
|
280
280
|
const onSignal = () => {
|
|
281
281
|
if (current.child) { try { current.child.kill('SIGTERM'); } catch {} }
|
|
282
282
|
const reason = stopRequested(root) ? 'stop requested' : 'interrupted';
|
|
@@ -314,7 +314,7 @@ async function autopilotFront(args = []) {
|
|
|
314
314
|
if (maxLegs && state.legs >= maxLegs) { stopReason = `leg cap reached (--iterations=${maxLegs})`; break; }
|
|
315
315
|
if (stopRequested(root)) { stopReason = 'stop requested'; break; }
|
|
316
316
|
|
|
317
|
-
// A leg that finishes in seconds means no real work happened
|
|
317
|
+
// A leg that finishes in seconds means no real work happened, a crash,
|
|
318
318
|
// or a degenerate mission whose ticks record instantly (exit 0). Either
|
|
319
319
|
// way, back off and stop instead of hot-looping on it.
|
|
320
320
|
const legSeconds = (Date.now() - legStarted) / 1000;
|