atris 3.56.1 → 3.56.2
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 +17 -5
- package/commands/auth.js +6 -1
- package/commands/engine.js +7 -0
- package/commands/experiments.js +178 -0
- package/commands/founder.js +12 -0
- package/commands/human-missions.js +46 -8
- package/commands/launchpad.js +45 -10
- package/commands/now.js +36 -1
- package/commands/recap.js +34 -3
- package/commands/status.js +10 -2
- package/commands/workflow.js +4 -3
- package/commands/youtube.js +310 -8
- package/lib/apply-gate.js +15 -7
- package/lib/config-guard.js +106 -0
- package/lib/engine-registry.js +14 -4
- package/lib/first-minute.js +21 -2
- package/lib/pack-capabilities.js +13 -3
- package/lib/workspace-scaffold.js +3 -1
- package/package.json +1 -1
- package/utils/auth.js +76 -7
package/lib/first-minute.js
CHANGED
|
@@ -8,6 +8,7 @@ const { isGenericScratchRoot } = require('./scratch-root');
|
|
|
8
8
|
|
|
9
9
|
const FIRST_USE_COMMAND = 'atris "help me choose the first useful step for this project"';
|
|
10
10
|
const FIRST_TALK_ASK = 'what do you want here';
|
|
11
|
+
const CLAIMED_KEEP_WORKING_VERIFY = 'git diff --check';
|
|
11
12
|
const LATER_NOTE_FILE = '.atris-later';
|
|
12
13
|
const BARE_ATRIS_FLAGS = new Set(['--yes', '-y', '--json', '--verbose']);
|
|
13
14
|
const NOT_A_PERSON = new Set([
|
|
@@ -237,6 +238,17 @@ function loadLatestRecap(root = process.cwd()) {
|
|
|
237
238
|
return title ? { title, file: hit.file } : null;
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
function claimedKeepWorkingCommand(task) {
|
|
242
|
+
const ref = taskRef(task);
|
|
243
|
+
return ref
|
|
244
|
+
? `atris task ready ${ref} --verify "${CLAIMED_KEEP_WORKING_VERIFY}"`
|
|
245
|
+
: `atris task ready --verify "${CLAIMED_KEEP_WORKING_VERIFY}"`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function isClaimedKeepWorkingCommand(command) {
|
|
249
|
+
return /^atris task ready\b/.test(String(command || ''));
|
|
250
|
+
}
|
|
251
|
+
|
|
240
252
|
function taskCommand(task, person) {
|
|
241
253
|
const ref = taskRef(task);
|
|
242
254
|
const who = person || 'operator';
|
|
@@ -244,7 +256,7 @@ function taskCommand(task, person) {
|
|
|
244
256
|
if (task.status === 'review') {
|
|
245
257
|
return ref ? `atris task accept ${ref}` : 'atris task reviews --limit 5';
|
|
246
258
|
}
|
|
247
|
-
if (task.status === 'claimed') return
|
|
259
|
+
if (task.status === 'claimed') return claimedKeepWorkingCommand(task);
|
|
248
260
|
if (task.status === 'open') {
|
|
249
261
|
return ref ? `atris task claim ${ref} --as ${who}` : 'atris task next';
|
|
250
262
|
}
|
|
@@ -855,7 +867,13 @@ function spokenWinReason(text) {
|
|
|
855
867
|
}
|
|
856
868
|
|
|
857
869
|
function isKeepWorkingMinute(minute) {
|
|
858
|
-
|
|
870
|
+
const next = String((minute && minute.nextCommand) || '');
|
|
871
|
+
if (next === 'atris do') return true;
|
|
872
|
+
return isClaimedKeepWorkingCommand(next);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function isClaimMinute(minute) {
|
|
876
|
+
return Boolean(minute && /^atris task claim\b/.test(minute.nextCommand || ''));
|
|
859
877
|
}
|
|
860
878
|
|
|
861
879
|
function speakKeepWorkingMinute({
|
|
@@ -918,6 +936,7 @@ module.exports = {
|
|
|
918
936
|
spokenLineCount,
|
|
919
937
|
spokenWinReason,
|
|
920
938
|
isKeepWorkingMinute,
|
|
939
|
+
isClaimMinute,
|
|
921
940
|
taskCommand,
|
|
922
941
|
taskNextCommand,
|
|
923
942
|
writeLaterNote,
|
package/lib/pack-capabilities.js
CHANGED
|
@@ -6,6 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const dns = require('dns').promises;
|
|
7
7
|
const net = require('net');
|
|
8
8
|
const { randomUUID } = require('crypto');
|
|
9
|
+
const { enforceConfigGuard } = require('./config-guard');
|
|
9
10
|
|
|
10
11
|
const CAPABILITY_DEFINITIONS = Object.freeze({
|
|
11
12
|
'pack.read': Object.freeze({
|
|
@@ -34,6 +35,7 @@ const TOOL_ORDER = Object.freeze([
|
|
|
34
35
|
]);
|
|
35
36
|
|
|
36
37
|
const FILE_TOOLS = new Set(['Read', 'Glob', 'Grep', 'Edit', 'Write']);
|
|
38
|
+
const PRE_TOOL_USE_MATCHER = 'Read|Glob|Grep|Edit|Write|WebFetch|Bash';
|
|
37
39
|
const CLAUDE_SESSION_END_REASONS = new Set([
|
|
38
40
|
'clear',
|
|
39
41
|
'resume',
|
|
@@ -211,7 +213,7 @@ function buildClaudeCapabilityArgs(policy, options = {}) {
|
|
|
211
213
|
},
|
|
212
214
|
hooks: {
|
|
213
215
|
PreToolUse: [{
|
|
214
|
-
matcher:
|
|
216
|
+
matcher: PRE_TOOL_USE_MATCHER,
|
|
215
217
|
hooks: [{ type: 'command', command: `${hookCommand} pre` }],
|
|
216
218
|
}],
|
|
217
219
|
PostToolUse: [{
|
|
@@ -396,6 +398,7 @@ function beginPackRunReceipt(packDir, manifest, policy, options = {}) {
|
|
|
396
398
|
runner: 'claude',
|
|
397
399
|
builtInToolCeiling: true,
|
|
398
400
|
packRootFileBoundary: true,
|
|
401
|
+
claudeSettingsGuard: true,
|
|
399
402
|
preLaunchContextBoundary: true,
|
|
400
403
|
declaredTreeSymlinksRejected: true,
|
|
401
404
|
packOpeningSlashCommandsEscaped: true,
|
|
@@ -611,12 +614,18 @@ function denyPreToolUse(env, input, reason, now) {
|
|
|
611
614
|
};
|
|
612
615
|
}
|
|
613
616
|
|
|
617
|
+
function decidePreToolUse(input, env) {
|
|
618
|
+
const configDecision = enforceConfigGuard(input, { cwd: env.root });
|
|
619
|
+
if (!configDecision.allowed) return configDecision;
|
|
620
|
+
return enforcePackRoot(input, env.root);
|
|
621
|
+
}
|
|
622
|
+
|
|
614
623
|
function runHook(mode, rawInput) {
|
|
615
624
|
const env = hookEnvironment();
|
|
616
625
|
const input = rawInput ? JSON.parse(rawInput) : {};
|
|
617
626
|
const now = new Date().toISOString();
|
|
618
627
|
if (mode === 'pre') {
|
|
619
|
-
const decision =
|
|
628
|
+
const decision = decidePreToolUse(input, env);
|
|
620
629
|
if (!decision.allowed) {
|
|
621
630
|
return denyPreToolUse(env, input, decision.reason, now);
|
|
622
631
|
}
|
|
@@ -645,7 +654,7 @@ async function runHookAsync(mode, rawInput, options = {}) {
|
|
|
645
654
|
const env = hookEnvironment();
|
|
646
655
|
const input = rawInput ? JSON.parse(rawInput) : {};
|
|
647
656
|
const now = new Date().toISOString();
|
|
648
|
-
const fileDecision =
|
|
657
|
+
const fileDecision = decidePreToolUse(input, env);
|
|
649
658
|
if (!fileDecision.allowed) return denyPreToolUse(env, input, fileDecision.reason, now);
|
|
650
659
|
const webDecision = await enforcePublicWeb(input, options);
|
|
651
660
|
if (!webDecision.allowed) return denyPreToolUse(env, input, webDecision.reason, now);
|
|
@@ -677,6 +686,7 @@ module.exports = {
|
|
|
677
686
|
finalizePackRunReceipt,
|
|
678
687
|
receiptDirectory,
|
|
679
688
|
classifyPackRunLifecycle,
|
|
689
|
+
enforceConfigGuard,
|
|
680
690
|
enforcePackRoot,
|
|
681
691
|
publicWebUrlPreflight,
|
|
682
692
|
enforcePublicWeb,
|
|
@@ -147,7 +147,9 @@ function ensureWorkspaceBrain(projectRoot = process.cwd(), { now = new Date() }
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
const year = String(now.getFullYear());
|
|
150
|
-
|
|
150
|
+
// Local calendar day, matching lib/file-ops getLogPath; toISOString is UTC
|
|
151
|
+
// and creates tomorrow's journal every evening west of Greenwich.
|
|
152
|
+
const today = `${year}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
151
153
|
const journalFile = path.join(atrisDir, 'logs', year, `${today}.md`);
|
|
152
154
|
if (writeIfMissing(journalFile, firstJournalMarkdown(today))) note(journalFile);
|
|
153
155
|
|
package/package.json
CHANGED
package/utils/auth.js
CHANGED
|
@@ -86,6 +86,7 @@ function promptUser(question) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
const TOKEN_REFRESH_BUFFER_SECONDS = 300;
|
|
89
|
+
const AGENT_TOKEN_EXPIRED_DETAIL = 'Agent token expired. Mint a new one: atris login --agent';
|
|
89
90
|
|
|
90
91
|
/**
|
|
91
92
|
* Decode and parse the claims from a JWT token.
|
|
@@ -164,6 +165,48 @@ function getCredentialsPath() {
|
|
|
164
165
|
return path.join(getAtrisDir(), 'credentials.json');
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
function getPlacedAgentTokenPath() {
|
|
169
|
+
const override = process.env.ATRIS_AGENT_TOKEN_FILE;
|
|
170
|
+
if (override && override.trim()) return override.trim();
|
|
171
|
+
return path.join(os.homedir(), '.atris', 'agent-token.json');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function expiryTimeMs(value) {
|
|
175
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
176
|
+
return value > 1e12 ? value : value * 1000;
|
|
177
|
+
}
|
|
178
|
+
if (typeof value !== 'string' || !value.trim()) return null;
|
|
179
|
+
const trimmed = value.trim();
|
|
180
|
+
if (/^\d+(?:\.\d+)?$/.test(trimmed)) {
|
|
181
|
+
const numeric = Number(trimmed);
|
|
182
|
+
if (Number.isFinite(numeric)) return numeric > 1e12 ? numeric : numeric * 1000;
|
|
183
|
+
}
|
|
184
|
+
const parsed = Date.parse(trimmed);
|
|
185
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function loadPlacedAgentToken() {
|
|
189
|
+
try {
|
|
190
|
+
const parsed = JSON.parse(fs.readFileSync(getPlacedAgentTokenPath(), 'utf8'));
|
|
191
|
+
const token = typeof parsed?.token === 'string' ? parsed.token.trim() : '';
|
|
192
|
+
if (!token || expiryTimeMs(parsed?.expires_at) === null) return null;
|
|
193
|
+
return {
|
|
194
|
+
token,
|
|
195
|
+
expires_at: parsed.expires_at,
|
|
196
|
+
scopes: Array.isArray(parsed.scopes) ? parsed.scopes : [],
|
|
197
|
+
provider: null,
|
|
198
|
+
source: 'agent_token_file',
|
|
199
|
+
};
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function isUnexpiredCredential(credentials) {
|
|
206
|
+
const expiresAt = expiryTimeMs(credentials?.expires_at);
|
|
207
|
+
return expiresAt !== null && expiresAt > Date.now();
|
|
208
|
+
}
|
|
209
|
+
|
|
167
210
|
function getSessionsDir() {
|
|
168
211
|
return mkPrivateDir(path.join(getAtrisDir(), 'sessions'));
|
|
169
212
|
}
|
|
@@ -332,17 +375,36 @@ function saveCredentials(token, refreshToken, email, userId, provider) {
|
|
|
332
375
|
}
|
|
333
376
|
|
|
334
377
|
function loadCredentials() {
|
|
335
|
-
// Priority: ATRIS_TOKEN env var →
|
|
378
|
+
// Priority: ATRIS_TOKEN env var → placed agent token → ATRIS_PROFILE env var
|
|
379
|
+
// → per-terminal session file → global credentials.json. A fresh placed token
|
|
380
|
+
// overrides a different env token because cloud images can carry a stale one.
|
|
336
381
|
|
|
337
382
|
// 0. Raw token injection. Headless boxes (cloud business computers) have no
|
|
338
383
|
// browser for `atris login`; the runner injects a scoped token as env
|
|
339
384
|
// instead, so no credentials file ever lands on disk.
|
|
340
385
|
const envToken = process.env.ATRIS_TOKEN;
|
|
341
|
-
|
|
342
|
-
|
|
386
|
+
const normalizedEnvToken = envToken && envToken.trim();
|
|
387
|
+
const placedAgentToken = loadPlacedAgentToken();
|
|
388
|
+
if (
|
|
389
|
+
normalizedEnvToken &&
|
|
390
|
+
placedAgentToken &&
|
|
391
|
+
placedAgentToken.token !== normalizedEnvToken &&
|
|
392
|
+
isUnexpiredCredential(placedAgentToken)
|
|
393
|
+
) {
|
|
394
|
+
return placedAgentToken;
|
|
395
|
+
}
|
|
396
|
+
if (normalizedEnvToken) {
|
|
397
|
+
return { token: normalizedEnvToken, provider: null, source: 'env' };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// 1. Backend-placed key for per-user cloud computers. Missing, malformed,
|
|
401
|
+
// incomplete, and expired files are ignored so later sources keep their
|
|
402
|
+
// existing order.
|
|
403
|
+
if (placedAgentToken && isUnexpiredCredential(placedAgentToken)) {
|
|
404
|
+
return placedAgentToken;
|
|
343
405
|
}
|
|
344
406
|
|
|
345
|
-
//
|
|
407
|
+
// 2. Explicit env var override
|
|
346
408
|
const profileOverride = process.env.ATRIS_PROFILE;
|
|
347
409
|
if (profileOverride) {
|
|
348
410
|
const profile = loadProfile(profileOverride);
|
|
@@ -358,14 +420,14 @@ function loadCredentials() {
|
|
|
358
420
|
}
|
|
359
421
|
}
|
|
360
422
|
|
|
361
|
-
//
|
|
423
|
+
// 3. Per-terminal session override (set by atris switch)
|
|
362
424
|
const sessionProfile = getSessionProfile();
|
|
363
425
|
if (sessionProfile) {
|
|
364
426
|
const profile = loadProfile(sessionProfile);
|
|
365
427
|
if (profile) return { ...profile, source_profile: sessionProfile };
|
|
366
428
|
}
|
|
367
429
|
|
|
368
|
-
//
|
|
430
|
+
// 4. Global credentials.json
|
|
369
431
|
const credentialsPath = getCredentialsPath();
|
|
370
432
|
|
|
371
433
|
if (!fs.existsSync(credentialsPath)) {
|
|
@@ -546,10 +608,15 @@ async function ensureValidCredentials(apiRequestJson, options = {}) {
|
|
|
546
608
|
return { error: 'not_logged_in' };
|
|
547
609
|
}
|
|
548
610
|
|
|
611
|
+
const selectedExpiry = expiryTimeMs(credentials.expires_at);
|
|
612
|
+
if (selectedExpiry !== null && selectedExpiry <= Date.now()) {
|
|
613
|
+
return { error: 'token_invalid', detail: AGENT_TOKEN_EXPIRED_DETAIL };
|
|
614
|
+
}
|
|
615
|
+
|
|
549
616
|
const claims = decodeJwtClaims(credentials.token);
|
|
550
617
|
if (claims?.type === 'agent_access') {
|
|
551
618
|
if (typeof claims.exp === 'number' && claims.exp <= Math.floor(Date.now() / 1000)) {
|
|
552
|
-
return { error: 'token_invalid', detail:
|
|
619
|
+
return { error: 'token_invalid', detail: AGENT_TOKEN_EXPIRED_DETAIL };
|
|
553
620
|
}
|
|
554
621
|
// Server-side scope enforcement on every real request is unchanged and remains the actual security boundary.
|
|
555
622
|
return { credentials, user: null, source: 'agent_token' };
|
|
@@ -572,6 +639,7 @@ async function ensureValidCredentials(apiRequestJson, options = {}) {
|
|
|
572
639
|
|
|
573
640
|
if (
|
|
574
641
|
credentials.source !== 'env' &&
|
|
642
|
+
credentials.source !== 'agent_token_file' &&
|
|
575
643
|
(updatedEmail !== credentials.email ||
|
|
576
644
|
updatedProvider !== credentials.provider ||
|
|
577
645
|
updatedUserId !== credentials.user_id)
|
|
@@ -680,6 +748,7 @@ async function displayAccountSummary(apiRequestJson) {
|
|
|
680
748
|
}
|
|
681
749
|
|
|
682
750
|
module.exports = {
|
|
751
|
+
AGENT_TOKEN_EXPIRED_DETAIL,
|
|
683
752
|
decodeJwtClaims,
|
|
684
753
|
getTokenExpiryEpochSeconds,
|
|
685
754
|
shouldRefreshToken,
|