atris 3.57.3 → 3.58.5
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 +57 -13
- package/atris/skills/x-search/SKILL.md +13 -5
- package/atris/skills/youtube/SKILL.md +39 -8
- 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 +157 -24
- package/bin/atris.js +79 -31
- 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 +7 -7
- 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 +297 -15
- 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 +12 -12
- 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 +97 -98
- 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 +97 -40
- package/commands/worktree.js +57 -1
- package/commands/write.js +9 -9
- package/commands/x-search.js +304 -15
- package/commands/xp.js +6 -3
- package/commands/youtube.js +916 -65
- package/lib/accept-verify-gate.js +3 -3
- package/lib/activity-stream.js +1 -1
- package/lib/apply-gate.js +13 -0
- 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 +66 -9
- 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/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 +0 -3
- package/utils/config.js +0 -1
- package/scripts/det/checklist-score.js +0 -191
package/lib/engine-ask.js
CHANGED
|
@@ -24,13 +24,13 @@ const DEFAULT_ASK_CONCURRENCY = 3;
|
|
|
24
24
|
const DEFAULT_ASK_TIMEOUT_MS = 120000;
|
|
25
25
|
const MAX_ASK_CONCURRENCY = 4;
|
|
26
26
|
const MAX_ASK_JOBS = 8;
|
|
27
|
-
const
|
|
28
|
-
const
|
|
27
|
+
const DEFAULT_FABLE_ASK_TIMEOUT_MS = 10 * 60 * 1000;
|
|
28
|
+
const MAX_ASK_TIMEOUT_MS = 60 * 60 * 1000;
|
|
29
29
|
const MAX_ASK_PROMPT_BYTES = 16 * 1024;
|
|
30
30
|
const MAX_ASK_TOTAL_PROMPT_BYTES = 64 * 1024;
|
|
31
31
|
const MAX_ASK_OUTPUT_BYTES = 1024 * 1024;
|
|
32
32
|
const ASK_STOP_GRACE_MS = 250;
|
|
33
|
-
const ASK_MODEL_ENGINES = new Set(['claude', 'fable', 'haiku', 'codex', 'cursor', 'devin', 'grok', 'agy']);
|
|
33
|
+
const ASK_MODEL_ENGINES = new Set(['claude', 'fable', 'haiku', 'codex', 'cursor', 'devin', 'grok', 'agy', 'opencode', 'commandcode']);
|
|
34
34
|
const READ_ONLY_PREAMBLE = [
|
|
35
35
|
'This is a read-only request.',
|
|
36
36
|
'Do not modify files, create worktrees, start background agents, or run commands with side effects.',
|
|
@@ -49,6 +49,9 @@ function askUsage() {
|
|
|
49
49
|
` --timeout <sec> per-engine timeout, 1-${MAX_ASK_TIMEOUT_MS / 1000} (default ${DEFAULT_ASK_TIMEOUT_MS / 1000}; Fable ${DEFAULT_FABLE_ASK_TIMEOUT_MS / 1000})`,
|
|
50
50
|
' --json print the receipt as json',
|
|
51
51
|
'',
|
|
52
|
+
'long jobs: set --timeout explicitly; agy receives the same print deadline.',
|
|
53
|
+
'watch a running receipt from another shell: atris engine watch latest',
|
|
54
|
+
'',
|
|
52
55
|
`jobs files contain up to ${MAX_ASK_JOBS} objects: [{"engine":"codex","model":"optional","prompt":"question","label":"optional"}]`,
|
|
53
56
|
].join('\n');
|
|
54
57
|
}
|
|
@@ -202,7 +205,9 @@ function resolveAskModel(engine, modelName = '') {
|
|
|
202
205
|
return RUNNER_PROFILE_DEFS[engine]?.model || DEFAULT_CLAUDE_RUNNER_MODEL;
|
|
203
206
|
}
|
|
204
207
|
|
|
205
|
-
function buildReadOnlyEngineInvocation(engineName, prompt, modelName = ''
|
|
208
|
+
function buildReadOnlyEngineInvocation(engineName, prompt, modelName = '', {
|
|
209
|
+
timeoutMs = DEFAULT_ASK_TIMEOUT_MS,
|
|
210
|
+
} = {}) {
|
|
206
211
|
const engine = canonicalEngineName(engineName);
|
|
207
212
|
const profile = RUNNER_PROFILE_DEFS[engine];
|
|
208
213
|
if (!profile) throw new Error(`unknown engine "${engineName}"`);
|
|
@@ -222,7 +227,6 @@ function buildReadOnlyEngineInvocation(engineName, prompt, modelName = '') {
|
|
|
222
227
|
'--model', model || profile.model || DEFAULT_CLAUDE_RUNNER_MODEL,
|
|
223
228
|
'--tools', 'Read,Glob,Grep,WebSearch,WebFetch',
|
|
224
229
|
'--permission-mode', 'plan',
|
|
225
|
-
'--safe-mode',
|
|
226
230
|
'--no-session-persistence',
|
|
227
231
|
],
|
|
228
232
|
};
|
|
@@ -259,9 +263,28 @@ function buildReadOnlyEngineInvocation(engineName, prompt, modelName = '') {
|
|
|
259
263
|
return {
|
|
260
264
|
engine,
|
|
261
265
|
bin: profile.bin,
|
|
262
|
-
args: [
|
|
266
|
+
args: [
|
|
267
|
+
'--mode', 'plan',
|
|
268
|
+
'--sandbox',
|
|
269
|
+
'--dangerously-skip-permissions',
|
|
270
|
+
'--print-timeout', `${Math.max(1, Math.ceil(timeoutMs / 1000))}s`,
|
|
271
|
+
...(model ? ['--model', model] : []),
|
|
272
|
+
'-p', request,
|
|
273
|
+
],
|
|
263
274
|
};
|
|
264
275
|
}
|
|
276
|
+
if (engine === 'commandcode') {
|
|
277
|
+
// Headless print mode blocks file edits and shell commands by default,
|
|
278
|
+
// so plain -p is already read-only; no sandbox flags needed. Default is
|
|
279
|
+
// no --model: the run rides this machine's configured Command Code
|
|
280
|
+
// default, with an explicit ask --model still honored.
|
|
281
|
+
return { engine, bin: profile.bin, args: ['-p', request, ...(model ? ['--model', model] : [])] };
|
|
282
|
+
}
|
|
283
|
+
if (engine === 'opencode') {
|
|
284
|
+
// `run` with a message is headless print mode; the built-in plan agent
|
|
285
|
+
// keeps the ask read-only (verified live 2026-08-21, ~7s per lookup).
|
|
286
|
+
return { engine, bin: profile.bin, args: ['--agent', 'plan', ...(model ? ['-m', model] : []), request] };
|
|
287
|
+
}
|
|
265
288
|
throw new Error(`engine ask has no read-only command for ${engine}`);
|
|
266
289
|
}
|
|
267
290
|
|
|
@@ -293,6 +316,33 @@ function cancelledAskResult(job, extra = {}) {
|
|
|
293
316
|
};
|
|
294
317
|
}
|
|
295
318
|
|
|
319
|
+
function normalizeAskResult(engine, result = {}) {
|
|
320
|
+
const output = `${String(result.stdout || '')}\n${String(result.stderr || '')}`;
|
|
321
|
+
if (engine === 'agy' && !result.cancelled && /timeout waiting for response/i.test(output)) {
|
|
322
|
+
return { ...result, ok: false, reason: 'timeout', timed_out: true };
|
|
323
|
+
}
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function osAccountName() {
|
|
328
|
+
try {
|
|
329
|
+
return String(os.userInfo().username || '').trim();
|
|
330
|
+
} catch {
|
|
331
|
+
return '';
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Claude Code looks up macOS Keychain with process.env.USER, not whoami.
|
|
336
|
+
// Host apps sometimes pass a display name, so pin USER and LOGNAME to the OS account.
|
|
337
|
+
function buildAskSpawnEnv(sourceEnv = process.env) {
|
|
338
|
+
const env = { ...sourceEnv };
|
|
339
|
+
const username = osAccountName();
|
|
340
|
+
if (!username) return env;
|
|
341
|
+
env.USER = username;
|
|
342
|
+
env.LOGNAME = username;
|
|
343
|
+
return env;
|
|
344
|
+
}
|
|
345
|
+
|
|
296
346
|
function runAskProcess(invocation, {
|
|
297
347
|
cwd = process.cwd(),
|
|
298
348
|
timeoutMs = DEFAULT_ASK_TIMEOUT_MS,
|
|
@@ -387,7 +437,7 @@ function runAskProcess(invocation, {
|
|
|
387
437
|
try {
|
|
388
438
|
child = spawnProcess(invocation.bin, invocation.args, {
|
|
389
439
|
cwd,
|
|
390
|
-
env:
|
|
440
|
+
env: buildAskSpawnEnv(),
|
|
391
441
|
detached: process.platform !== 'win32',
|
|
392
442
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
393
443
|
});
|
|
@@ -430,7 +480,7 @@ async function runEngineAskJobs(jobs, {
|
|
|
430
480
|
const answers = new Array(jobs.length);
|
|
431
481
|
let nextIndex = 0;
|
|
432
482
|
const execute = executeAskJob || (async (job) => {
|
|
433
|
-
const invocation = buildReadOnlyEngineInvocation(job.engine, job.prompt, job.model);
|
|
483
|
+
const invocation = buildReadOnlyEngineInvocation(job.engine, job.prompt, job.model, { timeoutMs });
|
|
434
484
|
return runAskProcess(invocation, { cwd: invocation.cwd || root, timeoutMs, signal, onOutputChunk });
|
|
435
485
|
});
|
|
436
486
|
const workerCount = Math.min(concurrency, jobs.length);
|
|
@@ -447,7 +497,8 @@ async function runEngineAskJobs(jobs, {
|
|
|
447
497
|
}
|
|
448
498
|
try {
|
|
449
499
|
assertAskModelSupported(job.engine, job.model);
|
|
450
|
-
|
|
500
|
+
const result = await execute(job, { root, timeoutMs, index, signal, onOutputChunk });
|
|
501
|
+
answers[index] = { ...job, ...normalizeAskResult(job.engine, result) };
|
|
451
502
|
} catch (error) {
|
|
452
503
|
answers[index] = {
|
|
453
504
|
...job,
|
|
@@ -568,12 +619,17 @@ async function runEngineAskCommand(args, root = process.cwd(), deps = {}) {
|
|
|
568
619
|
started_at: startedAt,
|
|
569
620
|
at: startedAt,
|
|
570
621
|
read_only: true,
|
|
622
|
+
timeout_ms: parsed.timeoutMs,
|
|
571
623
|
};
|
|
572
624
|
const receiptPath = (deps.createReceipt || createEngineAskReceipt)(root, runningReceipt);
|
|
573
625
|
const relativeReceiptPath = path.relative(root, receiptPath) || receiptPath;
|
|
574
626
|
const liveLogPath = (deps.createLiveLog || createEngineLiveLog)(receiptPath);
|
|
575
627
|
runningReceipt.live_log = path.relative(root, liveLogPath) || liveLogPath;
|
|
576
628
|
(deps.updateReceipt || atomicWriteEngineAskReceipt)(receiptPath, runningReceipt);
|
|
629
|
+
if (!parsed.json) {
|
|
630
|
+
console.log(`engine ask started: ${relativeReceiptPath}`);
|
|
631
|
+
console.log(`watch: atris engine watch ${path.basename(receiptPath, '.json')}`);
|
|
632
|
+
}
|
|
577
633
|
const appendLiveLog = deps.appendLiveLog
|
|
578
634
|
|| ((filePath, chunk) => appendEngineLiveLogChunk(filePath, chunk));
|
|
579
635
|
let interruptedSignal = '';
|
|
@@ -671,6 +727,7 @@ module.exports = {
|
|
|
671
727
|
MAX_ASK_PROMPT_BYTES,
|
|
672
728
|
parseEngineAskArgs,
|
|
673
729
|
buildReadOnlyEngineInvocation,
|
|
730
|
+
buildAskSpawnEnv,
|
|
674
731
|
runAskProcess,
|
|
675
732
|
runEngineAskJobs,
|
|
676
733
|
runEngineAskCommand,
|
package/lib/engine-registry.js
CHANGED
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
RUNNER_PROFILE_DEFS,
|
|
8
8
|
RUNNER_PROFILE_ALIASES,
|
|
9
9
|
RUNNER_PROFILE_NAMES,
|
|
10
|
+
HIDDEN_PROFILE_NAMES,
|
|
10
11
|
} = require('./runner-command');
|
|
11
12
|
const { rankEnginesDetailed, routerPickExplanation } = require('./router-brain');
|
|
12
13
|
|
|
@@ -53,6 +54,7 @@ const ENGINE_SEED_META = Object.freeze({
|
|
|
53
54
|
tier: 'pro',
|
|
54
55
|
roles: Object.freeze(['executor']),
|
|
55
56
|
models: Object.freeze([
|
|
57
|
+
'gemini-3.8-flash-high',
|
|
56
58
|
'gemini-3.7-flash-high',
|
|
57
59
|
'gemini-3.1-pro-high',
|
|
58
60
|
'claude-sonnet-4-6',
|
|
@@ -62,6 +64,15 @@ const ENGINE_SEED_META = Object.freeze({
|
|
|
62
64
|
duty: 'errands',
|
|
63
65
|
fallback_order: 90,
|
|
64
66
|
}),
|
|
67
|
+
opencode: Object.freeze({ tier: 'pro', roles: Object.freeze(['executor']), models: Object.freeze(['opencode/big-pickle', 'opencode/claude-opus-4-8', 'opencode/gpt-5.2']), fallback_order: 95 }),
|
|
68
|
+
commandcode: Object.freeze({
|
|
69
|
+
tier: 'max',
|
|
70
|
+
roles: Object.freeze(['validator', 'executor']),
|
|
71
|
+
// No pinned model: the engine rides this machine's Command Code default.
|
|
72
|
+
models: Object.freeze(['machine default']),
|
|
73
|
+
duty: 'errands',
|
|
74
|
+
fallback_order: 98,
|
|
75
|
+
}),
|
|
65
76
|
});
|
|
66
77
|
|
|
67
78
|
function engineRegistryFile(root = process.cwd()) {
|
|
@@ -176,7 +187,9 @@ function seededRegistry(root = process.cwd(), preloadedRaw = null) {
|
|
|
176
187
|
return {
|
|
177
188
|
schema: ENGINE_REGISTRY_SCHEMA,
|
|
178
189
|
updated_at: new Date().toISOString(),
|
|
179
|
-
engines:
|
|
190
|
+
// Hidden profiles are registered engines too: they seed, route, and take
|
|
191
|
+
// health policy exactly like visible ones.
|
|
192
|
+
engines: Object.keys(RUNNER_PROFILE_DEFS).map((id) => normalizeEngineEntry(id, savedById.get(id) || {})),
|
|
180
193
|
};
|
|
181
194
|
}
|
|
182
195
|
|
|
@@ -192,7 +205,7 @@ function writeEngineRegistry(root, registry) {
|
|
|
192
205
|
|
|
193
206
|
function setEngineOverrides(name, overrides = {}, root = process.cwd()) {
|
|
194
207
|
const id = canonicalEngineName(name);
|
|
195
|
-
const knownIds =
|
|
208
|
+
const knownIds = Object.keys(RUNNER_PROFILE_DEFS).filter((engineId) => ENGINE_SEED_META[engineId]);
|
|
196
209
|
if (!id || !knownIds.includes(id)) {
|
|
197
210
|
throw new Error(`Unknown engine "${name}". Known engines: ${knownIds.join(', ')}`);
|
|
198
211
|
}
|
package/lib/file-ops.js
CHANGED
|
@@ -83,7 +83,7 @@ function createLogFile(logFile, dateFormatted) {
|
|
|
83
83
|
const backlogBody = carryBacklog ? `${carryBacklog}\n\n` : '';
|
|
84
84
|
const inboxBody = carryInbox ? `${carryInbox}\n\n` : '';
|
|
85
85
|
|
|
86
|
-
const initialContent = `# Log
|
|
86
|
+
const initialContent = `# Log, ${dateFormatted}\n\n## Handoff\n\n---\n\n## Completed ✅\n\n---\n\n## In Progress 🔄\n\n${inProgressBody}---\n\n## Backlog\n\n${backlogBody}---\n\n## Notes\n\n---\n\n## Inbox\n\n${inboxBody}\n`;
|
|
87
87
|
fs.writeFileSync(logFile, initialContent);
|
|
88
88
|
}
|
|
89
89
|
|
package/lib/fleet.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
// --fleet`), the full loop inside: staff every idle engine on claimable
|
|
5
5
|
// safe-lane tasks, one mission per task, one worktree per engine, land
|
|
6
6
|
// arrivals serially. Proven by hand on 2026-07-02 (PRs #191-193, three
|
|
7
|
-
// engines) before any of this code existed
|
|
7
|
+
// engines) before any of this code existed, each function here is one thing
|
|
8
8
|
// the orchestrator did manually that day.
|
|
9
9
|
//
|
|
10
10
|
// INVARIANT: the fleet has NO state file. Flight state IS missions +
|
|
11
|
-
// worktrees + receipts. Kill a flight mid-run and nothing is orphaned
|
|
11
|
+
// worktrees + receipts. Kill a flight mid-run and nothing is orphaned, it
|
|
12
12
|
// is just missions in flight, resumable the normal way.
|
|
13
13
|
|
|
14
14
|
const fs = require('fs');
|
|
@@ -48,7 +48,7 @@ const { appendEngineLiveLogChunk, createEngineLiveLog, engineTerminalReason } =
|
|
|
48
48
|
const DENIED_TAGS = ['billing', 'money', 'payments', 'deploy', 'security', 'customer', 'external', 'feedback'];
|
|
49
49
|
|
|
50
50
|
// ---------------------------------------------------------------------------
|
|
51
|
-
// T1
|
|
51
|
+
// T1: dispatch primitive
|
|
52
52
|
|
|
53
53
|
// A task's own text is its spec: the board convention writes "Done: ..." and
|
|
54
54
|
// "Check: ..." into the title/description. Extract both so the dispatch
|
|
@@ -68,10 +68,10 @@ function parseDoneCheck(text) {
|
|
|
68
68
|
// hypothesis-driven unsticking, smallest diff.
|
|
69
69
|
const METHOD_KERNEL = [
|
|
70
70
|
'Read this whole brief before acting; locate every file you will touch before editing any of them.',
|
|
71
|
-
'Never pipe a verify or test command through tail/head/grep
|
|
71
|
+
'Never pipe a verify or test command through tail/head/grep, run it bare and read the real exit code.',
|
|
72
72
|
'Done requires a receipt: paste the exact verify command and its final output lines in your report.',
|
|
73
73
|
'Before deleting or renaming any function or call site, grep the whole repo for its callers; a caller outside your change means the contract stays intact.',
|
|
74
|
-
'Stuck? Never run the same failing command a third time
|
|
74
|
+
'Stuck? Never run the same failing command a third time, write 3 one-line hypotheses, then run the cheapest test that discriminates between them.',
|
|
75
75
|
'Smallest diff that satisfies Done wins; prefer deleting code over adding it.',
|
|
76
76
|
];
|
|
77
77
|
|
|
@@ -99,7 +99,7 @@ function buildFleetPrompt(task, { worktreePath, yolo = false } = {}) {
|
|
|
99
99
|
'',
|
|
100
100
|
ATRIS_BUILD_PROCESS_PREAMBLE,
|
|
101
101
|
'',
|
|
102
|
-
`You are working task ${ref} in this repo checkout (an isolated git worktree${worktreePath ? ` at ${worktreePath}` : ''}
|
|
102
|
+
`You are working task ${ref} in this repo checkout (an isolated git worktree${worktreePath ? ` at ${worktreePath}` : ''}, commit here, NEVER push).`,
|
|
103
103
|
'',
|
|
104
104
|
`Task: ${title}`,
|
|
105
105
|
'',
|
|
@@ -145,10 +145,10 @@ function buildFleetPrompt(task, { worktreePath, yolo = false } = {}) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
// Shell command that runs one engine on one prompt file, from the worktree.
|
|
148
|
-
// Rides the same profile definitions the whole CLI uses
|
|
148
|
+
// Rides the same profile definitions the whole CLI uses, no fleet-only
|
|
149
149
|
// spawn shapes.
|
|
150
150
|
// Build work needs file tools: allowedTools reaches the default claude-shaped
|
|
151
|
-
// spawn; template engines (cursor/codex/devin) ignore it
|
|
151
|
+
// spawn; template engines (cursor/codex/devin) ignore it, their CLIs manage
|
|
152
152
|
// their own permissions.
|
|
153
153
|
const FLEET_ALLOWED_TOOLS = 'Bash,Read,Edit,Write,Grep,Glob';
|
|
154
154
|
const VALIDATOR_ALLOWED_TOOLS = 'Bash,Read,Grep,Glob';
|
|
@@ -864,7 +864,7 @@ function dispatchToEngine({ task, engine, worktreePath, root = process.cwd(), ti
|
|
|
864
864
|
}
|
|
865
865
|
|
|
866
866
|
// ---------------------------------------------------------------------------
|
|
867
|
-
// T2
|
|
867
|
+
// T2: staffing
|
|
868
868
|
|
|
869
869
|
const {
|
|
870
870
|
taskTagTokens,
|
|
@@ -894,7 +894,7 @@ function isSafeLane(task) {
|
|
|
894
894
|
}
|
|
895
895
|
|
|
896
896
|
// Rough file-surface guess from a task's text: paths and bare `commands/x.js`
|
|
897
|
-
// style mentions. Used only to keep concurrent picks disjoint
|
|
897
|
+
// style mentions. Used only to keep concurrent picks disjoint, the merge
|
|
898
898
|
// conflict on 2026-07-02 (two engines in commands/mission.js) is the tax this
|
|
899
899
|
// avoids.
|
|
900
900
|
function fileSurface(task) {
|
|
@@ -938,7 +938,7 @@ function assignEngines(staffed, engines) {
|
|
|
938
938
|
}
|
|
939
939
|
|
|
940
940
|
// ---------------------------------------------------------------------------
|
|
941
|
-
// T3
|
|
941
|
+
// T3: serial landing lane
|
|
942
942
|
|
|
943
943
|
// Land one arrival: rebase onto latest base first; a conflict pauses the
|
|
944
944
|
// landing (never auto-resolve) and reports which files collided so the
|
|
@@ -971,7 +971,7 @@ function dispatchResultToTerminal(entry = {}, result = {}) {
|
|
|
971
971
|
}
|
|
972
972
|
|
|
973
973
|
// Fleet landings always target master. Without the explicit --target, ship
|
|
974
|
-
// falls back to the launcher branch's atris-base
|
|
974
|
+
// falls back to the launcher branch's atris-base, a flight launched from a
|
|
975
975
|
// feature-branch checkout would merge PRs into that branch while the receipt
|
|
976
976
|
// and land board define "landed" as in-master (PRs #207/#208, 2026-07-04).
|
|
977
977
|
// A ship failure must name its real cause. MODULE_NOT_FOUND from the spawned
|
|
@@ -1010,8 +1010,8 @@ function fleetShipArgs(entry, check) {
|
|
|
1010
1010
|
// A fleet build worktree is ephemeral. If any process inside one runs
|
|
1011
1011
|
// `npm link` (which repoints the global `atris` package symlink at that
|
|
1012
1012
|
// worktree), then removing the worktree at teardown leaves the global `atris`
|
|
1013
|
-
// binary a dangling symlink, and everything resolving `atris` through PATH
|
|
1014
|
-
// interactive shells, any tooling that shells out to the bare command
|
|
1013
|
+
// binary a dangling symlink, and everything resolving `atris` through PATH:
|
|
1014
|
+
// interactive shells, any tooling that shells out to the bare command, dies
|
|
1015
1015
|
// with 'command not found'. Measured scope, not assumed: crons that invoke
|
|
1016
1016
|
// `node <checkout>/bin/atris.js` by absolute path are UNAFFECTED; an autoland
|
|
1017
1017
|
// tick ran fine four minutes before the repair. The failure is silent and
|
|
@@ -1083,7 +1083,7 @@ function guardGlobalCliLink({
|
|
|
1083
1083
|
};
|
|
1084
1084
|
}
|
|
1085
1085
|
|
|
1086
|
-
// One-line " (path, path)" suffix naming the files behind a paused landing
|
|
1086
|
+
// One-line " (path, path)" suffix naming the files behind a paused landing,
|
|
1087
1087
|
// unmerged files for a rebase_conflict, uncommitted paths for a dirty_worktree.
|
|
1088
1088
|
function pausedPathsSuffix(landed) {
|
|
1089
1089
|
const paths = (landed && landed.conflicts) || (landed && landed.dirty) || [];
|
|
@@ -1166,10 +1166,10 @@ module.exports = {
|
|
|
1166
1166
|
};
|
|
1167
1167
|
|
|
1168
1168
|
// ---------------------------------------------------------------------------
|
|
1169
|
-
// T4
|
|
1169
|
+
// T4, the conductor: one flight, watchable, receipted
|
|
1170
1170
|
|
|
1171
1171
|
// Engines that can edit a repo headlessly. atris-fast (ax) is a chat lane,
|
|
1172
|
-
// not a repo worker
|
|
1172
|
+
// not a repo worker, it keeps owning normal mission ticks, not fleet builds.
|
|
1173
1173
|
const FLEET_CAPABLE = ['claude', 'codex', 'cursor', 'devin', 'grok'];
|
|
1174
1174
|
const DISPATCH_CAPABLE = [...FLEET_CAPABLE, 'fable', 'commandcode'];
|
|
1175
1175
|
|
|
@@ -2326,6 +2326,33 @@ function defaultOwnCli(root) {
|
|
|
2326
2326
|
};
|
|
2327
2327
|
}
|
|
2328
2328
|
|
|
2329
|
+
// A merge and its task handoff are separate writes. Keep the merge on disk
|
|
2330
|
+
// before attempting the handoff, and never report success if that write fails.
|
|
2331
|
+
function recordLandedTaskReview(cli, args, { flight, landedRow, worktree, save }) {
|
|
2332
|
+
landedRow.review_recorded = false;
|
|
2333
|
+
save();
|
|
2334
|
+
let result;
|
|
2335
|
+
try { result = cli(args); } catch (error) {
|
|
2336
|
+
result = { status: null, stderr: error.message };
|
|
2337
|
+
}
|
|
2338
|
+
if (!result || result.status !== 0) {
|
|
2339
|
+
flight.paused.push({
|
|
2340
|
+
task: landedRow.task,
|
|
2341
|
+
engine: landedRow.engine,
|
|
2342
|
+
stage: 'task_ready',
|
|
2343
|
+
landed: true,
|
|
2344
|
+
worktree,
|
|
2345
|
+
detail: headTail(String(result && (result.stderr || result.stdout) || 'task ready failed')),
|
|
2346
|
+
next_action: 'record fresh task proof with task ready; do not rebuild or merge again',
|
|
2347
|
+
});
|
|
2348
|
+
save();
|
|
2349
|
+
return false;
|
|
2350
|
+
}
|
|
2351
|
+
landedRow.review_recorded = true;
|
|
2352
|
+
save();
|
|
2353
|
+
return true;
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2329
2356
|
// Extract a runnable focused check from the task's Check: line; the wide
|
|
2330
2357
|
// gates (npm test, CI) stay at the ship gate, not per-arrival.
|
|
2331
2358
|
function focusedCheck(task) {
|
|
@@ -2539,7 +2566,7 @@ async function runFleetFlight({
|
|
|
2539
2566
|
|
|
2540
2567
|
const staffed = assignEngines(staffedTasks, roster);
|
|
2541
2568
|
// The receipt path is decided BEFORE landings so each task's ready-proof
|
|
2542
|
-
// can cite it
|
|
2569
|
+
// can cite it, the proof policy certifies receipt-backed proofs agent-side.
|
|
2543
2570
|
const receiptPath = path.join(root, 'atris', 'runs', `fleet-${nowStamp()}.json`);
|
|
2544
2571
|
const flight = {
|
|
2545
2572
|
at: new Date().toISOString(),
|
|
@@ -2557,7 +2584,7 @@ async function runFleetFlight({
|
|
|
2557
2584
|
}
|
|
2558
2585
|
|
|
2559
2586
|
log('');
|
|
2560
|
-
log(` fleet
|
|
2587
|
+
log(` fleet, ${roster.length} engine${roster.length === 1 ? '' : 's'} ready, ${staffed.length} task${staffed.length === 1 ? '' : 's'} staffed`);
|
|
2561
2588
|
for (const s of staffed) log(` ${s.engine.padEnd(8)} → ${s.task.display_id} ${String(s.task.title || '').slice(0, 80)}`);
|
|
2562
2589
|
log('');
|
|
2563
2590
|
|
|
@@ -2596,7 +2623,7 @@ async function runFleetFlight({
|
|
|
2596
2623
|
if (!claimed || claimed.status !== 0) {
|
|
2597
2624
|
const detail = String(claimed && (claimed.stderr || claimed.stdout) || 'claim failed').trim().slice(0, 300);
|
|
2598
2625
|
flight.paused.push({ task: entry.task.display_id, engine: entry.engine, stage: 'claim', detail });
|
|
2599
|
-
log(` ${entry.engine.padEnd(8)} ⏸ paused ${entry.task.display_id} at claim${detail ?
|
|
2626
|
+
log(` ${entry.engine.padEnd(8)} ⏸ paused ${entry.task.display_id} at claim${detail ? `, ${detail}` : ''}`);
|
|
2600
2627
|
continue;
|
|
2601
2628
|
}
|
|
2602
2629
|
const started = cli(['worktree', 'start', '--agent', entry.engine, '--task', `fleet-${String(entry.task.display_id).toLowerCase()}`, ...startBaseArgs]);
|
|
@@ -2658,7 +2685,7 @@ async function runFleetFlight({
|
|
|
2658
2685
|
if (result.deadEngine) paused.deadEngine = result.deadEngine;
|
|
2659
2686
|
flight.paused.push(paused);
|
|
2660
2687
|
stampDispatchBrief(root, result.brief_id, 'fail', `build failed for ${entry.task.display_id}`);
|
|
2661
|
-
log(` ${activeEngine.padEnd(8)} ✗ build failed ${entry.task.display_id}
|
|
2688
|
+
log(` ${activeEngine.padEnd(8)} ✗ build failed ${entry.task.display_id}, worktree kept for takeover`);
|
|
2662
2689
|
continue;
|
|
2663
2690
|
}
|
|
2664
2691
|
log(` ${activeEngine.padEnd(8)} landing ${entry.task.display_id}...`);
|
|
@@ -2668,19 +2695,24 @@ async function runFleetFlight({
|
|
|
2668
2695
|
if (result.brief_id) landedRow.brief_id = result.brief_id;
|
|
2669
2696
|
if (result.restaffed) landedRow.restaffed = result.restaffed;
|
|
2670
2697
|
flight.landed.push(landedRow);
|
|
2671
|
-
stampDispatchBrief(root, result.brief_id, 'pass', `landed ${entry.task.display_id} via fleet ship`);
|
|
2672
2698
|
const restaffProof = result.restaffed ? ` Restaffed from ${result.restaffed.from} to ${result.restaffed.to} (${result.restaffed.reason}).` : '';
|
|
2673
|
-
cli
|
|
2699
|
+
const reviewRecorded = recordLandedTaskReview(cli, [
|
|
2674
2700
|
'task', 'ready', String(entry.task.display_id),
|
|
2675
2701
|
'--proof', `Built by ${activeEngine} engine in fleet flight.${restaffProof} Landed via worktree ship gate (rebase-before-ship, verify re-run). Receipt saved at ${path.relative(root, receiptPath)}. Report tail: ${headTail(String(result.report || '').replace(/\n/g, ' '))}`,
|
|
2676
2702
|
'--result', 'Operators can now review fleet-shipped work faster because the worktree was verified before landing.',
|
|
2677
2703
|
'--as', `fleet-${activeEngine}`,
|
|
2678
|
-
]);
|
|
2704
|
+
], { flight, landedRow, worktree: entry.worktreePath, save: () => atomicWriteFlightReceipt(flight, receiptPath) });
|
|
2705
|
+
if (!reviewRecorded) {
|
|
2706
|
+
stampDispatchBrief(root, result.brief_id, 'partial', `landed ${entry.task.display_id}, but task ready failed`);
|
|
2707
|
+
log(' the change landed, but saving its proof failed. repair the task record before starting more work.');
|
|
2708
|
+
continue;
|
|
2709
|
+
}
|
|
2710
|
+
stampDispatchBrief(root, result.brief_id, 'pass', `landed ${entry.task.display_id} via fleet ship`);
|
|
2679
2711
|
log(` ${activeEngine.padEnd(8)} ✓ landed ${entry.task.display_id}`);
|
|
2680
2712
|
} else {
|
|
2681
2713
|
flight.paused.push({ task: entry.task.display_id, engine: activeEngine, ...(result.restaffed ? { restaffed: result.restaffed } : {}), ...landed });
|
|
2682
2714
|
stampDispatchBrief(root, result.brief_id, 'partial', `paused ${entry.task.display_id} at ${landed.stage || 'landing'}`);
|
|
2683
|
-
log(` ${activeEngine.padEnd(8)} ⏸ paused ${entry.task.display_id} at ${landed.stage}${pausedPathsSuffix(landed)}
|
|
2715
|
+
log(` ${activeEngine.padEnd(8)} ⏸ paused ${entry.task.display_id} at ${landed.stage}${pausedPathsSuffix(landed)}, worktree kept`);
|
|
2684
2716
|
}
|
|
2685
2717
|
}
|
|
2686
2718
|
|
|
@@ -2714,7 +2746,7 @@ async function runFleetFlight({
|
|
|
2714
2746
|
}
|
|
2715
2747
|
|
|
2716
2748
|
// ---------------------------------------------------------------------------
|
|
2717
|
-
// T5
|
|
2749
|
+
// T5, one-command dispatch: `atris engine dispatch <task-id> --engine <name>`
|
|
2718
2750
|
|
|
2719
2751
|
// A claim taken before the engine starts must not stay held if the flight
|
|
2720
2752
|
// refuses or errors first. Release through the same task plane the claim used.
|
|
@@ -2736,7 +2768,7 @@ function releaseUnstartedDispatchClaim(cli, { taskId, actor, detail }) {
|
|
|
2736
2768
|
// claim so takeover stays attributed.
|
|
2737
2769
|
function releasePausedClaimsWithoutWork(cli, { paused, worktreeByTask, actorByTask, baseRef = 'origin/master', log = () => {} }) {
|
|
2738
2770
|
for (const row of paused || []) {
|
|
2739
|
-
if (row.stage === 'claim' || row.stage === 'task_lookup') continue; // claim
|
|
2771
|
+
if (row.stage === 'claim' || row.stage === 'task_lookup' || row.landed === true) continue; // no claim, or work already landed
|
|
2740
2772
|
if (row.claim_released !== undefined) continue;
|
|
2741
2773
|
const taskId = String(row.task || '');
|
|
2742
2774
|
const actor = actorByTask.get(taskId);
|
|
@@ -2815,7 +2847,7 @@ async function runDispatchFlight({
|
|
|
2815
2847
|
}
|
|
2816
2848
|
|
|
2817
2849
|
const cli = ownCli || defaultOwnCli(root);
|
|
2818
|
-
// Cut worktrees from
|
|
2850
|
+
// Cut worktrees from, and check self-landing against, the workspace's real
|
|
2819
2851
|
// protected branch, not a hard-coded origin/master (CLI-1298).
|
|
2820
2852
|
const landTarget = resolveDispatchLandTarget(root, checkoutBase);
|
|
2821
2853
|
const verify = verifier || (trustedVerifier ? defaultTrustedVerifyRunner : defaultVerifyRunner);
|
|
@@ -2844,7 +2876,7 @@ async function runDispatchFlight({
|
|
|
2844
2876
|
const liveLogPath = startLiveFlight(root, receiptPath, flight);
|
|
2845
2877
|
|
|
2846
2878
|
log('');
|
|
2847
|
-
log(` dispatch
|
|
2879
|
+
log(` dispatch, ${ids.length} task${ids.length === 1 ? '' : 's'} -> ${engine}`);
|
|
2848
2880
|
log('');
|
|
2849
2881
|
|
|
2850
2882
|
// Same rule as the fleet: dispatch worktrees cut from the protected branch by
|
|
@@ -3100,7 +3132,7 @@ async function runDispatchFlight({
|
|
|
3100
3132
|
if (result.deadEngine) paused.deadEngine = result.deadEngine;
|
|
3101
3133
|
flight.paused.push(paused);
|
|
3102
3134
|
stampDispatchBrief(root, result.brief_id, 'fail', `build failed for ${entry.taskId}`);
|
|
3103
|
-
log(` ✗ build failed ${entry.taskId}
|
|
3135
|
+
log(` ✗ build failed ${entry.taskId}, worktree kept for takeover`);
|
|
3104
3136
|
continue;
|
|
3105
3137
|
}
|
|
3106
3138
|
if (enforceRemoteBoundary && (!candidate || candidate.ok !== true)) {
|
|
@@ -3445,16 +3477,20 @@ async function runDispatchFlight({
|
|
|
3445
3477
|
if (result.brief_id) landedRow.brief_id = result.brief_id;
|
|
3446
3478
|
if (result.restaffed) landedRow.restaffed = result.restaffed;
|
|
3447
3479
|
flight.landed.push(landedRow);
|
|
3448
|
-
|
|
3449
|
-
cli([
|
|
3480
|
+
const reviewRecorded = recordLandedTaskReview(cli, [
|
|
3450
3481
|
'task', 'ready', entry.taskId,
|
|
3451
3482
|
'--proof', `Built by ${activeEngine} engine via atris engine dispatch.${restaffProof} Landed via worktree ship gate (rebase-before-ship, verify re-run). The declared verifier passed (exit 0). Verify output: ${verifyTail || '(command produced no output, exit 0)'}. Receipt saved at ${path.relative(root, receiptPath)}.`,
|
|
3452
3483
|
'--result', 'Operators can now review engine-built work faster because dispatch reran the verifier before landing.',
|
|
3453
|
-
'--landing', 'The verified change is on master and ready for review.',
|
|
3454
3484
|
'--checked', `${landed.check} passed before landing`,
|
|
3455
3485
|
'--tested', 'The requested behavior passed its declared verifier.',
|
|
3456
3486
|
'--as', readyActor,
|
|
3457
|
-
]);
|
|
3487
|
+
], { flight, landedRow, worktree: entry.worktreePath, save: () => writeDispatchReceipt(flight, receiptPath, { ids, reviewOnly, enforceRemoteBoundary }) });
|
|
3488
|
+
if (!reviewRecorded) {
|
|
3489
|
+
stampDispatchBrief(root, result.brief_id, 'partial', `landed ${entry.taskId}, but task ready failed`);
|
|
3490
|
+
log(' the change landed, but saving its proof failed. repair the task record before starting more work.');
|
|
3491
|
+
continue;
|
|
3492
|
+
}
|
|
3493
|
+
stampDispatchBrief(root, result.brief_id, 'pass', `landed ${entry.taskId} via engine dispatch`);
|
|
3458
3494
|
log(` ✓ landed ${entry.taskId}`);
|
|
3459
3495
|
} else {
|
|
3460
3496
|
flight.paused.push({
|
|
@@ -3465,7 +3501,7 @@ async function runDispatchFlight({
|
|
|
3465
3501
|
...landed,
|
|
3466
3502
|
});
|
|
3467
3503
|
stampDispatchBrief(root, result.brief_id, 'partial', `paused ${entry.taskId} at ${landed.stage || 'landing'}`);
|
|
3468
|
-
log(` ⏸ paused ${entry.taskId} at ${landed.stage}${pausedPathsSuffix(landed)}
|
|
3504
|
+
log(` ⏸ paused ${entry.taskId} at ${landed.stage}${pausedPathsSuffix(landed)}, worktree kept`);
|
|
3469
3505
|
}
|
|
3470
3506
|
}
|
|
3471
3507
|
|
package/lib/html-render.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Atris HTML renderer
|
|
1
|
+
// Atris HTML renderer, same content model as the deck engine, rendered to
|
|
2
2
|
// beautiful, anti-slop HTML made of semantic blocks. Pure: spec -> HTML string.
|
|
3
3
|
//
|
|
4
4
|
// Connects with the web apps: the `atris` theme matches atrisos-web tokens
|