atris 3.70.0 → 3.71.1
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 +18 -0
- package/atris/skills/README.md +1 -0
- package/atris/skills/campaigns/SKILL.md +81 -0
- package/atris/skills/meta/SKILL.md +52 -8
- package/ax +226 -42
- package/bin/atris.js +38 -8
- package/commands/autoland.js +176 -7
- package/commands/autopilot.js +71 -11
- package/commands/campaign.js +779 -0
- package/commands/compile.js +19 -8
- package/commands/doctor.js +58 -0
- package/commands/dream.js +18 -3
- package/commands/engine.js +147 -5
- package/commands/improve.js +119 -9
- package/commands/init.js +18 -1
- package/commands/integrations.js +17 -5
- package/commands/interview.js +15 -3
- package/commands/land.js +22 -6
- package/commands/link.js +1 -0
- package/commands/member.js +359 -94
- package/commands/mission.js +249 -36
- package/commands/report.js +42 -8
- package/commands/run.js +9 -2
- package/commands/scout.js +9 -1
- package/commands/task.js +71 -17
- package/commands/team.js +212 -4
- package/commands/unknowns.js +15 -1
- package/commands/verify.js +46 -0
- package/commands/worktree.js +108 -15
- package/commands/write.js +12 -1
- package/commands/youtube.js +17 -2
- package/lib/accept-verify-gate.js +1 -0
- package/lib/account-bound.js +13 -0
- package/lib/auto-accept-certified.js +1 -0
- package/lib/autoland.js +13 -4
- package/lib/campaign-cadence.js +131 -0
- package/lib/campaign-core.js +330 -0
- package/lib/campaign-holdout.js +65 -0
- package/lib/campaign-loop.js +246 -0
- package/lib/campaign-missions.js +237 -0
- package/lib/campaign-new.js +251 -0
- package/lib/campaign-protect.js +75 -0
- package/lib/campaign-reading.js +147 -0
- package/lib/campaign-run-lock.js +59 -0
- package/lib/campaign-schema.js +196 -0
- package/lib/campaign-store.js +380 -0
- package/lib/campaign-sync.js +302 -0
- package/lib/campaign-trace.js +300 -0
- package/lib/conductor-artifacts.js +15 -1
- package/lib/daily-log.js +11 -1
- package/lib/dispatch-scout.js +18 -0
- package/lib/engine-ask.js +33 -3
- package/lib/engine-registry.js +2 -0
- package/lib/engine-resource.js +518 -0
- package/lib/fleet.js +125 -16
- package/lib/init-commit.js +80 -0
- package/lib/intents.js +8 -0
- package/lib/known-commands.js +1 -1
- package/lib/land-green.js +58 -7
- package/lib/landing-proof.js +352 -0
- package/lib/member-autoland.js +395 -18
- package/lib/member-board.js +7 -1
- package/lib/member-map-fold.js +126 -0
- package/lib/member-map-inbox.js +80 -0
- package/lib/member-progress.js +48 -1
- package/lib/member-report.js +127 -8
- package/lib/member-self-next-job.js +97 -0
- package/lib/member-task-pick.js +41 -14
- package/lib/member-up.js +249 -16
- package/lib/roster-prep.js +25 -2
- package/lib/runner-command.js +5 -0
- package/lib/task-db.js +47 -11
- package/lib/task-explanation.js +7 -0
- package/lib/task-list-keeper.js +8 -1
- package/lib/task-receipt.js +6 -1
- package/mcp/atris-mcp/index.mjs +212 -10
- package/package.json +2 -1
- package/scripts/det/engine-reading.js +37 -0
- package/scripts/det/ytnotes +29 -5
package/commands/mission.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
5
6
|
const crypto = require('crypto');
|
|
6
7
|
const readline = require('readline');
|
|
7
8
|
const { spawn, spawnSync } = require('child_process');
|
|
@@ -115,6 +116,8 @@ const {
|
|
|
115
116
|
|
|
116
117
|
const VALID_STATUSES = new Set(['planning', 'running', 'ready', 'paused', 'blocked', 'stopped', 'complete']);
|
|
117
118
|
const TERMINAL_STATUSES = new Set(['stopped', 'complete']);
|
|
119
|
+
// Campaign hooks load on first use, so missions without a campaign never open its store.
|
|
120
|
+
const campaignMissions = () => require('../lib/campaign-missions');
|
|
118
121
|
const GOAL_LOOP_STATUSES = new Set(['planning', 'running', 'ready']);
|
|
119
122
|
const STATUS_ALIASES = new Set(['active']);
|
|
120
123
|
const CODEX_NATIVE_GOAL_SLOT_STATUSES = new Set(['active', 'paused', 'usage_limited']);
|
|
@@ -425,11 +428,30 @@ async function missionTickPrep(root, { mission, runtimeMission, job, prompt, ask
|
|
|
425
428
|
|
|
426
429
|
// A tick's time limit: the time left on the mission, capped by the roster
|
|
427
430
|
// line's "max 20 min" when it sets one, else by the default tick cap.
|
|
428
|
-
function
|
|
429
|
-
|
|
431
|
+
function missionTickCapMs(mission) {
|
|
432
|
+
return Number(mission && mission.roster_max_seconds) > 0
|
|
430
433
|
? Number(mission.roster_max_seconds) * 1000
|
|
431
434
|
: MISSION_RUN_DEFAULTS.claudeTimeoutMs;
|
|
432
|
-
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function missionTickTimeoutMs(mission, remainingMs) {
|
|
438
|
+
return clampTimeoutMs(remainingMs, missionTickCapMs(mission));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// True when the time left on the mission, not the tick's own cap, set the
|
|
442
|
+
// tick's limit, so a kill there means the mission clock ran out.
|
|
443
|
+
function missionTickWallBound(mission, remainingMs) {
|
|
444
|
+
return remainingMs <= missionTickCapMs(mission);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// The reason a member log shows for a tick. A failed engine tick names its
|
|
448
|
+
// engine beside the reason, so a codex run that timed out stops counting as
|
|
449
|
+
// a claude timeout in the repeat-problem scan. The reason itself is left as
|
|
450
|
+
// is because engine health and backoff match on it.
|
|
451
|
+
function memberLogTickReason(result) {
|
|
452
|
+
const reason = result && result.reason;
|
|
453
|
+
if (!reason || !result.engine_id || result.status !== 'errored') return reason;
|
|
454
|
+
return `${reason} (engine: ${result.engine_id})`;
|
|
433
455
|
}
|
|
434
456
|
|
|
435
457
|
function resolveMissionTickRunner(mission, root = process.cwd(), options = {}) {
|
|
@@ -742,6 +764,8 @@ const MISSION_RUN_VALUE_FLAGS = [
|
|
|
742
764
|
'--engine',
|
|
743
765
|
'--referee-engine',
|
|
744
766
|
'--budget',
|
|
767
|
+
'--campaign',
|
|
768
|
+
'--reserve',
|
|
745
769
|
'--repo',
|
|
746
770
|
'--native-goal-status',
|
|
747
771
|
'--native-goal-objective',
|
|
@@ -1478,14 +1502,24 @@ function nearestMissionCandidates(missions, ref, limit = 5) {
|
|
|
1478
1502
|
.map((entry) => entry.mission);
|
|
1479
1503
|
}
|
|
1480
1504
|
|
|
1481
|
-
|
|
1505
|
+
// Sibling folders looked at for a hint. A missing-id error waited 26 seconds
|
|
1506
|
+
// on a temp folder holding 276,000 entries, so the scan is capped and never
|
|
1507
|
+
// walks the system temp folder.
|
|
1508
|
+
const SIBLING_HINT_SCAN_LIMIT = 2000;
|
|
1509
|
+
|
|
1510
|
+
function listSiblingWorkspaceMissionHints(ref, root = process.cwd(), limit = 5, { scanLimit = SIBLING_HINT_SCAN_LIMIT, tmpRoot = os.tmpdir() } = {}) {
|
|
1482
1511
|
if (!ref) return [];
|
|
1483
1512
|
const baseRoot = path.resolve(root);
|
|
1484
1513
|
const parent = path.dirname(baseRoot);
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1514
|
+
const real = (target) => {
|
|
1515
|
+
try {
|
|
1516
|
+
return fs.realpathSync(target);
|
|
1517
|
+
} catch {
|
|
1518
|
+
return target;
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
const here = real(baseRoot);
|
|
1522
|
+
if (real(parent) === real(tmpRoot)) return [];
|
|
1489
1523
|
let entries = [];
|
|
1490
1524
|
try {
|
|
1491
1525
|
entries = fs.readdirSync(parent, { withFileTypes: true });
|
|
@@ -1493,17 +1527,11 @@ function listSiblingWorkspaceMissionHints(ref, root = process.cwd(), limit = 5)
|
|
|
1493
1527
|
return [];
|
|
1494
1528
|
}
|
|
1495
1529
|
const hints = [];
|
|
1496
|
-
for (const entry of entries) {
|
|
1530
|
+
for (const entry of entries.slice(0, scanLimit)) {
|
|
1497
1531
|
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
1498
1532
|
const candidateRoot = path.join(parent, entry.name);
|
|
1499
|
-
let candidateReal = candidateRoot;
|
|
1500
|
-
try {
|
|
1501
|
-
candidateReal = fs.realpathSync(candidateRoot);
|
|
1502
|
-
} catch {
|
|
1503
|
-
continue;
|
|
1504
|
-
}
|
|
1505
|
-
if (candidateReal === here) continue;
|
|
1506
1533
|
if (!fs.existsSync(path.join(candidateRoot, '.atris', 'state', 'missions.jsonl'))) continue;
|
|
1534
|
+
if (real(candidateRoot) === here) continue;
|
|
1507
1535
|
const mission = listMissions(candidateRoot).find((row) => missionMatchesRef(row, ref));
|
|
1508
1536
|
if (!mission) continue;
|
|
1509
1537
|
hints.push({
|
|
@@ -1696,6 +1724,7 @@ function saveMission(mission, root = process.cwd(), eventType = 'mission_updated
|
|
|
1696
1724
|
// this file several times, so unbounded snapshot history taxes everything.
|
|
1697
1725
|
try { compactMissionLedger(paths.missionsJsonl); } catch {}
|
|
1698
1726
|
const event = appendEvent(eventType, next, payload, root);
|
|
1727
|
+
if (next.campaign_id) campaignMissions().recordCampaignMissionEnded(root, existing, next, TERMINAL_STATUSES);
|
|
1699
1728
|
renderMissionStatus(root);
|
|
1700
1729
|
renderMemberMissionState(next.owner, root);
|
|
1701
1730
|
return { mission: next, event };
|
|
@@ -3169,6 +3198,9 @@ function missionFromArgs(args) {
|
|
|
3169
3198
|
'--base',
|
|
3170
3199
|
'--task',
|
|
3171
3200
|
'--ask',
|
|
3201
|
+
'--campaign',
|
|
3202
|
+
'--reserve',
|
|
3203
|
+
'--mission-id',
|
|
3172
3204
|
'--model',
|
|
3173
3205
|
'--destination',
|
|
3174
3206
|
'--native-goal-status',
|
|
@@ -3185,6 +3217,7 @@ function missionFromArgs(args) {
|
|
|
3185
3217
|
const budgetContract = inferRunObjectiveBudgetContract(objective, args);
|
|
3186
3218
|
const maxTicksOverride = readPositiveIntegerFlag(args, '--max-ticks', null, { json: wantsJson(args) });
|
|
3187
3219
|
const requestedOwner = readFlag(args, '--owner', process.env.ATRIS_AGENT_ID || 'mission-lead');
|
|
3220
|
+
const campaignId = readFlag(args, '--campaign', '');
|
|
3188
3221
|
const cadence = readFlag(args, '--cadence', readFlag(args, '--loop', 'manual')) || 'manual';
|
|
3189
3222
|
const engineFlag = readFlag(args, '--engine', '');
|
|
3190
3223
|
const runnerSelection = engineFlag
|
|
@@ -3212,7 +3245,13 @@ function missionFromArgs(args) {
|
|
|
3212
3245
|
const alwaysOn = hasFlag(args, '--always-on');
|
|
3213
3246
|
const xpTaskEnabled = hasFlag(args, '--xp-task') || hasFlag(args, '--agent-xp');
|
|
3214
3247
|
const businessBinding = readBusinessBinding(process.cwd());
|
|
3215
|
-
|
|
3248
|
+
// A campaign proposes a mission under an id before it runs; --mission-id
|
|
3249
|
+
// keeps that id so the campaign and the mission name the same thing.
|
|
3250
|
+
const givenId = readFlag(args, '--mission-id', '');
|
|
3251
|
+
if (givenId && !/^[a-z0-9][a-z0-9-]{2,120}$/.test(givenId)) {
|
|
3252
|
+
exitMissionError('--mission-id takes lowercase letters, digits, and dashes', 2, wantsJson(args));
|
|
3253
|
+
}
|
|
3254
|
+
const id = givenId || missionId(objective);
|
|
3216
3255
|
const mission = {
|
|
3217
3256
|
schema: 'atris.mission.v1',
|
|
3218
3257
|
id,
|
|
@@ -3238,6 +3277,7 @@ function missionFromArgs(args) {
|
|
|
3238
3277
|
stop_condition: stopCondition,
|
|
3239
3278
|
task_ids: taskIds,
|
|
3240
3279
|
human_asks: humanAsks,
|
|
3280
|
+
...(campaignId ? { campaign_id: campaignId } : {}),
|
|
3241
3281
|
...(drive ? { drive } : {}),
|
|
3242
3282
|
next_action: verifier ? 'run verifier with `atris mission tick <id> --verify`' : 'define verifier or run next task',
|
|
3243
3283
|
receipt_path: null,
|
|
@@ -4351,6 +4391,7 @@ function seedMissionRunContinuation(parent, root = process.cwd(), proof = '') {
|
|
|
4351
4391
|
...mission,
|
|
4352
4392
|
started_from: 'mission_run_continuation',
|
|
4353
4393
|
parent_mission_id: parent.id,
|
|
4394
|
+
...(parent.campaign_id && campaignMissions().checkCampaignStart(root, parent.campaign_id, { reserveUsd: 0 }).ok ? { campaign_id: parent.campaign_id } : {}),
|
|
4354
4395
|
parent_objective: parent.objective,
|
|
4355
4396
|
continue_on_complete: false,
|
|
4356
4397
|
continuation_policy: 'choose_next_mission',
|
|
@@ -4367,6 +4408,7 @@ function seedMissionRunContinuation(parent, root = process.cwd(), proof = '') {
|
|
|
4367
4408
|
parent_objective: parent.objective,
|
|
4368
4409
|
proof: proof || null,
|
|
4369
4410
|
});
|
|
4411
|
+
if (saved.campaign_id) campaignMissions().recordCampaignMissionStarted(root, saved, 0);
|
|
4370
4412
|
const worktreeBaseline = captureMissionWorktreeBaseline(saved, root);
|
|
4371
4413
|
const seededAt = stampIso();
|
|
4372
4414
|
const { mission: updatedParent } = saveMission({
|
|
@@ -4452,7 +4494,7 @@ function startMission(args, options = {}) {
|
|
|
4452
4494
|
const asJson = wantsJson(args);
|
|
4453
4495
|
const firstArg = String(args[0] || '').trim().toLowerCase();
|
|
4454
4496
|
if (args.length === 1 && (firstArg === '--help' || firstArg === '-h' || firstArg === 'help')) {
|
|
4455
|
-
console.log('Usage: atris mission start "<objective>" --owner <member> --verify "..." [--destination "..."] [--no-verify] [--always-on] [--budget quick|long|deep] [--runner manual|claude|atris2|codex_goal|auto]');
|
|
4497
|
+
console.log('Usage: atris mission start "<objective>" --owner <member> --verify "..." [--destination "..."] [--no-verify] [--always-on] [--budget quick|long|deep] [--runner manual|claude|atris2|codex_goal|auto] [--campaign <id> [--reserve <dollars>]]');
|
|
4456
4498
|
console.log('Run `atris mission --help` for the full option list.');
|
|
4457
4499
|
process.exit(0);
|
|
4458
4500
|
}
|
|
@@ -4539,6 +4581,18 @@ function startMission(args, options = {}) {
|
|
|
4539
4581
|
asJson,
|
|
4540
4582
|
);
|
|
4541
4583
|
}
|
|
4584
|
+
// --campaign: the campaign must be open and able to hold this mission's
|
|
4585
|
+
// reserve. Checked before any state exists, so a refusal leaves nothing behind.
|
|
4586
|
+
let campaignReserve = 0;
|
|
4587
|
+
if (mission.campaign_id) {
|
|
4588
|
+
const check = campaignMissions().checkCampaignStart(process.cwd(), mission.campaign_id, { missionId: mission.id, reserveUsd: readFlag(args, '--reserve', '') });
|
|
4589
|
+
if (!check.ok) exitMissionError(`mission start refused: ${check.error}`, 2, asJson);
|
|
4590
|
+
try {
|
|
4591
|
+
campaignMissions().reserveCampaignMission(process.cwd(), mission.campaign_id, mission.id, check.reserve, mission.owner || 'mission-lead');
|
|
4592
|
+
} catch (error) {
|
|
4593
|
+
exitMissionError(`mission start refused: ${error.message}`, 2, asJson);
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4542
4596
|
// --worktree: bind the mission to its own isolated checkout. We chdir before
|
|
4543
4597
|
// any state writes so the mission record, baseline sidecar, receipts, and
|
|
4544
4598
|
// member files all land inside the worktree, ticks run there, and the main
|
|
@@ -4630,6 +4684,7 @@ function startMission(args, options = {}) {
|
|
|
4630
4684
|
const warnings = [missingVerifierWarning(mission), missingOwnerMemberWarning(mission.owner)].filter(Boolean);
|
|
4631
4685
|
ensureMemberMissionFile(mission.owner, process.cwd(), mission.objective);
|
|
4632
4686
|
const { mission: saved } = saveMission(assignMissionNumber(mission, process.cwd()), process.cwd(), 'mission_started', { objective: mission.objective });
|
|
4687
|
+
if (saved.campaign_id) campaignMissions().recordCampaignMissionStarted(process.cwd(), saved, campaignReserve);
|
|
4633
4688
|
const goalSlotHandoff = hasFlag(args, '--take-goal-slot') && isCodexGoalMission(saved)
|
|
4634
4689
|
? takeCodexGoalSlotForMission(saved, process.cwd())
|
|
4635
4690
|
: null;
|
|
@@ -4750,6 +4805,8 @@ async function startMissionFromRunObjective(objective, args) {
|
|
|
4750
4805
|
];
|
|
4751
4806
|
if (verifier) startArgs.push('--verify', verifier);
|
|
4752
4807
|
if (budgetTier) startArgs.push('--budget', budgetTier.name);
|
|
4808
|
+
const campaignFlag = readFlag(args, '--campaign', '');
|
|
4809
|
+
if (campaignFlag) startArgs.push('--campaign', campaignFlag);
|
|
4753
4810
|
if (maxTicksOverride) startArgs.push('--max-ticks', String(maxTicksOverride));
|
|
4754
4811
|
const model = readFlag(args, '--model', '');
|
|
4755
4812
|
if (model) startArgs.push('--model', model);
|
|
@@ -4757,6 +4814,16 @@ async function startMissionFromRunObjective(objective, args) {
|
|
|
4757
4814
|
if (hasFlag(args, '--xp-task') || hasFlag(args, '--agent-xp') || missionRunPreflight?.task_spine_required) startArgs.push('--xp-task');
|
|
4758
4815
|
|
|
4759
4816
|
const mission = markMissionRunContinuation(missionFromArgs(startArgs));
|
|
4817
|
+
let campaignReserve = 0;
|
|
4818
|
+
if (mission.campaign_id) {
|
|
4819
|
+
const check = campaignMissions().checkCampaignStart(process.cwd(), mission.campaign_id, { missionId: mission.id, reserveUsd: readFlag(args, '--reserve', '') });
|
|
4820
|
+
if (!check.ok) exitMissionError(`mission start refused: ${check.error}`, 2, wantsJson(args));
|
|
4821
|
+
try {
|
|
4822
|
+
campaignMissions().reserveCampaignMission(process.cwd(), mission.campaign_id, mission.id, check.reserve, mission.owner || 'mission-lead');
|
|
4823
|
+
} catch (error) {
|
|
4824
|
+
exitMissionError(`mission start refused: ${error.message}`, 2, wantsJson(args));
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4760
4827
|
if (landOwnerResolution) {
|
|
4761
4828
|
mission.owner = runOwner;
|
|
4762
4829
|
mission.owner_resolution = landOwnerResolution.reason || mission.owner_resolution;
|
|
@@ -4840,6 +4907,7 @@ async function startMissionFromRunObjective(objective, args) {
|
|
|
4840
4907
|
const warnings = [missingVerifierWarning(mission), missingOwnerMemberWarning(mission.owner)].filter(Boolean);
|
|
4841
4908
|
ensureMemberMissionFile(mission.owner, process.cwd(), mission.objective);
|
|
4842
4909
|
const { mission: saved } = saveMission(assignMissionNumber(mission, process.cwd()), process.cwd(), 'mission_started', { objective: mission.objective, source: 'mission_run_objective' });
|
|
4910
|
+
if (saved.campaign_id) campaignMissions().recordCampaignMissionStarted(process.cwd(), saved, campaignReserve);
|
|
4843
4911
|
if (landRun) {
|
|
4844
4912
|
const memberState = renderMemberMissionState(saved.owner);
|
|
4845
4913
|
const logPath = appendMemberLog(saved.owner, 'Mission landing flight dispatched', {
|
|
@@ -6597,7 +6665,7 @@ function runVerifier(command, root = process.cwd()) {
|
|
|
6597
6665
|
const resolvedCommand = resolveVerifierCommand(command);
|
|
6598
6666
|
const { envWithNodeDir } = require('../lib/spawn-env');
|
|
6599
6667
|
const timeoutMs = missionVerifierTimeoutMs();
|
|
6600
|
-
const startedAt =
|
|
6668
|
+
const startedAt = process.hrtime.bigint();
|
|
6601
6669
|
const result = spawnSync(resolvedCommand, {
|
|
6602
6670
|
cwd: root,
|
|
6603
6671
|
shell: true,
|
|
@@ -6605,11 +6673,15 @@ function runVerifier(command, root = process.cwd()) {
|
|
|
6605
6673
|
timeout: timeoutMs,
|
|
6606
6674
|
env: envWithNodeDir(process.env),
|
|
6607
6675
|
});
|
|
6608
|
-
const elapsedMs = Date.now() - startedAt;
|
|
6609
6676
|
// A verifier that blows its window is not a failing check: the code may be
|
|
6610
6677
|
// fine and the suite merely slow (or starved by a concurrent flight). Report
|
|
6611
6678
|
// it as a distinct timed-out state so operators do not read slow as broken.
|
|
6612
6679
|
const timedOut = result.error ? result.error.code === 'ETIMEDOUT' : false;
|
|
6680
|
+
// Steady clock, not wall time: the wall clock can slew, and a kill at the
|
|
6681
|
+
// window once read as 927ms of a 1000ms window. A timed-out run used the
|
|
6682
|
+
// whole window by definition.
|
|
6683
|
+
const measuredMs = Number((process.hrtime.bigint() - startedAt) / 1000000n);
|
|
6684
|
+
const elapsedMs = timedOut ? Math.max(measuredMs, timeoutMs) : measuredMs;
|
|
6613
6685
|
return {
|
|
6614
6686
|
command,
|
|
6615
6687
|
resolved_command: resolvedCommand === command ? null : resolvedCommand,
|
|
@@ -6629,17 +6701,29 @@ const CHECK_FEEDBACK_TRIM_MARKER = 'output trimmed';
|
|
|
6629
6701
|
|
|
6630
6702
|
function extractCheckFeedback(verifierResult) {
|
|
6631
6703
|
if (!verifierResult || verifierResult.passed !== false) return '';
|
|
6632
|
-
|
|
6704
|
+
// A reviewer turn-down goes first: when the tests passed, it is the only
|
|
6705
|
+
// real reason the try failed, and passing test names must not crowd it out.
|
|
6706
|
+
const referee = verifierResult.referee && verifierResult.referee.passed === false
|
|
6707
|
+
? verifierResult.referee
|
|
6708
|
+
: null;
|
|
6709
|
+
const refereeLines = referee
|
|
6710
|
+
? [referee.reason, referee.output].filter((value) => String(value || '').trim())
|
|
6711
|
+
: [];
|
|
6712
|
+
const lines = [...new Set([...refereeLines, verifierResult.output, verifierResult.stdout, verifierResult.stderr]
|
|
6633
6713
|
.filter((value) => String(value || '').trim())
|
|
6634
6714
|
.flatMap((value) => String(value).split(/\r?\n/))
|
|
6635
6715
|
.map((line) => line.trim())
|
|
6636
|
-
.filter(Boolean);
|
|
6716
|
+
.filter(Boolean))];
|
|
6637
6717
|
if (!lines.length) return '';
|
|
6638
6718
|
|
|
6719
|
+
const refereeSet = new Set(refereeLines.flatMap((value) => String(value).split(/\r?\n/)).map((line) => line.trim()).filter(Boolean));
|
|
6720
|
+
const passingLine = /^(?:✔|ℹ )/;
|
|
6639
6721
|
const usefulPattern = /\b(?:error|fail|expected|assert)\w*\b|(?:^|\s|\()[^:\s()]+:\d+(?::\d+)?(?:\b|$)/i;
|
|
6640
|
-
const
|
|
6641
|
-
const
|
|
6642
|
-
const
|
|
6722
|
+
const rest = lines.filter((line) => !refereeSet.has(line));
|
|
6723
|
+
const useful = rest.filter((line) => !passingLine.test(line) && usefulPattern.test(line));
|
|
6724
|
+
const context = rest.filter((line) => !passingLine.test(line) && !usefulPattern.test(line));
|
|
6725
|
+
const passing = rest.filter((line) => passingLine.test(line));
|
|
6726
|
+
const feedback = [...lines.filter((line) => refereeSet.has(line)), ...useful, ...context, ...passing].join('\n');
|
|
6643
6727
|
if (feedback.length <= CHECK_FEEDBACK_LIMIT) return feedback;
|
|
6644
6728
|
|
|
6645
6729
|
const contentLimit = CHECK_FEEDBACK_LIMIT - CHECK_FEEDBACK_TRIM_MARKER.length - 1;
|
|
@@ -6878,7 +6962,10 @@ function worktreeReceipt(before, after, { verifier = '', baseline = null } = {})
|
|
|
6878
6962
|
const MISSION_RUN_DEFAULTS = {
|
|
6879
6963
|
maxTicks: 4,
|
|
6880
6964
|
maxWallSeconds: 3600,
|
|
6881
|
-
|
|
6965
|
+
// Sized from real runs: claude ticks that landed 10/08-10/14 ran a median
|
|
6966
|
+
// 4 min, but 15 of 92 needed more than 10 and the slowest 5% took 17+.
|
|
6967
|
+
// A 10 min cap killed 12 runs at exactly 10:00; 20 min fits 89 of 92.
|
|
6968
|
+
claudeTimeoutMs: 20 * 60 * 1000,
|
|
6882
6969
|
backoff: { initialMs: 30_000, maxMs: 10 * 60_000, factor: 2, jitter: 0.3 },
|
|
6883
6970
|
};
|
|
6884
6971
|
|
|
@@ -8865,6 +8952,45 @@ function consumeMissionPings(mission, cwd) {
|
|
|
8865
8952
|
return { mission: saved, pings: pending };
|
|
8866
8953
|
}
|
|
8867
8954
|
|
|
8955
|
+
// A one-tick job has no next prompt. Deliver notes that arrived while the
|
|
8956
|
+
// worker ran before checking its work, within the same wall budget. Only a
|
|
8957
|
+
// successful worker response acknowledges a batch; failed delivery can retry.
|
|
8958
|
+
async function deliverLateMissionPings(mission, cwd, firstResult, { run, remainingMs, prompt }) {
|
|
8959
|
+
let result = firstResult;
|
|
8960
|
+
let delivered = 0;
|
|
8961
|
+
while (result.ok) {
|
|
8962
|
+
const latest = resolveMission(mission.id, cwd) || mission;
|
|
8963
|
+
const pending = pendingMissionPings(latest);
|
|
8964
|
+
if (!pending.length || TERMINAL_STATUSES.has(latest.status)) break;
|
|
8965
|
+
if (remainingMs() <= 0) {
|
|
8966
|
+
return { ...result, ok: false, timedOut: true, error: 'unread notes at the wall limit' };
|
|
8967
|
+
}
|
|
8968
|
+
const next = await run([
|
|
8969
|
+
prompt,
|
|
8970
|
+
'',
|
|
8971
|
+
'## Notes received before this job finished',
|
|
8972
|
+
'This is the same work step. Read and apply these notes before returning the final receipt. Do not start a second slice.',
|
|
8973
|
+
...pending.map((p) => `- [${p.at}] ${p.from || 'operator'}: ${p.text}`),
|
|
8974
|
+
'',
|
|
8975
|
+
'Previous work receipt:',
|
|
8976
|
+
result.receipt_text || result.result || '',
|
|
8977
|
+
].join('\n'));
|
|
8978
|
+
if (!next.ok) return next;
|
|
8979
|
+
// Re-read so notes sent during delivery survive the acknowledgement.
|
|
8980
|
+
const fresh = resolveMission(mission.id, cwd) || latest;
|
|
8981
|
+
const keys = new Set(pending.map((p) => JSON.stringify([p.at, p.from, p.text])));
|
|
8982
|
+
const consumedAt = stampIso();
|
|
8983
|
+
saveMission({
|
|
8984
|
+
...fresh,
|
|
8985
|
+
pings: (fresh.pings || []).map((p) => p && !p.consumed_at && keys.has(JSON.stringify([p.at, p.from, p.text]))
|
|
8986
|
+
? { ...p, consumed_at: consumedAt } : p),
|
|
8987
|
+
}, cwd, 'mission_pings_consumed', { count: pending.length });
|
|
8988
|
+
delivered += pending.length;
|
|
8989
|
+
result = { ...next, operator_notes_read: delivered };
|
|
8990
|
+
}
|
|
8991
|
+
return result;
|
|
8992
|
+
}
|
|
8993
|
+
|
|
8868
8994
|
function readBoundedContextFile(filePath, maxChars = 1600) {
|
|
8869
8995
|
try {
|
|
8870
8996
|
if (!fs.existsSync(filePath)) return '';
|
|
@@ -9235,6 +9361,22 @@ function tooDeepRunnerResult(depth) {
|
|
|
9235
9361
|
};
|
|
9236
9362
|
}
|
|
9237
9363
|
|
|
9364
|
+
// Machine memory and load at both ends of a tick, like a dispatch, so a
|
|
9365
|
+
// killed tick still says whether the machine was short on memory. The
|
|
9366
|
+
// returned end writes once, whichever of close or error comes first.
|
|
9367
|
+
function startTickResource(cwd, engine, mission) {
|
|
9368
|
+
const { recordEngineResource } = require('../lib/engine-resource');
|
|
9369
|
+
const resource = { engine: engine || 'runner', task: mission.task_id || mission.id || '', run_id: crypto.randomUUID() };
|
|
9370
|
+
recordEngineResource(cwd, { phase: 'start', ...resource });
|
|
9371
|
+
const startedAt = process.hrtime.bigint();
|
|
9372
|
+
let ended = false;
|
|
9373
|
+
return ({ signal = null, timedOut = false, error = '' } = {}) => {
|
|
9374
|
+
if (ended) return;
|
|
9375
|
+
ended = true;
|
|
9376
|
+
recordEngineResource(cwd, { phase: 'end', ...resource, signal, timed_out: timedOut, error, duration_ms: Math.round(Number(process.hrtime.bigint() - startedAt) / 1e6) });
|
|
9377
|
+
};
|
|
9378
|
+
}
|
|
9379
|
+
|
|
9238
9380
|
function spawnGenericRunnerTick(mission, opts) {
|
|
9239
9381
|
const { sessionId, cwd, signal, timeoutMs, prompt, model, missionLock } = opts;
|
|
9240
9382
|
const dispatchDepth = missionDispatchDepth();
|
|
@@ -9293,6 +9435,7 @@ function spawnGenericRunnerTick(mission, opts) {
|
|
|
9293
9435
|
}
|
|
9294
9436
|
|
|
9295
9437
|
const startedAt = Date.now();
|
|
9438
|
+
const endResource = startTickResource(cwd, canonicalEngineName(mission.runner), mission);
|
|
9296
9439
|
const progressMember = mission.owner || 'mission';
|
|
9297
9440
|
// A codex run opens its shell script with the run marker as a no-op, so
|
|
9298
9441
|
// member stop can prove the shell leading this group is this run. Only
|
|
@@ -9366,8 +9509,10 @@ function spawnGenericRunnerTick(mission, opts) {
|
|
|
9366
9509
|
});
|
|
9367
9510
|
proc.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
|
9368
9511
|
|
|
9369
|
-
proc.on('close', (code) => {
|
|
9512
|
+
proc.on('close', (code, closeSignal) => {
|
|
9370
9513
|
clearTimeout(timer);
|
|
9514
|
+
// The shell returns 128 + 9 when its engine child was force-killed.
|
|
9515
|
+
endResource({ signal: closeSignal || (code === 137 ? 'SIGKILL' : null), timedOut, error: code === 0 ? '' : stderr.slice(-2000) });
|
|
9371
9516
|
if (lastMessageFile && eventBuf.trim()) readCodexEvent(eventBuf.trim());
|
|
9372
9517
|
let finalText = String(stdout || '').trim();
|
|
9373
9518
|
if (lastMessageFile) {
|
|
@@ -9405,6 +9550,7 @@ function spawnGenericRunnerTick(mission, opts) {
|
|
|
9405
9550
|
|
|
9406
9551
|
proc.on('error', (e) => {
|
|
9407
9552
|
clearTimeout(timer);
|
|
9553
|
+
endResource({ timedOut, error: e.message });
|
|
9408
9554
|
cleanupPrompt();
|
|
9409
9555
|
gitGuard.cleanup();
|
|
9410
9556
|
if (signal) signal.removeEventListener?.('abort', onAbort);
|
|
@@ -9489,6 +9635,7 @@ function spawnClaudeTick(mission, opts) {
|
|
|
9489
9635
|
// detached: the runner spawns its own children; killing only the direct
|
|
9490
9636
|
// child on timeout leaves them holding the session lock, and the next
|
|
9491
9637
|
// tick's resume fails with "Session ID ... is already in use".
|
|
9638
|
+
const endResource = startTickResource(cwd, 'claude', mission);
|
|
9492
9639
|
const proc = spawn(resolveClaudeRunnerBin(), args, {
|
|
9493
9640
|
cwd,
|
|
9494
9641
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -9567,8 +9714,9 @@ function spawnClaudeTick(mission, opts) {
|
|
|
9567
9714
|
|
|
9568
9715
|
proc.stderr.on('data', (c) => { stderr += c.toString(); });
|
|
9569
9716
|
|
|
9570
|
-
proc.on('close', (code) => {
|
|
9717
|
+
proc.on('close', (code, closeSignal) => {
|
|
9571
9718
|
clearTimeout(timer);
|
|
9719
|
+
endResource({ signal: closeSignal, timedOut, error: code === 0 ? '' : stderr.slice(-2000) });
|
|
9572
9720
|
gitGuard.cleanup();
|
|
9573
9721
|
if (signal) signal.removeEventListener?.('abort', onAbort);
|
|
9574
9722
|
updateMissionLockOwner(missionLock, missionLock?.driverPid);
|
|
@@ -9601,6 +9749,7 @@ function spawnClaudeTick(mission, opts) {
|
|
|
9601
9749
|
|
|
9602
9750
|
proc.on('error', (e) => {
|
|
9603
9751
|
clearTimeout(timer);
|
|
9752
|
+
endResource({ timedOut, error: e.message });
|
|
9604
9753
|
gitGuard.cleanup();
|
|
9605
9754
|
updateMissionLockOwner(missionLock, missionLock?.driverPid);
|
|
9606
9755
|
resolve({ ok: false, error: e.message, sessionIds: [], aborted, timedOut, authExpired: false });
|
|
@@ -9611,14 +9760,42 @@ function spawnClaudeTick(mission, opts) {
|
|
|
9611
9760
|
const ENGINE_VERIFY_TIMEOUT_MS = 5 * 60 * 1000;
|
|
9612
9761
|
const ENGINE_VERIFY_OUTPUT_LIMIT = 4000;
|
|
9613
9762
|
|
|
9614
|
-
|
|
9763
|
+
// The job's own done-looks-like line, so the reviewer judges the work against
|
|
9764
|
+
// what the job promised instead of the mission headline alone. Only a line the
|
|
9765
|
+
// job wrote down counts: a derived fallback ("it clears review") hands the
|
|
9766
|
+
// reviewer a target that says nothing. Any lookup failure means no line.
|
|
9767
|
+
function missionDoneLooksLike(mission, root = process.cwd()) {
|
|
9768
|
+
try {
|
|
9769
|
+
const task = findOpenTaskMatchingMissionObjective(mission, root, true);
|
|
9770
|
+
if (!task) return '';
|
|
9771
|
+
const explanation = require('../lib/task-explanation').taskExplanation(task);
|
|
9772
|
+
return explanation.sources.done_looks_like === 'explicit' ? explanation.done_looks_like : '';
|
|
9773
|
+
} catch {
|
|
9774
|
+
return '';
|
|
9775
|
+
}
|
|
9776
|
+
}
|
|
9777
|
+
|
|
9778
|
+
function buildEngineVerifyPrompt(mission, tickIndex, doneLooksLike = '', { timeoutMs = ENGINE_VERIFY_TIMEOUT_MS, checkPassed = '' } = {}) {
|
|
9779
|
+
const doneLine = String(doneLooksLike || '').replace(/\s+/g, ' ').trim();
|
|
9780
|
+
const minutes = Math.max(1, Math.round(timeoutMs / 60000));
|
|
9615
9781
|
return [
|
|
9616
9782
|
`# Verify Mission Tick ${tickIndex}`,
|
|
9617
9783
|
``,
|
|
9618
9784
|
`This is a bounded verify-only pass for mission: ${mission.objective}`,
|
|
9785
|
+
...(doneLine ? [
|
|
9786
|
+
`The job says done looks like: ${doneLine}`,
|
|
9787
|
+
`Compare the work against that line. If the work does not show it, the verdict is FAIL even when the checks pass.`,
|
|
9788
|
+
`Split that line into each separate thing it promises and check every one. List each part as met or missing.`,
|
|
9789
|
+
`Work that does only some of the parts is not done. If any part is missing, end with 'VERDICT: FAIL; missing: <that part>'.`,
|
|
9790
|
+
] : []),
|
|
9619
9791
|
`Read the diff this tick produced. Do not edit files and do not start another work slice.`,
|
|
9620
9792
|
`ACTUALLY RUN the changed surface on this computer using real commands and real exit codes.`,
|
|
9621
9793
|
`Paste the commands and their real output. Do not accept a receipt claim or static inspection as execution proof.`,
|
|
9794
|
+
// Reviews that outran the clock were 11 of 25 turn-downs (10/11 to 10/14).
|
|
9795
|
+
`You have ${minutes} minutes. Leave time to write the verdict; running out of time counts as a fail.`,
|
|
9796
|
+
...(checkPassed ? [`The mission check \`${checkPassed}\` already passed on this exact code. Do not run it again.`] : []),
|
|
9797
|
+
`Prove the change with the narrowest real run: the changed command, or one test file or one named test.`,
|
|
9798
|
+
`Do not run whole test suites or large test files; on a busy machine they outlast your time.`,
|
|
9622
9799
|
`End your reply with a final line exactly 'VERDICT: PASS' or 'VERDICT: FAIL'.`,
|
|
9623
9800
|
].join('\n');
|
|
9624
9801
|
}
|
|
@@ -9680,7 +9857,10 @@ async function runEngineVerifier(mission, options = {}) {
|
|
|
9680
9857
|
cwd,
|
|
9681
9858
|
signal: options.signal,
|
|
9682
9859
|
timeoutMs: ENGINE_VERIFY_TIMEOUT_MS,
|
|
9683
|
-
prompt: buildEngineVerifyPrompt(mission, options.tickIndex || 1),
|
|
9860
|
+
prompt: buildEngineVerifyPrompt(mission, options.tickIndex || 1, missionDoneLooksLike(mission, cwd), {
|
|
9861
|
+
timeoutMs: ENGINE_VERIFY_TIMEOUT_MS,
|
|
9862
|
+
checkPassed: options.checkPassed || '',
|
|
9863
|
+
}),
|
|
9684
9864
|
model: resolveMissionTickRunnerModel(verifyMission),
|
|
9685
9865
|
});
|
|
9686
9866
|
} catch (error) {
|
|
@@ -10139,6 +10319,9 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10139
10319
|
let cycleTickLimit = effectiveMaxTicks;
|
|
10140
10320
|
while (true) {
|
|
10141
10321
|
while (ticks.length < cycleTickLimit) {
|
|
10322
|
+
// A campaign that is done, paused, or out of money stops its missions.
|
|
10323
|
+
const campaignStop = mission.campaign_id ? campaignMissions().campaignGate(cwd, mission) : null;
|
|
10324
|
+
if (campaignStop) { pauseReason = campaignStop; break; }
|
|
10142
10325
|
// Re-read before measuring the wall. Detached full-budget drivers use
|
|
10143
10326
|
// the mission contract as their wall so process startup drift cannot
|
|
10144
10327
|
// shorten the promised work window.
|
|
@@ -10278,8 +10461,8 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10278
10461
|
}, Math.max(1, Math.floor(remainingWall * 1000)));
|
|
10279
10462
|
let turn;
|
|
10280
10463
|
try {
|
|
10281
|
-
|
|
10282
|
-
prompt,
|
|
10464
|
+
const runTurn = (direction) => runAtris2Turn({
|
|
10465
|
+
prompt: direction,
|
|
10283
10466
|
model: tickRuntimeMission.model || 'atris:fast',
|
|
10284
10467
|
business: businessId,
|
|
10285
10468
|
maxTurns: 16,
|
|
@@ -10290,6 +10473,16 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10290
10473
|
localEnv: atris2GitGuard.env,
|
|
10291
10474
|
localPathPrefix: atris2GitGuard.pathPrefix,
|
|
10292
10475
|
});
|
|
10476
|
+
turn = await runTurn(prompt);
|
|
10477
|
+
turn = await deliverLateMissionPings(mission, cwd, { ...turn, receipt_text: turn.text }, {
|
|
10478
|
+
prompt,
|
|
10479
|
+
remainingMs: () => (maxWallSeconds * 1000) - (Date.now() - startedAt),
|
|
10480
|
+
run: async (direction) => {
|
|
10481
|
+
const next = await runTurn(direction);
|
|
10482
|
+
return { ...next, receipt_text: next.text };
|
|
10483
|
+
},
|
|
10484
|
+
});
|
|
10485
|
+
mission = resolveMission(mission.id, cwd) || mission;
|
|
10293
10486
|
} finally {
|
|
10294
10487
|
atris2GitGuard.cleanup();
|
|
10295
10488
|
clearTimeout(tickTimer);
|
|
@@ -10305,6 +10498,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10305
10498
|
error: turn.error,
|
|
10306
10499
|
backend_unavailable: isTransientAtris2BackendError(turn.error) || undefined,
|
|
10307
10500
|
receipt_text: String(turn.text || '').slice(0, 4000),
|
|
10501
|
+
operator_notes_read: turn.operator_notes_read,
|
|
10308
10502
|
};
|
|
10309
10503
|
tickWorkerRun = { endedMs: Date.now(), usage: {} };
|
|
10310
10504
|
if (controller.signal.aborted) { pauseReason = 'aborted-during-atris2'; break; }
|
|
@@ -10338,14 +10532,14 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10338
10532
|
const restoreTickRunnerProfile = tickEngineId ? applyMissionRunnerProfile(tickEngineId) : () => {};
|
|
10339
10533
|
let claudeResult;
|
|
10340
10534
|
let sessionBusyRetried = false;
|
|
10341
|
-
const runClaudeSession = () => spawnClaudeTick(tickRuntimeMission, {
|
|
10535
|
+
const runClaudeSession = (direction = workerPrompt) => spawnClaudeTick(tickRuntimeMission, {
|
|
10342
10536
|
sessionMode, sessionId: useId, cwd, signal: controller.signal,
|
|
10343
10537
|
missionLock: lock,
|
|
10344
10538
|
timeoutMs: missionTickTimeoutMs(
|
|
10345
10539
|
tickRuntimeMission,
|
|
10346
10540
|
(maxWallSeconds - ((Date.now() - startedAt) / 1000)) * 1000,
|
|
10347
10541
|
),
|
|
10348
|
-
prompt:
|
|
10542
|
+
prompt: direction,
|
|
10349
10543
|
model: resolveMissionTickRunnerModel(tickRuntimeMission),
|
|
10350
10544
|
});
|
|
10351
10545
|
try {
|
|
@@ -10362,12 +10556,24 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10362
10556
|
useId = pendingSessionId;
|
|
10363
10557
|
claudeResult = await runClaudeSession();
|
|
10364
10558
|
}
|
|
10559
|
+
claudeResult = await deliverLateMissionPings(mission, cwd, claudeResult, {
|
|
10560
|
+
prompt: workerPrompt,
|
|
10561
|
+
remainingMs: () => (maxWallSeconds * 1000) - (Date.now() - startedAt),
|
|
10562
|
+
run: (direction) => {
|
|
10563
|
+
// Claude needs to resume the session it just confirmed. Generic
|
|
10564
|
+
// runners receive the earlier receipt in the delivery prompt.
|
|
10565
|
+
if (claudeResult.sessionIds?.includes(useId)) sessionMode = 'resume';
|
|
10566
|
+
return runClaudeSession(direction);
|
|
10567
|
+
},
|
|
10568
|
+
});
|
|
10569
|
+
mission = resolveMission(mission.id, cwd) || mission;
|
|
10365
10570
|
} finally {
|
|
10366
10571
|
restoreTickRunnerProfile();
|
|
10367
10572
|
}
|
|
10368
10573
|
tickWorkerRun = { endedMs: Date.now(), usage: (claudeResult && claudeResult.run_usage) || {}, prep: tickPrep, startedAt: workerStartedAt };
|
|
10369
10574
|
result.claude = {
|
|
10370
10575
|
ok: claudeResult.ok,
|
|
10576
|
+
error: claudeResult.error,
|
|
10371
10577
|
reason: claudeResult.reason,
|
|
10372
10578
|
protected_lane_guard: claudeResult.protected_lane_guard || null,
|
|
10373
10579
|
brief_id: claudeResult.brief_id || null,
|
|
@@ -10384,6 +10590,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10384
10590
|
timed_out: claudeResult.timedOut,
|
|
10385
10591
|
aborted: claudeResult.aborted,
|
|
10386
10592
|
session_busy_retry: sessionBusyRetried || undefined,
|
|
10593
|
+
operator_notes_read: claudeResult.operator_notes_read,
|
|
10387
10594
|
};
|
|
10388
10595
|
if (claudeResult.rate_limit_info) {
|
|
10389
10596
|
// Claude reports the five-hour window's resetsAt on every turn, even
|
|
@@ -10398,7 +10605,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10398
10605
|
|
|
10399
10606
|
if (!claudeResult.ok) {
|
|
10400
10607
|
const deadModel = detectUnavailableModel(claudeResult.summary || claudeResult.receipt_text);
|
|
10401
|
-
const wallBoundTimeout = remainingWall * 1000
|
|
10608
|
+
const wallBoundTimeout = missionTickWallBound(tickRuntimeMission, remainingWall * 1000);
|
|
10402
10609
|
let reason = claudeResult.reason === 'too-deep'
|
|
10403
10610
|
? 'too-deep'
|
|
10404
10611
|
: claudeResult.timedOut
|
|
@@ -10488,6 +10695,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10488
10695
|
prep: tickWorkerRun.prep || null,
|
|
10489
10696
|
});
|
|
10490
10697
|
}
|
|
10698
|
+
if (mission.campaign_id && !cachedStep) campaignMissions().recordCampaignTick(cwd, mission, { ...((tickWorkerRun && tickWorkerRun.usage) || {}), ...(tickEngineId ? { engine: tickEngineId } : {}) });
|
|
10491
10699
|
let receiptPath = cachedStep ? cachedStep.receipt_path : null;
|
|
10492
10700
|
|
|
10493
10701
|
// Review-lane drain: always-on loops sweep the agent-safe review actions
|
|
@@ -10635,7 +10843,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
10635
10843
|
mission: mission.objective,
|
|
10636
10844
|
state: mission.status,
|
|
10637
10845
|
tick_status: result.status,
|
|
10638
|
-
reason: result
|
|
10846
|
+
reason: memberLogTickReason(result),
|
|
10639
10847
|
layer: result.layer || undefined,
|
|
10640
10848
|
verifier: verifierResult ? (verifierResult.passed ? 'passed' : 'failed') : 'not_run',
|
|
10641
10849
|
summary: tickSummaryText ? tickSummaryText.slice(0, 240) : undefined,
|
|
@@ -10849,6 +11057,7 @@ async function verifyMissionRunTickPhase(context) {
|
|
|
10849
11057
|
signal: controller.signal,
|
|
10850
11058
|
tickIndex: currentTick.tickIdx,
|
|
10851
11059
|
refereeEngine: context.refereeEngine,
|
|
11060
|
+
checkPassed: verifierResult?.passed ? currentTick.frozen.verifier : '',
|
|
10852
11061
|
});
|
|
10853
11062
|
const afterReview = gitCodeState(cwd);
|
|
10854
11063
|
const codeMoved = beforeReview.tree !== afterReview.tree
|
|
@@ -12493,6 +12702,7 @@ function missionCommand(args) {
|
|
|
12493
12702
|
}
|
|
12494
12703
|
|
|
12495
12704
|
module.exports = {
|
|
12705
|
+
listSiblingWorkspaceMissionHints,
|
|
12496
12706
|
missionProtectedLaneHold,
|
|
12497
12707
|
missionCommand,
|
|
12498
12708
|
startMission,
|
|
@@ -12545,6 +12755,7 @@ module.exports = {
|
|
|
12545
12755
|
missionVerifierCheckedText,
|
|
12546
12756
|
missionVerifierHighLevelTestText,
|
|
12547
12757
|
buildEngineVerifyPrompt,
|
|
12758
|
+
missionDoneLooksLike,
|
|
12548
12759
|
engineVerifierResultFromRun,
|
|
12549
12760
|
pickCrossEngineVerifier,
|
|
12550
12761
|
runVerifier,
|
|
@@ -12555,6 +12766,8 @@ module.exports = {
|
|
|
12555
12766
|
resolveMissionTickRunner,
|
|
12556
12767
|
resolveMissionTickRunnerModel,
|
|
12557
12768
|
missionTickTimeoutMs,
|
|
12769
|
+
missionTickWallBound,
|
|
12770
|
+
memberLogTickReason,
|
|
12558
12771
|
engineFailureHealthStatus,
|
|
12559
12772
|
recordMissionEngineTickOutcome,
|
|
12560
12773
|
recordMissionTickRosterRun,
|