atris 3.58.8 → 3.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -2
- package/atris/skills/engines/SKILL.md +28 -1
- package/bin/atris.js +13 -0
- package/commands/autopilot.js +75 -5
- package/commands/clean.js +17 -0
- package/commands/doc-health.js +11 -6
- package/commands/engine.js +386 -4
- package/commands/host.js +62 -0
- package/commands/member.js +57 -6
- package/commands/mission.js +72 -8
- package/commands/one-lap.js +58 -3
- package/commands/run.js +4 -1
- package/commands/scout.js +1 -1
- package/commands/search.js +5 -3
- package/commands/team.js +20 -3
- package/commands/unknowns.js +5 -0
- package/commands/verify.js +12 -4
- package/commands/visualize.js +20 -3
- package/commands/workflow.js +17 -6
- package/lib/dispatch-scout.js +31 -22
- package/lib/engine-registry.js +1482 -13
- package/lib/feedback-triage.js +2 -0
- package/lib/fleet.js +107 -11
- package/lib/host.js +461 -0
- package/lib/intents.js +15 -0
- package/lib/known-commands.js +1 -1
- package/lib/map-refs.js +44 -1
- package/lib/member-engine.js +253 -0
- package/lib/roster-markdown.js +206 -0
- package/lib/roster-models.js +179 -0
- package/lib/runner-command.js +94 -14
- package/lib/self-drive.js +9 -1
- package/lib/team-lineup.js +167 -0
- package/lib/wish-audit.js +10 -6
- package/lib/wish-delegate.js +10 -0
- package/package.json +1 -1
- package/templates/members/host/MEMBER.md +23 -0
- package/templates/members/host/MISSION.md +11 -0
- package/templates/members/host/SOUL.md +7 -0
- package/templates/members/host/context/why.md +3 -0
- package/templates/members/host/goals.json +5 -0
- package/templates/members/host/goals.md +3 -0
- package/templates/members/host/skills/host/SKILL.md +15 -0
package/lib/feedback-triage.js
CHANGED
|
@@ -183,9 +183,11 @@ async function dispatchFeedbackTriage(brief, workspace, deps = {}) {
|
|
|
183
183
|
: resolveEngine('executor', workspace, 'codex', { taskType: 'feedback_triage', lowStakes: true });
|
|
184
184
|
const engine = selected && selected.engine && selected.engine.id;
|
|
185
185
|
if (!engine) throw new Error('feedback triage found no ready engine');
|
|
186
|
+
const model = selected.engine.roster_model || '';
|
|
186
187
|
|
|
187
188
|
const answers = await dispatch([{
|
|
188
189
|
engine,
|
|
190
|
+
...(model ? { model } : {}),
|
|
189
191
|
prompt: brief.prompt,
|
|
190
192
|
label: `feedback ${brief.feedback_id}`,
|
|
191
193
|
}], {
|
package/lib/fleet.js
CHANGED
|
@@ -37,6 +37,7 @@ const { sleepSync } = require('./sleep-sync');
|
|
|
37
37
|
const { ownerIdentity } = require('../utils/owner-identity');
|
|
38
38
|
const { matchTaste } = require('./taste-lessons');
|
|
39
39
|
const { buildVerifiedScoutPack, appendVerifiedScoutPack } = require('./dispatch-scout');
|
|
40
|
+
const { mapNotesPath } = require('./map-refs');
|
|
40
41
|
const {
|
|
41
42
|
applySecretGrantEnvironment,
|
|
42
43
|
buildGatewaySupervisorScript,
|
|
@@ -110,7 +111,9 @@ function buildFleetPrompt(task, { worktreePath, yolo = false } = {}) {
|
|
|
110
111
|
lines.push(`Check: ${check}`, '');
|
|
111
112
|
lines.push(
|
|
112
113
|
'Rules:',
|
|
113
|
-
|
|
114
|
+
mapNotesPath(worktreePath || process.cwd())
|
|
115
|
+
? '- Read atris/MAP.md first to locate the code, then atris/refs/MAP-NOTES.md for exact file:line refs; never guess file locations.'
|
|
116
|
+
: '- Read atris/MAP.md first to locate the code; never guess file locations.',
|
|
114
117
|
'- Keep one concern per PR; split anything larger into separate PRs because git history guides future agents and small PRs are cheap to revert and bisect.',
|
|
115
118
|
'- Run git status first. Stage ONLY files you changed. Never revert or touch files another agent modified.',
|
|
116
119
|
'- Include or update a focused regression test; run it with npm run test:fast && node --test <focused files> before committing.',
|
|
@@ -205,21 +208,37 @@ function assertIsolatedWorktree(worktreePath, root = process.cwd()) {
|
|
|
205
208
|
return { worktreePath: resolvedWorktree, primaryRoot };
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
|
|
211
|
+
// A roster time cap, in whole seconds, or 0 when none is set.
|
|
212
|
+
function capSeconds(value) {
|
|
213
|
+
const seconds = Math.floor(Number(value));
|
|
214
|
+
return Number.isFinite(seconds) && seconds > 0 ? seconds : 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// The pin a validator runs with: a model string (older callers) or
|
|
218
|
+
// { model, effort, max_seconds } from the roster.
|
|
219
|
+
function validatorPin(validatorModels, engine) {
|
|
220
|
+
const pin = validatorModels && validatorModels[engine];
|
|
221
|
+
if (!pin) return { model: '', effort: '', maxSeconds: 0 };
|
|
222
|
+
if (typeof pin === 'string') return { model: pin, effort: '', maxSeconds: 0 };
|
|
223
|
+
return { model: String(pin.model || ''), effort: String(pin.effort || ''), maxSeconds: capSeconds(pin.max_seconds) };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function wrapCodexWithWatchdog(cmd, watchdogPath = CODEX_WATCHDOG_SOURCE, receiptPath = '', maxSeconds = 0) {
|
|
209
227
|
// Codex hangs forever at startup when stdin is an open pipe (three real
|
|
210
228
|
// flights on 2026-08-11 sat 50-100 minutes at zero CPU). Force every fleet
|
|
211
229
|
// codex spawn through the silent-start / runtime-cap watchdog with stdin
|
|
212
230
|
// sealed, so orchestrator prompts cannot forget the convention.
|
|
213
231
|
const receiptArg = receiptPath ? ` --receipt ${shellSingleQuote(receiptPath)}` : '';
|
|
214
|
-
|
|
232
|
+
const maxRuntime = capSeconds(maxSeconds) || 3600;
|
|
233
|
+
return `${shellSingleQuote(process.execPath)} ${shellSingleQuote(watchdogPath)} --startup-deadline 90 --max-runtime ${maxRuntime}${receiptArg} -- sh -c ${shellSingleQuote(cmd)} </dev/null`;
|
|
215
234
|
}
|
|
216
235
|
|
|
217
|
-
function buildEngineCommand(engineName, promptFile, { yolo = false, sealed = false, allowedTools = FLEET_ALLOWED_TOOLS, watchdogPath = CODEX_WATCHDOG_SOURCE, watchdogReceiptPath = '' } = {}) {
|
|
236
|
+
function buildEngineCommand(engineName, promptFile, { yolo = false, sealed = false, allowedTools = FLEET_ALLOWED_TOOLS, watchdogPath = CODEX_WATCHDOG_SOURCE, watchdogReceiptPath = '', model = '', effort = '', maxSeconds = 0 } = {}) {
|
|
218
237
|
if (!RUNNER_PROFILE_DEFS[engineName]) throw new Error(`unknown engine "${engineName}"`);
|
|
219
238
|
const prev = process.env.ATRIS_RUNNER_PROFILE;
|
|
220
239
|
process.env.ATRIS_RUNNER_PROFILE = engineName;
|
|
221
240
|
try {
|
|
222
|
-
let cmd = buildRunnerCommand({ promptFile, allowedTools });
|
|
241
|
+
let cmd = buildRunnerCommand({ promptFile, allowedTools, ...(model ? { model } : {}), ...(effort ? { effort } : {}) });
|
|
223
242
|
// devin's default is read-only for writes; fleet builds ALWAYS run in an
|
|
224
243
|
// isolated worktree, so the conductor grants write permission here and
|
|
225
244
|
// only here (the profile itself stays safe for non-worktree ticks).
|
|
@@ -243,7 +262,7 @@ function buildEngineCommand(engineName, promptFile, { yolo = false, sealed = fal
|
|
|
243
262
|
if (yolo && engineName === 'codex') cmd = cmd.replace(/\bexec\b/, `exec ${YOLO_ENGINE_FLAGS.codex}`);
|
|
244
263
|
if (yolo && (engineName === 'claude' || engineName === 'fable')) cmd = `${cmd} ${YOLO_ENGINE_FLAGS.claude}`;
|
|
245
264
|
if (yolo && engineName === 'commandcode') cmd = `${cmd} --yolo`;
|
|
246
|
-
if (engineName === 'codex') cmd = wrapCodexWithWatchdog(cmd, watchdogPath, watchdogReceiptPath);
|
|
265
|
+
if (engineName === 'codex') cmd = wrapCodexWithWatchdog(cmd, watchdogPath, watchdogReceiptPath, maxSeconds);
|
|
247
266
|
return cmd;
|
|
248
267
|
} finally {
|
|
249
268
|
if (prev === undefined) delete process.env.ATRIS_RUNNER_PROFILE;
|
|
@@ -303,6 +322,10 @@ function terminateSameSandboxProfile(executable, args, options) {
|
|
|
303
322
|
});
|
|
304
323
|
}
|
|
305
324
|
|
|
325
|
+
// Every parent signal guard a dispatch installs, so one dispatch can tell
|
|
326
|
+
// another dispatch's guard from a handler someone else added.
|
|
327
|
+
const DISPATCH_PARENT_GUARDS = new WeakSet();
|
|
328
|
+
|
|
306
329
|
function spawnWithOutputChunks(executable, args, options = {}) {
|
|
307
330
|
const {
|
|
308
331
|
encoding,
|
|
@@ -311,8 +334,13 @@ function spawnWithOutputChunks(executable, args, options = {}) {
|
|
|
311
334
|
timeout,
|
|
312
335
|
...childOptions
|
|
313
336
|
} = options;
|
|
337
|
+
// detached makes the child its own process-group leader, so a time cap can
|
|
338
|
+
// stop the engine the shell launched and not just the shell. Windows has no
|
|
339
|
+
// process groups and keeps the direct child kill.
|
|
340
|
+
const processGroups = process.platform !== 'win32';
|
|
314
341
|
const child = spawn(executable, args, {
|
|
315
342
|
...childOptions,
|
|
343
|
+
detached: processGroups,
|
|
316
344
|
stdio: [input == null ? 'ignore' : 'pipe', 'pipe', 'pipe'],
|
|
317
345
|
});
|
|
318
346
|
if (input != null && child.stdin) {
|
|
@@ -320,6 +348,53 @@ function spawnWithOutputChunks(executable, args, options = {}) {
|
|
|
320
348
|
child.stdin.end(input);
|
|
321
349
|
}
|
|
322
350
|
|
|
351
|
+
const killChild = () => {
|
|
352
|
+
if (!processGroups) {
|
|
353
|
+
try { child.kill('SIGKILL'); } catch {}
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
try { process.kill(-child.pid, 'SIGKILL'); } catch { try { child.kill('SIGKILL'); } catch {} }
|
|
357
|
+
};
|
|
358
|
+
// If this process dies while the child runs, take the child's group down
|
|
359
|
+
// with it so an interrupted dispatch does not orphan the engine. A detached
|
|
360
|
+
// child leads its own group, so the terminal's Ctrl-C never reaches it, and
|
|
361
|
+
// Node's default signal exit skips 'exit' listeners. Guard the termination
|
|
362
|
+
// signals too: kill the group, drop every listener this dispatch added,
|
|
363
|
+
// then re-raise the same signal so the parent still dies the way it would
|
|
364
|
+
// have. Re-raise only when nothing but dispatch guards listened when this
|
|
365
|
+
// one installed, and nothing listens now: a handler added with
|
|
366
|
+
// process.once has already removed itself by the time this runs, and its
|
|
367
|
+
// async cleanup owns exiting. Other dispatches' guards do not count at
|
|
368
|
+
// install, so the last of several running dispatches still re-raises.
|
|
369
|
+
const killGroupOnParentExit = () => {
|
|
370
|
+
try { process.kill(-child.pid, 'SIGKILL'); } catch {}
|
|
371
|
+
};
|
|
372
|
+
const parentGuards = [['exit', killGroupOnParentExit]];
|
|
373
|
+
const othersAtInstall = new Map();
|
|
374
|
+
const setParentGuards = (install) => {
|
|
375
|
+
for (const [event, handler] of parentGuards) {
|
|
376
|
+
if (install) {
|
|
377
|
+
othersAtInstall.set(event, process.listeners(event).filter((fn) => !DISPATCH_PARENT_GUARDS.has(fn)).length);
|
|
378
|
+
DISPATCH_PARENT_GUARDS.add(handler);
|
|
379
|
+
process.on(event, handler);
|
|
380
|
+
} else {
|
|
381
|
+
process.removeListener(event, handler);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
386
|
+
parentGuards.push([signal, () => {
|
|
387
|
+
killGroupOnParentExit();
|
|
388
|
+
setParentGuards(false);
|
|
389
|
+
if (othersAtInstall.get(signal) === 0 && process.listenerCount(signal) === 0) {
|
|
390
|
+
process.kill(process.pid, signal);
|
|
391
|
+
}
|
|
392
|
+
}]);
|
|
393
|
+
}
|
|
394
|
+
if (processGroups && typeof child.pid === 'number') {
|
|
395
|
+
setParentGuards(true);
|
|
396
|
+
}
|
|
397
|
+
|
|
323
398
|
const stdoutChunks = [];
|
|
324
399
|
const stderrChunks = [];
|
|
325
400
|
let spawnError = null;
|
|
@@ -337,11 +412,12 @@ function spawnWithOutputChunks(executable, args, options = {}) {
|
|
|
337
412
|
? setTimeout(() => {
|
|
338
413
|
timeoutError = new Error('spawn ETIMEDOUT');
|
|
339
414
|
timeoutError.code = 'ETIMEDOUT';
|
|
340
|
-
|
|
415
|
+
killChild();
|
|
341
416
|
}, Number(timeout))
|
|
342
417
|
: null;
|
|
343
418
|
child.once('close', (status, signal) => {
|
|
344
419
|
if (timer) clearTimeout(timer);
|
|
420
|
+
setParentGuards(false);
|
|
345
421
|
const encode = (chunks) => {
|
|
346
422
|
const output = Buffer.concat(chunks);
|
|
347
423
|
return encoding ? output.toString(encoding) : output;
|
|
@@ -761,7 +837,7 @@ async function dispatchEntryWithRestaff({
|
|
|
761
837
|
// injectable for tests. `prompt` is injectable too: a caller-supplied prompt
|
|
762
838
|
// (e.g. `atris engine dispatch --prompt-file`) skips the generated
|
|
763
839
|
// buildFleetPrompt text entirely.
|
|
764
|
-
function dispatchToEngine({ task, engine, worktreePath, root = process.cwd(), timeoutMs = null, runner = null, prompt: promptOverride = '', yolo = false, sealed = false, briefId = '', skipBriefCapture = false, environment = null, allowedTools = FLEET_ALLOWED_TOOLS, liveLogPath = '' }) {
|
|
840
|
+
function dispatchToEngine({ task, engine, worktreePath, root = process.cwd(), timeoutMs = null, runner = null, prompt: promptOverride = '', yolo = false, sealed = false, briefId = '', skipBriefCapture = false, environment = null, allowedTools = FLEET_ALLOWED_TOOLS, liveLogPath = '', model = '', effort = '', maxSeconds = 0 }) {
|
|
765
841
|
assertIsolatedWorktree(worktreePath, root);
|
|
766
842
|
const prompt = promptOverride || buildFleetPrompt(task, { worktreePath, yolo });
|
|
767
843
|
let capturedBriefId = briefId;
|
|
@@ -788,10 +864,17 @@ function dispatchToEngine({ task, engine, worktreePath, root = process.cwd(), ti
|
|
|
788
864
|
allowedTools,
|
|
789
865
|
watchdogPath,
|
|
790
866
|
watchdogReceiptPath: watchdogArtifact,
|
|
867
|
+
model,
|
|
868
|
+
effort,
|
|
869
|
+
maxSeconds,
|
|
791
870
|
});
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
871
|
+
// A roster time cap replaces the default backstop. Codex keeps a minute of
|
|
872
|
+
// slack past its watchdog so the watchdog's own receipt gets written.
|
|
873
|
+
const cap = capSeconds(maxSeconds);
|
|
874
|
+
const defaultTimeoutMs = cap
|
|
875
|
+
? cap * 1000 + (engine === 'codex' ? 60000 : 0)
|
|
876
|
+
: (engine === 'codex' ? CODEX_DISPATCH_TIMEOUT_MS : DEFAULT_DISPATCH_TIMEOUT_MS);
|
|
877
|
+
const dispatchTimeoutMs = timeoutMs === null || timeoutMs === undefined ? defaultTimeoutMs : timeoutMs;
|
|
795
878
|
const exec = runner || ((cmd) => {
|
|
796
879
|
const childEnv = sealed && environment
|
|
797
880
|
? { ...environment }
|
|
@@ -2130,6 +2213,7 @@ async function runIndependentValidator({
|
|
|
2130
2213
|
validatorEngines,
|
|
2131
2214
|
validatorDispatcher = null,
|
|
2132
2215
|
stateInspector = reviewWorktreeSnapshot,
|
|
2216
|
+
validatorModels = null,
|
|
2133
2217
|
}) {
|
|
2134
2218
|
const candidates = [...new Set((validatorEngines || []).map((value) => String(value || '').trim()).filter(Boolean))]
|
|
2135
2219
|
.filter((name) => RUNNER_PROFILE_DEFS[name] && name !== executorEngine);
|
|
@@ -2152,6 +2236,7 @@ async function runIndependentValidator({
|
|
|
2152
2236
|
};
|
|
2153
2237
|
}
|
|
2154
2238
|
const dispatch = validatorDispatcher || ((entry) => Promise.resolve(dispatchToEngine({
|
|
2239
|
+
...validatorPin(validatorModels, entry.engine),
|
|
2155
2240
|
task,
|
|
2156
2241
|
engine: entry.engine,
|
|
2157
2242
|
worktreePath,
|
|
@@ -2828,6 +2913,13 @@ async function runDispatchFlight({
|
|
|
2828
2913
|
validatorDispatcher = null,
|
|
2829
2914
|
validatorStateInspector = null,
|
|
2830
2915
|
scoutAsk = null,
|
|
2916
|
+
// Pinned model, effort, and time cap for the requested engine only; a
|
|
2917
|
+
// restaffed fallback engine runs on its own defaults. validatorModels maps
|
|
2918
|
+
// a validator engine to its model, or to { model, effort, max_seconds }.
|
|
2919
|
+
model = '',
|
|
2920
|
+
effort = '',
|
|
2921
|
+
maxSeconds = 0,
|
|
2922
|
+
validatorModels = null,
|
|
2831
2923
|
} = {}) {
|
|
2832
2924
|
if (!engine) throw new Error('runDispatchFlight: engine is required');
|
|
2833
2925
|
if (!DISPATCH_CAPABLE.includes(engine)) {
|
|
@@ -2985,6 +3077,9 @@ async function runDispatchFlight({
|
|
|
2985
3077
|
briefId: entry.brief_id,
|
|
2986
3078
|
skipBriefCapture: true,
|
|
2987
3079
|
liveLogPath,
|
|
3080
|
+
model: entry.engine === engine ? model : '',
|
|
3081
|
+
effort: entry.engine === engine ? effort : '',
|
|
3082
|
+
maxSeconds: entry.engine === engine ? maxSeconds : 0,
|
|
2988
3083
|
}));
|
|
2989
3084
|
}));
|
|
2990
3085
|
const restaffState = { used: false };
|
|
@@ -3272,6 +3367,7 @@ async function runDispatchFlight({
|
|
|
3272
3367
|
validatorEngines,
|
|
3273
3368
|
validatorDispatcher,
|
|
3274
3369
|
stateInspector: validatorStateInspector || reviewWorktreeSnapshot,
|
|
3370
|
+
validatorModels,
|
|
3275
3371
|
});
|
|
3276
3372
|
validatorResult = validation.validator_result;
|
|
3277
3373
|
flight.validator_result = validatorResult;
|