atris 3.68.4 → 3.68.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/commands/member.js +7 -2
- package/commands/mission.js +13 -0
- package/lib/engine-registry.js +15 -96
- package/lib/member-progress.js +82 -0
- package/lib/model-names.js +116 -0
- package/lib/runner-command.js +11 -10
- package/package.json +1 -1
package/commands/member.js
CHANGED
|
@@ -10499,16 +10499,21 @@ async function memberGo(...rawArgs) {
|
|
|
10499
10499
|
const root = process.cwd();
|
|
10500
10500
|
const teamDir = path.join(root, 'atris', 'team');
|
|
10501
10501
|
const coreOrder = ['auto-improver', 'executor', 'navigator', 'validator', 'architect', 'customer-lead', 'researcher'];
|
|
10502
|
-
let chosen = '
|
|
10502
|
+
let chosen = '';
|
|
10503
10503
|
for (const slug of coreOrder) {
|
|
10504
10504
|
const mDir = path.join(teamDir, slug);
|
|
10505
10505
|
if (!fs.existsSync(path.join(mDir, 'MEMBER.md'))) continue;
|
|
10506
|
-
|
|
10506
|
+
if (!chosen) chosen = slug;
|
|
10507
|
+
const gp = memberGoalPlaneStatus(slug, { memberDir: mDir, goalsJson: path.join(mDir, 'goals.json') });
|
|
10507
10508
|
if (gp?.goal?.next_push || gp?.goal?.title) {
|
|
10508
10509
|
chosen = slug;
|
|
10509
10510
|
break;
|
|
10510
10511
|
}
|
|
10511
10512
|
}
|
|
10513
|
+
if (!chosen) {
|
|
10514
|
+
const fallback = findAllMembers(teamDir).filter(isFleetMember)[0];
|
|
10515
|
+
if (fallback) chosen = memberSlugFromRecord(fallback);
|
|
10516
|
+
}
|
|
10512
10517
|
return memberSteerOrDispatch(chosen, ...rawArgs);
|
|
10513
10518
|
}
|
|
10514
10519
|
|
package/commands/mission.js
CHANGED
|
@@ -8,6 +8,7 @@ const { spawn, spawnSync } = require('child_process');
|
|
|
8
8
|
const { hasFlag, readFlag, readIntFlag } = require('../lib/arg-parser');
|
|
9
9
|
const { memberProcessPrompt } = require('../lib/member-context');
|
|
10
10
|
const { memberIdentityLine, missionObjectiveLine } = require('../lib/member-identity');
|
|
11
|
+
const { formatToolStep, appendProgress, reportProgress } = require('../lib/member-progress');
|
|
11
12
|
const {
|
|
12
13
|
compactErrorPayload,
|
|
13
14
|
compactSuccessPayload,
|
|
@@ -9377,6 +9378,9 @@ function spawnClaudeTick(mission, opts) {
|
|
|
9377
9378
|
env: { ...gitGuard.env, ATRIS_DISPATCH_DEPTH: String(dispatchDepth.depth + 1) },
|
|
9378
9379
|
});
|
|
9379
9380
|
updateMissionLockOwner(missionLock, proc.pid, { phase: 'tick' });
|
|
9381
|
+
const progressMember = mission.owner || 'mission';
|
|
9382
|
+
const seenToolIds = new Set();
|
|
9383
|
+
appendProgress(progressMember, `started: ${mission.objective || ''}`, mission.id);
|
|
9380
9384
|
|
|
9381
9385
|
let stdoutBuf = '';
|
|
9382
9386
|
let observedSessionIds = new Set();
|
|
@@ -9417,6 +9421,14 @@ function spawnClaudeTick(mission, opts) {
|
|
|
9417
9421
|
try {
|
|
9418
9422
|
const ev = JSON.parse(line);
|
|
9419
9423
|
if (ev.session_id) observedSessionIds.add(ev.session_id);
|
|
9424
|
+
if (ev.type === 'assistant' && Array.isArray(ev.message?.content)) {
|
|
9425
|
+
for (const block of ev.message.content) {
|
|
9426
|
+
const step = formatToolStep(block, cwd);
|
|
9427
|
+
if (!step || seenToolIds.has(block.id)) continue;
|
|
9428
|
+
if (block.id) seenToolIds.add(block.id);
|
|
9429
|
+
reportProgress(progressMember, step, mission.id);
|
|
9430
|
+
}
|
|
9431
|
+
}
|
|
9420
9432
|
if (ev.type === 'rate_limit_event' && ev.rate_limit_info) {
|
|
9421
9433
|
rateLimitInfo = ev.rate_limit_info;
|
|
9422
9434
|
}
|
|
@@ -9443,6 +9455,7 @@ function spawnClaudeTick(mission, opts) {
|
|
|
9443
9455
|
if (signal) signal.removeEventListener?.('abort', onAbort);
|
|
9444
9456
|
updateMissionLockOwner(missionLock, missionLock?.driverPid);
|
|
9445
9457
|
const ok = code === 0 && !isError && !timedOut && !aborted;
|
|
9458
|
+
appendProgress(progressMember, `finished: ${ok ? 'ok' : 'failed'}`, mission.id);
|
|
9446
9459
|
const errStr = stderr.slice(-2000);
|
|
9447
9460
|
const authExpired = !ok && missionRunnerAuthExpired(errStr, finalText, stdoutBuf);
|
|
9448
9461
|
resolve({
|
package/lib/engine-registry.js
CHANGED
|
@@ -15,6 +15,21 @@ const {
|
|
|
15
15
|
engineTakesModel,
|
|
16
16
|
engineEffortLevels,
|
|
17
17
|
} = require('./runner-command');
|
|
18
|
+
// The one model-name table, shared with runner-command: friendly spellings
|
|
19
|
+
// to the ids each cli accepts, plus retired-model upgrades.
|
|
20
|
+
const {
|
|
21
|
+
CLAUDE_FAMILY_ENGINES,
|
|
22
|
+
CLAUDE_MODEL_ALIASES,
|
|
23
|
+
RETIRED_CLAUDE_MODELS,
|
|
24
|
+
CLAUDE_MODEL_SPELLING,
|
|
25
|
+
CLAUDE_LONG_CONTEXT,
|
|
26
|
+
GROK_MODEL_SPELLING,
|
|
27
|
+
currentClaudeModel,
|
|
28
|
+
claudeModelId,
|
|
29
|
+
retiredModelsNamed,
|
|
30
|
+
normalizeRosterModel,
|
|
31
|
+
savedRosterModel,
|
|
32
|
+
} = require('./model-names');
|
|
18
33
|
// Through the module object so tests can see when ranking runs.
|
|
19
34
|
const routerBrain = require('./router-brain');
|
|
20
35
|
const { routerPickExplanation } = routerBrain;
|
|
@@ -584,102 +599,6 @@ function rosterPickExpired(pick, now = new Date()) {
|
|
|
584
599
|
return localDay(rosterDate(now)).getTime() > until.getTime();
|
|
585
600
|
}
|
|
586
601
|
|
|
587
|
-
// Claude-family engines run through the claude CLI, which rejects loose
|
|
588
|
-
// spellings like "opus-5.5". Assign saves the id the CLI accepts, and every
|
|
589
|
-
// read normalizes again, so older saved picks and hand edits work too.
|
|
590
|
-
const CLAUDE_FAMILY_ENGINES = Object.freeze(['claude', 'fable', 'haiku']);
|
|
591
|
-
const CLAUDE_MODEL_ALIASES = Object.freeze(['opus', 'sonnet', 'haiku', 'fable', 'opusplan', 'default']);
|
|
592
|
-
// A retired model runs as its replacement, so a saved pick keeps working
|
|
593
|
-
// after the upgrade. Retiring the next one is one line here.
|
|
594
|
-
const RETIRED_CLAUDE_MODELS = Object.freeze({
|
|
595
|
-
'claude-fable-5': 'claude-fable-5-1',
|
|
596
|
-
'claude-haiku-4-5': 'claude-haiku-5-5',
|
|
597
|
-
'claude-haiku-4-5-20251001': 'claude-haiku-5-5',
|
|
598
|
-
});
|
|
599
|
-
const CLAUDE_MODEL_SPELLING = /^(opus|sonnet|haiku|fable)[\s_-]*(\d+)(?:[\s._-]+(\d+))?$/;
|
|
600
|
-
const CLAUDE_LONG_CONTEXT = /\s*\[1m\]$/;
|
|
601
|
-
|
|
602
|
-
// Grok takes friendly names too: "grok 4.7 fast" is grok-4.7-build-fast and
|
|
603
|
-
// "grok 4.7" is grok-4.7. Any full grok- id passes through as typed.
|
|
604
|
-
const GROK_MODEL_SPELLING = /^grok[\s_-]*(\d+(?:\.\d+)?)(?:[\s_-]+(?:build[\s_-]+)?(fast))?$/;
|
|
605
|
-
|
|
606
|
-
function normalizeGrokModel(raw) {
|
|
607
|
-
const match = GROK_MODEL_SPELLING.exec(raw.toLowerCase());
|
|
608
|
-
if (match) return `grok-${match[1]}${match[2] ? '-build-fast' : ''}`;
|
|
609
|
-
if (/^grok-[a-z0-9][a-z0-9._-]*$/i.test(raw)) return raw;
|
|
610
|
-
throw new Error(`grok does not know the model "${raw}". use grok 4.7 fast, grok 4.7, or a full grok- id`);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// The model to run for a claude id: its replacement when it is retired,
|
|
614
|
-
// else the id as given. Keeps a "[1m]" tail.
|
|
615
|
-
function currentClaudeModel(value) {
|
|
616
|
-
const raw = String(value || '').trim();
|
|
617
|
-
const suffix = CLAUDE_LONG_CONTEXT.test(raw) ? '[1m]' : '';
|
|
618
|
-
const base = raw.replace(CLAUDE_LONG_CONTEXT, '').trim();
|
|
619
|
-
const next = RETIRED_CLAUDE_MODELS[base.toLowerCase()];
|
|
620
|
-
return next ? `${next}${suffix}` : raw;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
// The id the claude cli accepts for a display name typed with a space:
|
|
624
|
-
// "opus 5.5" is claude-opus-5-5, "fable 5.1" is claude-fable-5-1. A full id,
|
|
625
|
-
// a bare alias ("opus"), a "[1m]" tail and anything else pass through as given
|
|
626
|
-
// (a retired id still becomes its replacement).
|
|
627
|
-
const CLAUDE_DISPLAY_NAME = /^(opus|sonnet|haiku|fable)\s+(\d+)(?:\.(\d+))?$/i;
|
|
628
|
-
|
|
629
|
-
function claudeModelId(value) {
|
|
630
|
-
const raw = String(value || '').trim();
|
|
631
|
-
const suffix = CLAUDE_LONG_CONTEXT.test(raw) ? '[1m]' : '';
|
|
632
|
-
const base = raw.replace(CLAUDE_LONG_CONTEXT, '').trim();
|
|
633
|
-
const match = CLAUDE_DISPLAY_NAME.exec(base);
|
|
634
|
-
if (!match) return currentClaudeModel(raw);
|
|
635
|
-
return currentClaudeModel(`claude-${match[1].toLowerCase()}-${match[2]}${match[3] ? `-${match[3]}` : ''}${suffix}`);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// The retired models a roster file's text still names, by id or spelled
|
|
639
|
-
// out, each once, so the roster can say they were upgraded.
|
|
640
|
-
function retiredModelsNamed(text) {
|
|
641
|
-
const found = [];
|
|
642
|
-
for (const id of Object.keys(RETIRED_CLAUDE_MODELS)) {
|
|
643
|
-
const [, family, major, minor] = /^claude-([a-z]+)-(\d+)-(\d+)/.exec(id) || [];
|
|
644
|
-
if (!family) continue;
|
|
645
|
-
const spelled = new RegExp(`\\b(?:claude[\\s_-]*)?${family}[\\s_-]*${major}[\\s._-]+${minor}(?![\\d.])`, 'i');
|
|
646
|
-
const base = `claude-${family}-${major}-${minor}`;
|
|
647
|
-
if (spelled.test(String(text || '')) && !found.includes(base)) found.push(base);
|
|
648
|
-
}
|
|
649
|
-
return found;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
function normalizeRosterModel(engineId, value) {
|
|
653
|
-
const raw = String(value || '').trim();
|
|
654
|
-
if (!raw) return '';
|
|
655
|
-
if (engineId === 'grok') return normalizeGrokModel(raw);
|
|
656
|
-
if (!CLAUDE_FAMILY_ENGINES.includes(engineId)) return raw;
|
|
657
|
-
// "[1m]" asks the claude cli for the long context window; keep it on
|
|
658
|
-
// whatever name it follows.
|
|
659
|
-
const suffix = CLAUDE_LONG_CONTEXT.test(raw) ? '[1m]' : '';
|
|
660
|
-
const base = raw.replace(CLAUDE_LONG_CONTEXT, '').trim();
|
|
661
|
-
const lower = base.toLowerCase();
|
|
662
|
-
if (CLAUDE_MODEL_ALIASES.includes(lower)) return `${lower}${suffix}`;
|
|
663
|
-
const match = CLAUDE_MODEL_SPELLING.exec(lower.replace(/^claude[\s_-]*/, ''));
|
|
664
|
-
if (match) {
|
|
665
|
-
const id = `claude-${match[1]}-${match[2]}${match[3] ? `-${match[3]}` : ''}`;
|
|
666
|
-
return currentClaudeModel(`${id}${suffix}`);
|
|
667
|
-
}
|
|
668
|
-
if (lower.startsWith('claude-')) return currentClaudeModel(`${base}${suffix}`);
|
|
669
|
-
throw new Error(`${engineId} does not know the model "${raw}". use opus, sonnet, haiku, fable, opusplan, default, a version like opus 5.5, sonnet 5, haiku 5.5, or fable 5.1, or a full claude- id; add [1m] for the long context window`);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// A saved pick's model, normalized the same way assign does. null means the
|
|
673
|
-
// saved name is one the claude cli would reject, so the pick cannot run.
|
|
674
|
-
function savedRosterModel(engineId, value) {
|
|
675
|
-
try {
|
|
676
|
-
return normalizeRosterModel(engineId, value);
|
|
677
|
-
} catch {
|
|
678
|
-
return null;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
|
|
683
602
|
// A registry saved before an engine learned a job keeps its old role list;
|
|
684
603
|
// the roster still honors the job the engine can do today, including a job
|
|
685
604
|
// it only takes by roster pick.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Live progress for a working member: one plain line per tool step, printed to
|
|
4
|
+
// stderr and appended to <dir>/<member>.log so another shell can tail it.
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
function realish(p) {
|
|
11
|
+
try { return fs.realpathSync(p); } catch {}
|
|
12
|
+
try { return path.join(fs.realpathSync(path.dirname(p)), path.basename(p)); } catch {}
|
|
13
|
+
return p;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function insideRel(cwd, p) {
|
|
17
|
+
const rel = path.relative(cwd, p);
|
|
18
|
+
return rel && !rel.startsWith('..') && !path.isAbsolute(rel) ? rel : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Relative to cwd when inside it; symlinked roots (macOS /var vs /private/var)
|
|
22
|
+
// still count as inside.
|
|
23
|
+
function relativeTo(cwd, file) {
|
|
24
|
+
const p = String(file || '');
|
|
25
|
+
if (!p || !cwd || !path.isAbsolute(p)) return p;
|
|
26
|
+
return insideRel(cwd, p) || insideRel(realish(cwd), realish(p)) || p;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function formatToolStep(block, cwd) {
|
|
30
|
+
if (!block || block.type !== 'tool_use') return null;
|
|
31
|
+
const name = String(block.name || 'tool');
|
|
32
|
+
const input = block.input || {};
|
|
33
|
+
switch (name) {
|
|
34
|
+
case 'Read':
|
|
35
|
+
return `reading ${relativeTo(cwd, input.file_path)}`;
|
|
36
|
+
case 'Edit':
|
|
37
|
+
case 'MultiEdit':
|
|
38
|
+
case 'Write':
|
|
39
|
+
return `editing ${relativeTo(cwd, input.file_path)}`;
|
|
40
|
+
case 'Bash': {
|
|
41
|
+
const first = String(input.command || '').split('\n')[0].trim();
|
|
42
|
+
return `running: ${first.slice(0, 70)}`;
|
|
43
|
+
}
|
|
44
|
+
case 'Grep':
|
|
45
|
+
return `searching for ${input.pattern || ''}`;
|
|
46
|
+
case 'Glob':
|
|
47
|
+
return `finding ${input.pattern || ''}`;
|
|
48
|
+
default:
|
|
49
|
+
return name.toLowerCase();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function progressDir() {
|
|
54
|
+
return process.env.ATRIS_PROGRESS_DIR || path.join(os.homedir(), '.atris', 'progress');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function safeMember(member) {
|
|
58
|
+
return String(member || 'mission').replace(/[^A-Za-z0-9._-]/g, '_') || 'mission';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function hhmm(date = new Date()) {
|
|
62
|
+
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function appendProgress(member, line, missionId) {
|
|
66
|
+
// Test runs never write into the real home folder unless they name a dir.
|
|
67
|
+
if (!process.env.ATRIS_PROGRESS_DIR && process.env.NODE_TEST_CONTEXT) return;
|
|
68
|
+
try {
|
|
69
|
+
const dir = progressDir();
|
|
70
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
71
|
+
const entry = `${new Date().toISOString()} ${missionId || '-'} ${line}\n`;
|
|
72
|
+
fs.appendFileSync(path.join(dir, `${safeMember(member)}.log`), entry, 'utf8');
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Print one step for the operator and keep it in the member's progress log.
|
|
77
|
+
function reportProgress(member, line, missionId) {
|
|
78
|
+
try { process.stderr.write(` ${member || 'mission'} ${hhmm()} ${line}\n`); } catch {}
|
|
79
|
+
appendProgress(member, line, missionId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = { formatToolStep, appendProgress, reportProgress };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Friendly model names to the ids each engine's cli accepts. This is the ONE
|
|
4
|
+
// table of spellings: engine-registry checks roster picks with it and
|
|
5
|
+
// runner-command normalizes launch models with it. "opus 5.5" is
|
|
6
|
+
// claude-opus-5-5, "grok 4.7 fast" is grok-4.7-build-fast. Kept free of
|
|
7
|
+
// requires so both files can load it without a circular dependency.
|
|
8
|
+
|
|
9
|
+
// Claude-family engines run through the claude CLI, which rejects loose
|
|
10
|
+
// spellings like "opus-5.5". Assign saves the id the CLI accepts, and every
|
|
11
|
+
// read normalizes again, so older saved picks and hand edits work too.
|
|
12
|
+
const CLAUDE_FAMILY_ENGINES = Object.freeze(['claude', 'fable', 'haiku']);
|
|
13
|
+
const CLAUDE_MODEL_ALIASES = Object.freeze(['opus', 'sonnet', 'haiku', 'fable', 'opusplan', 'default']);
|
|
14
|
+
// A retired model runs as its replacement, so a saved pick keeps working
|
|
15
|
+
// after the upgrade. Retiring the next one is one line here.
|
|
16
|
+
const RETIRED_CLAUDE_MODELS = Object.freeze({
|
|
17
|
+
'claude-fable-5': 'claude-fable-5-1',
|
|
18
|
+
'claude-haiku-4-5': 'claude-haiku-5-5',
|
|
19
|
+
'claude-haiku-4-5-20251001': 'claude-haiku-5-5',
|
|
20
|
+
});
|
|
21
|
+
const CLAUDE_MODEL_SPELLING = /^(opus|sonnet|haiku|fable)[\s_-]*(\d+)(?:[\s._-]+(\d+))?$/;
|
|
22
|
+
const CLAUDE_LONG_CONTEXT = /\s*\[1m\]$/;
|
|
23
|
+
|
|
24
|
+
// Grok takes friendly names too: "grok 4.7 fast" is grok-4.7-build-fast and
|
|
25
|
+
// "grok 4.7" is grok-4.7. Any full grok- id passes through as typed.
|
|
26
|
+
const GROK_MODEL_SPELLING = /^grok[\s_-]*(\d+(?:\.\d+)?)(?:[\s_-]+(?:build[\s_-]+)?(fast))?$/;
|
|
27
|
+
|
|
28
|
+
function normalizeGrokModel(raw) {
|
|
29
|
+
const match = GROK_MODEL_SPELLING.exec(raw.toLowerCase());
|
|
30
|
+
if (match) return `grok-${match[1]}${match[2] ? '-build-fast' : ''}`;
|
|
31
|
+
if (/^grok-[a-z0-9][a-z0-9._-]*$/i.test(raw)) return raw;
|
|
32
|
+
throw new Error(`grok does not know the model "${raw}". use grok 4.7 fast, grok 4.7, or a full grok- id`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// The model to run for a claude id: its replacement when it is retired,
|
|
36
|
+
// else the id as given. Keeps a "[1m]" tail.
|
|
37
|
+
function currentClaudeModel(value) {
|
|
38
|
+
const raw = String(value || '').trim();
|
|
39
|
+
const suffix = CLAUDE_LONG_CONTEXT.test(raw) ? '[1m]' : '';
|
|
40
|
+
const base = raw.replace(CLAUDE_LONG_CONTEXT, '').trim();
|
|
41
|
+
const next = RETIRED_CLAUDE_MODELS[base.toLowerCase()];
|
|
42
|
+
return next ? `${next}${suffix}` : raw;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The id the claude cli accepts for a display name typed with a space:
|
|
46
|
+
// "opus 5.5" is claude-opus-5-5, "fable 5.1" is claude-fable-5-1. A full id,
|
|
47
|
+
// a bare alias ("opus"), a "[1m]" tail and anything else pass through as given
|
|
48
|
+
// (a retired id still becomes its replacement).
|
|
49
|
+
const CLAUDE_DISPLAY_NAME = /^(opus|sonnet|haiku|fable)\s+(\d+)(?:\.(\d+))?$/i;
|
|
50
|
+
|
|
51
|
+
function claudeModelId(value) {
|
|
52
|
+
const raw = String(value || '').trim();
|
|
53
|
+
const suffix = CLAUDE_LONG_CONTEXT.test(raw) ? '[1m]' : '';
|
|
54
|
+
const base = raw.replace(CLAUDE_LONG_CONTEXT, '').trim();
|
|
55
|
+
const match = CLAUDE_DISPLAY_NAME.exec(base);
|
|
56
|
+
if (!match) return currentClaudeModel(raw);
|
|
57
|
+
return currentClaudeModel(`claude-${match[1].toLowerCase()}-${match[2]}${match[3] ? `-${match[3]}` : ''}${suffix}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The retired models a roster file's text still names, by id or spelled
|
|
61
|
+
// out, each once, so the roster can say they were upgraded.
|
|
62
|
+
function retiredModelsNamed(text) {
|
|
63
|
+
const found = [];
|
|
64
|
+
for (const id of Object.keys(RETIRED_CLAUDE_MODELS)) {
|
|
65
|
+
const [, family, major, minor] = /^claude-([a-z]+)-(\d+)-(\d+)/.exec(id) || [];
|
|
66
|
+
if (!family) continue;
|
|
67
|
+
const spelled = new RegExp(`\\b(?:claude[\\s_-]*)?${family}[\\s_-]*${major}[\\s._-]+${minor}(?![\\d.])`, 'i');
|
|
68
|
+
const base = `claude-${family}-${major}-${minor}`;
|
|
69
|
+
if (spelled.test(String(text || '')) && !found.includes(base)) found.push(base);
|
|
70
|
+
}
|
|
71
|
+
return found;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeRosterModel(engineId, value) {
|
|
75
|
+
const raw = String(value || '').trim();
|
|
76
|
+
if (!raw) return '';
|
|
77
|
+
if (engineId === 'grok') return normalizeGrokModel(raw);
|
|
78
|
+
if (!CLAUDE_FAMILY_ENGINES.includes(engineId)) return raw;
|
|
79
|
+
// "[1m]" asks the claude cli for the long context window; keep it on
|
|
80
|
+
// whatever name it follows.
|
|
81
|
+
const suffix = CLAUDE_LONG_CONTEXT.test(raw) ? '[1m]' : '';
|
|
82
|
+
const base = raw.replace(CLAUDE_LONG_CONTEXT, '').trim();
|
|
83
|
+
const lower = base.toLowerCase();
|
|
84
|
+
if (CLAUDE_MODEL_ALIASES.includes(lower)) return `${lower}${suffix}`;
|
|
85
|
+
const match = CLAUDE_MODEL_SPELLING.exec(lower.replace(/^claude[\s_-]*/, ''));
|
|
86
|
+
if (match) {
|
|
87
|
+
const id = `claude-${match[1]}-${match[2]}${match[3] ? `-${match[3]}` : ''}`;
|
|
88
|
+
return currentClaudeModel(`${id}${suffix}`);
|
|
89
|
+
}
|
|
90
|
+
if (lower.startsWith('claude-')) return currentClaudeModel(`${base}${suffix}`);
|
|
91
|
+
throw new Error(`${engineId} does not know the model "${raw}". use opus, sonnet, haiku, fable, opusplan, default, a version like opus 5.5, sonnet 5, haiku 5.5, or fable 5.1, or a full claude- id; add [1m] for the long context window`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// A saved pick's model, normalized the same way assign does. null means the
|
|
95
|
+
// saved name is one the claude cli would reject, so the pick cannot run.
|
|
96
|
+
function savedRosterModel(engineId, value) {
|
|
97
|
+
try {
|
|
98
|
+
return normalizeRosterModel(engineId, value);
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
CLAUDE_FAMILY_ENGINES,
|
|
106
|
+
CLAUDE_MODEL_ALIASES,
|
|
107
|
+
RETIRED_CLAUDE_MODELS,
|
|
108
|
+
CLAUDE_MODEL_SPELLING,
|
|
109
|
+
CLAUDE_LONG_CONTEXT,
|
|
110
|
+
GROK_MODEL_SPELLING,
|
|
111
|
+
currentClaudeModel,
|
|
112
|
+
claudeModelId,
|
|
113
|
+
retiredModelsNamed,
|
|
114
|
+
normalizeRosterModel,
|
|
115
|
+
savedRosterModel,
|
|
116
|
+
};
|
package/lib/runner-command.js
CHANGED
|
@@ -16,19 +16,20 @@ const DEFAULT_CLAUDE_RUNNER_BIN = 'claude';
|
|
|
16
16
|
const EFFORT_WORDS = Object.freeze(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
17
17
|
const CLAUDE_EFFORTS = EFFORT_WORDS;
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
'opus 4.8': 'claude-opus-4-8',
|
|
25
|
-
'opus-4.8': 'claude-opus-4-8',
|
|
26
|
-
});
|
|
19
|
+
// The one model-name table lives in model-names.js, shared with
|
|
20
|
+
// engine-registry. Friendly names become the ids the claude cli accepts;
|
|
21
|
+
// anything it does not know passes through unchanged (runners like glm and
|
|
22
|
+
// swe name their own models here, so this never throws).
|
|
23
|
+
const { claudeModelId, currentClaudeModel, normalizeRosterModel } = require('./model-names');
|
|
27
24
|
|
|
28
25
|
function normalizeClaudeModelName(value) {
|
|
29
26
|
const trimmed = String(value || '').trim();
|
|
30
27
|
if (!trimmed) return '';
|
|
31
|
-
|
|
28
|
+
try {
|
|
29
|
+
return normalizeRosterModel('claude', trimmed);
|
|
30
|
+
} catch {
|
|
31
|
+
return trimmed;
|
|
32
|
+
}
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
// Canonical runner profiles. Each entry is the config an operator would pick
|
|
@@ -246,7 +247,7 @@ function ownModelsOnlyEngineForBin(bin) {
|
|
|
246
247
|
// claude env pin) and rides its own default instead.
|
|
247
248
|
function runnerModelFor(engineId, model) {
|
|
248
249
|
// Required here, not at the top: engine-registry requires this file.
|
|
249
|
-
const { isClaudeFamilyModel
|
|
250
|
+
const { isClaudeFamilyModel } = require('./engine-registry');
|
|
250
251
|
const name = String(engineId || '').trim().toLowerCase();
|
|
251
252
|
// A display name like "opus 5.5" only becomes an id for the claude cli (or
|
|
252
253
|
// an unnamed engine). Any other engine's model name is handed over as is.
|