triflux 10.61.2 → 10.62.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/bin/triflux.mjs CHANGED
@@ -67,6 +67,7 @@ import {
67
67
  saveRegistry,
68
68
  syncRegistryTargets,
69
69
  } from "../scripts/lib/mcp-guard-engine.mjs";
70
+ import { resolveModelFamilies } from "../scripts/lib/model-family-resolver.mjs";
70
71
  import {
71
72
  formatPsmuxInstallGuidance,
72
73
  formatPsmuxUpdateGuidance,
@@ -988,11 +989,17 @@ function previewCodexProfiles() {
988
989
  ? readFileSync(CODEX_CONFIG_PATH, "utf8")
989
990
  : "";
990
991
  const profiles = [];
992
+ const resolved = resolveModelFamilies("codex", {
993
+ codexHome: CODEX_DIR,
994
+ allowCommand: false,
995
+ persist: false,
996
+ }).profiles;
991
997
 
992
998
  // Codex 0.134+: 프로필은 별도 파일 ~/.codex/<name>.config.toml 로 관리한다.
993
999
  for (const profile of REQUIRED_CODEX_PROFILES) {
994
1000
  const profilePath = join(CODEX_DIR, `${profile.name}.config.toml`);
995
- const desiredContent = `${profile.lines.join("\n")}\n`;
1001
+ const { model, effort } = resolved[profile.name];
1002
+ const desiredContent = `model = "${model}"\nmodel_reasoning_effort = "${effort}"\n`;
996
1003
  const existingContent = existsSync(profilePath)
997
1004
  ? readFileSync(profilePath, "utf8")
998
1005
  : null;
@@ -1483,7 +1490,7 @@ async function cmdSetup(options = {}) {
1483
1490
  "gemini-profiles",
1484
1491
  `Antigravity/Gemini profiles 설정 실패: ${renderErrorMessage(gemini.message)}`,
1485
1492
  );
1486
- if (gemini.added) report.alreadyInGoalState = false;
1493
+ if (gemini.changed) report.alreadyInGoalState = false;
1487
1494
 
1488
1495
  const settingsPath = join(CLAUDE_DIR, "settings.json");
1489
1496
  const hudPath = join(CLAUDE_DIR, "hud", "hud-qos-status.mjs");
@@ -58,6 +58,28 @@ function beginTurn(state) {
58
58
  function acceptEntry(state, entry, marker) {
59
59
  if (entry?.isSidechain) return;
60
60
  const message = entry?.message;
61
+ if (
62
+ entry?.type === "attachment" &&
63
+ entry.attachment?.type === "goal_status"
64
+ ) {
65
+ const goal = entry.attachment;
66
+ const cleared = goal.met === true && goal.sentinel === true;
67
+ state.goal = {
68
+ state: cleared ? "none" : goal.met ? "complete" : "active",
69
+ objective: cleared ? null : goal.condition,
70
+ judgedBy: "evaluator",
71
+ ...(goal.reason !== undefined ? { reason: goal.reason } : {}),
72
+ ...(goal.tokens !== undefined ? { tokensUsed: goal.tokens } : {}),
73
+ updatedAt: entry.timestamp ?? null,
74
+ };
75
+ if (goal.sentinel === true) {
76
+ state.goalSentinel = {
77
+ ...goal,
78
+ updatedAt: entry.timestamp,
79
+ count: (state.goalSentinel?.count ?? 0) + 1,
80
+ };
81
+ }
82
+ }
61
83
  if (entry?.type === "system" && entry.subtype === "compact_boundary") {
62
84
  state.compactCount += 1;
63
85
  state.compact = {
@@ -211,6 +233,8 @@ export async function readClaudeTranscript(transcriptPath, { requestId } = {}) {
211
233
  measuredAt: null,
212
234
  compactCount: 0,
213
235
  compact: null,
236
+ goal: { state: "none", objective: null, judgedBy: "evaluator" },
237
+ goalSentinel: null,
214
238
  };
215
239
  }
216
240
  try {
@@ -255,5 +279,7 @@ export async function readClaudeTranscript(transcriptPath, { requestId } = {}) {
255
279
  sectionClosed: state.sectionClosed,
256
280
  compactCount: state.compactCount,
257
281
  compact: state.compact,
282
+ goal: state.goal,
283
+ goalSentinel: state.goalSentinel,
258
284
  };
259
285
  }
@@ -23,10 +23,8 @@ import {
23
23
  } from "node:fs";
24
24
  import { dirname, join } from "node:path";
25
25
 
26
- import {
27
- canonicalCodexProfileModel,
28
- normalizeCodexProfileName,
29
- } from "../../scripts/lib/codex-profile-config.mjs";
26
+ import { normalizeCodexProfileName } from "../../scripts/lib/codex-profile-config.mjs";
27
+ import { CODEX_FAMILY_PROFILES } from "../../scripts/lib/model-family-resolver.mjs";
30
28
 
31
29
  export const STATES = Object.freeze({
32
30
  PLANNING: "PLANNING",
@@ -47,13 +45,12 @@ const ESCALATION_CHAIN_CONFIG_PATH = ".triflux/config/escalation-chain.json";
47
45
  const DEFAULT_CLAUDE_MODEL = "fable";
48
46
 
49
47
  // Escalation chain (2026-09-22 정책):
50
- // 1. codex gpt-6-astra max: 최난도 단일 작업용 Codex escalation 단계
48
+ // 1. codex astra_max: 최난도 단일 작업용 Codex escalation 단계
51
49
  // 2. claude fable alias: Claude CLI가 최신 Fable tier로 해석하는 최종 수단
52
50
  const DEFAULT_ESCALATION_CHAIN = Object.freeze([
53
51
  Object.freeze({
54
52
  cli: "codex",
55
- model: "gpt-6-astra",
56
- profile: "gpt6_astra_max",
53
+ profile: "astra_max",
57
54
  }),
58
55
  Object.freeze({ cli: "claude", model: DEFAULT_CLAUDE_MODEL }),
59
56
  ]);
@@ -62,8 +59,8 @@ const STUCK_THRESHOLD = 3;
62
59
 
63
60
  function normalizeEscalationProfile(profile) {
64
61
  const normalized = normalizeCodexProfileName(profile);
65
- return normalized === "ultra" || normalized === "gpt6_astra_ultra"
66
- ? "gpt6_astra_max"
62
+ return normalized === "ultra" || normalized === "astra_ultra"
63
+ ? "astra_max"
67
64
  : normalized;
68
65
  }
69
66
 
@@ -307,26 +304,21 @@ function normalizeEscalationChainEntry(entry, index, source) {
307
304
  `${source}: chain[${index}].cli must be a non-empty string`,
308
305
  );
309
306
  }
307
+ const profile = typeof entry.profile === "string" ? entry.profile.trim() : "";
308
+ const normalizedProfile = normalizeEscalationProfile(profile);
309
+ if (
310
+ entry.cli === "codex" &&
311
+ Object.hasOwn(CODEX_FAMILY_PROFILES, normalizedProfile)
312
+ ) {
313
+ return { cli: entry.cli, profile: normalizedProfile };
314
+ }
310
315
  if (typeof entry.model !== "string" || entry.model.trim() === "") {
311
316
  throw new Error(
312
317
  `${source}: chain[${index}].model must be a non-empty string`,
313
318
  );
314
319
  }
315
-
316
- const normalized = {
317
- cli: entry.cli,
318
- model: entry.model,
319
- };
320
- if (typeof entry.profile === "string" && entry.profile.trim() !== "") {
321
- const profile = entry.profile.trim();
322
- normalized.profile =
323
- entry.cli === "codex" ? normalizeEscalationProfile(profile) : profile;
324
- // A remapped legacy profile must not keep reporting its old model.
325
- if (normalized.profile !== profile) {
326
- normalized.model =
327
- canonicalCodexProfileModel(normalized.profile) ?? normalized.model;
328
- }
329
- }
320
+ const normalized = { cli: entry.cli, model: entry.model };
321
+ if (profile) normalized.profile = profile;
330
322
  return normalized;
331
323
  }
332
324
 
@@ -10,7 +10,7 @@ const MODEL_CONTEXT = [
10
10
  {
11
11
  cli: "claude",
12
12
  model:
13
- /^(?:claude-)?(?:fable-5[.-]1|opus-5[.-]5|sonnet-5[.-]5|haiku-5[.-]5)(?:$|[-[])/i,
13
+ /^(?:claude-)?(?:fable|opus|sonnet|haiku)-(?:[5-9]|[1-9]\d+)(?:[.-]\d+)*(?:$|[-[])/i,
14
14
  tokens: 1_000_000,
15
15
  source:
16
16
  "https://platform.claude.com/docs/en/build-with-claude/context-windows",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "10.61.2",
3
+ "version": "10.62.0",
4
4
  "description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Antigravity, and Claude",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,127 +10,127 @@ import { resolveGeminiProfileForPurpose } from "./gemini-profiles.mjs";
10
10
 
11
11
  const policy = {
12
12
  executor: {
13
- profile: "gpt61_sol_high",
13
+ profile: "sol_high",
14
14
  timeoutSec: 1080,
15
15
  mcpHint: "implement",
16
16
  subcommand: "exec",
17
17
  },
18
18
  codex: {
19
- profile: "gpt61_sol_high",
19
+ profile: "sol_high",
20
20
  timeoutSec: 1080,
21
21
  mcpHint: "implement",
22
22
  subcommand: "exec",
23
23
  },
24
24
  "build-fixer": {
25
- profile: "gpt6_luna_high",
25
+ profile: "luna_high",
26
26
  timeoutSec: 540,
27
27
  mcpHint: "implement",
28
28
  subcommand: "exec",
29
29
  },
30
30
  cleanup: {
31
- profile: "gpt61_sol_med",
31
+ profile: "sol_med",
32
32
  timeoutSec: 540,
33
33
  mcpHint: "implement",
34
34
  subcommand: "exec",
35
35
  },
36
36
  deslop: {
37
- profile: "gpt61_sol_med",
37
+ profile: "sol_med",
38
38
  timeoutSec: 540,
39
39
  mcpHint: "implement",
40
40
  subcommand: "exec",
41
41
  },
42
42
  debugger: {
43
- profile: "gpt6_astra_xhigh",
43
+ profile: "astra_xhigh",
44
44
  timeoutSec: 900,
45
45
  mcpHint: "implement",
46
46
  subcommand: "exec",
47
47
  },
48
48
  "deep-executor": {
49
- profile: "gpt6_astra_xhigh",
49
+ profile: "astra_xhigh",
50
50
  timeoutSec: 3600,
51
51
  mcpHint: "implement",
52
52
  subcommand: "exec",
53
53
  },
54
54
  architect: {
55
- profile: "gpt6_astra_xhigh",
55
+ profile: "astra_xhigh",
56
56
  timeoutSec: 3600,
57
57
  mcpHint: "analyze",
58
58
  subcommand: "exec",
59
59
  },
60
60
  critic: {
61
- profile: "gpt6_astra_xhigh",
61
+ profile: "astra_xhigh",
62
62
  timeoutSec: 3600,
63
63
  mcpHint: "analyze",
64
64
  subcommand: "exec",
65
65
  },
66
66
  planner: {
67
- profile: "gpt6_astra_xhigh",
67
+ profile: "astra_xhigh",
68
68
  timeoutSec: 3600,
69
69
  mcpHint: "analyze",
70
70
  subcommand: "exec",
71
71
  },
72
72
  analyst: {
73
- profile: "gpt6_astra_xhigh",
73
+ profile: "astra_xhigh",
74
74
  timeoutSec: 3600,
75
75
  mcpHint: "analyze",
76
76
  subcommand: "exec",
77
77
  },
78
78
  "code-reviewer": {
79
- profile: "gpt61_sol_high",
79
+ profile: "sol_high",
80
80
  timeoutSec: 1800,
81
81
  mcpHint: "review",
82
82
  subcommand: "review",
83
83
  },
84
84
  "quality-reviewer": {
85
- profile: "gpt61_sol_high",
85
+ profile: "sol_high",
86
86
  timeoutSec: 1800,
87
87
  mcpHint: "review",
88
88
  subcommand: "review",
89
89
  },
90
90
  "security-reviewer": {
91
- profile: "gpt6_astra_xhigh",
91
+ profile: "astra_xhigh",
92
92
  timeoutSec: 1800,
93
93
  mcpHint: "review",
94
94
  subcommand: "review",
95
95
  },
96
96
  scientist: {
97
- profile: "gpt61_sol_high",
97
+ profile: "sol_high",
98
98
  timeoutSec: 1440,
99
99
  mcpHint: "analyze",
100
100
  subcommand: "exec",
101
101
  },
102
102
  "document-specialist": {
103
- profile: "gpt61_sol_high",
103
+ profile: "sol_high",
104
104
  timeoutSec: 1440,
105
105
  mcpHint: "analyze",
106
106
  subcommand: "exec",
107
107
  },
108
108
  "scientist-deep": {
109
- profile: "gpt6_astra_xhigh",
109
+ profile: "astra_xhigh",
110
110
  timeoutSec: 3600,
111
111
  mcpHint: "analyze",
112
112
  subcommand: "exec",
113
113
  },
114
114
  verifier: {
115
- profile: "gpt61_sol_high",
115
+ profile: "sol_high",
116
116
  timeoutSec: 1200,
117
117
  mcpHint: "review",
118
118
  subcommand: "review",
119
119
  },
120
120
  "test-engineer": {
121
- profile: "gpt61_sol_high",
121
+ profile: "sol_high",
122
122
  timeoutSec: 1200,
123
123
  mcpHint: "implement",
124
124
  subcommand: "exec",
125
125
  },
126
126
  "qa-tester": {
127
- profile: "gpt61_sol_high",
127
+ profile: "sol_high",
128
128
  timeoutSec: 1200,
129
129
  mcpHint: "review",
130
130
  subcommand: "review",
131
131
  },
132
132
  spark: {
133
- profile: "gpt6_luna_low",
133
+ profile: "luna_low",
134
134
  timeoutSec: 180,
135
135
  mcpHint: "implement",
136
136
  subcommand: "exec",
@@ -138,13 +138,13 @@ const policy = {
138
138
  // These entries are only for explicit direct-Codex overrides. agent-map.json
139
139
  // remains the provider-selection authority for their normal execution.
140
140
  designer: {
141
- profile: "gpt6_astra_xhigh",
141
+ profile: "astra_xhigh",
142
142
  timeoutSec: 900,
143
143
  mcpHint: "implement",
144
144
  subcommand: "exec",
145
145
  },
146
146
  writer: {
147
- profile: "gpt6_luna_high",
147
+ profile: "luna_high",
148
148
  timeoutSec: 900,
149
149
  mcpHint: "implement",
150
150
  subcommand: "exec",
@@ -167,13 +167,13 @@ const DEFAULT_CODEX_AGENT = "executor";
167
167
 
168
168
  const CANONICAL_CODEX_PROFILE_OVERRIDES = Object.freeze(
169
169
  new Set([
170
- "gpt6_luna_low",
171
- "gpt6_luna_high",
172
- "gpt61_sol_med",
173
- "gpt61_sol_high",
174
- "gpt6_astra_xhigh",
175
- "gpt6_astra_max",
176
- "gpt6_astra_ultra",
170
+ "luna_low",
171
+ "luna_high",
172
+ "sol_med",
173
+ "sol_high",
174
+ "astra_xhigh",
175
+ "astra_max",
176
+ "astra_ultra",
177
177
  ]),
178
178
  );
179
179
  const ULTRA_ELIGIBLE_CODEX_AGENTS = Object.freeze(
@@ -183,8 +183,8 @@ const ULTRA_ELIGIBLE_CODEX_AGENTS = Object.freeze(
183
183
  const NESTED_GLOBAL_PROFILE_OVERRIDES = new Set([
184
184
  "max",
185
185
  "ultra",
186
- "gpt6_astra_max",
187
- "gpt6_astra_ultra",
186
+ "astra_max",
187
+ "astra_ultra",
188
188
  ]);
189
189
 
190
190
  export function resolveCodexAgentPolicy(agent) {
@@ -219,9 +219,9 @@ export function resolveCodexAgentProfile(
219
219
 
220
220
  const canonical =
221
221
  requested === "max"
222
- ? "gpt6_astra_max"
222
+ ? "astra_max"
223
223
  : requested === "ultra"
224
- ? "gpt6_astra_ultra"
224
+ ? "astra_ultra"
225
225
  : requested;
226
226
  if (!CANONICAL_CODEX_PROFILE_OVERRIDES.has(canonical)) {
227
227
  if (allowCustom) return requested;
@@ -229,9 +229,9 @@ export function resolveCodexAgentProfile(
229
229
  `[agent-route-policy] unsupported profile override: ${requested}`,
230
230
  );
231
231
  }
232
- if (canonical !== "gpt6_astra_ultra") return canonical;
232
+ if (canonical !== "astra_ultra") return canonical;
233
233
  return nested || !ULTRA_ELIGIBLE_CODEX_AGENTS.has(agent)
234
- ? "gpt6_astra_max"
234
+ ? "astra_max"
235
235
  : canonical;
236
236
  }
237
237
 
@@ -270,7 +270,7 @@ export function resolveNestedCodexAgentProfile(
270
270
  });
271
271
  const { effort } = resolveCodexProfileConfigValues(candidate, { codexHome });
272
272
  if (!effort) return fallback;
273
- return effort.toLowerCase() === "ultra" ? "gpt6_astra_max" : candidate;
273
+ return effort.toLowerCase() === "ultra" ? "astra_max" : candidate;
274
274
  }
275
275
 
276
276
  const EXPECTED_DURATION_BY_AGENT = Object.freeze({
@@ -408,13 +408,13 @@ async function runCli() {
408
408
  const module = await import(pathToFileURL(geminiProfilesModule).href);
409
409
  resolver = (agent) => {
410
410
  try {
411
- return module.resolveGeminiProfileForPurpose(agent) || "flash38";
411
+ return module.resolveGeminiProfileForPurpose(agent) || "flash";
412
412
  } catch {
413
- return "flash38";
413
+ return "flash";
414
414
  }
415
415
  };
416
416
  } catch {
417
- resolver = () => "flash38";
417
+ resolver = () => "flash";
418
418
  }
419
419
  }
420
420
  process.stdout.write(
@@ -9,6 +9,12 @@ import {
9
9
  import { homedir } from "node:os";
10
10
  import { dirname, join } from "node:path";
11
11
  import { writeRotatedBackup } from "./backup-rotation.mjs";
12
+ import {
13
+ CODEX_FAMILY_PROFILES,
14
+ modelFamilyPaths,
15
+ resolveModelFamilies,
16
+ writeModelFile,
17
+ } from "./model-family-resolver.mjs";
12
18
 
13
19
  const EFFORT_BY_SUFFIX = {
14
20
  ultra: "ultra",
@@ -20,41 +26,38 @@ const EFFORT_BY_SUFFIX = {
20
26
  low: "low",
21
27
  };
22
28
 
23
- const CANONICAL_PROFILE_VALUES = Object.freeze({
24
- gpt6_luna_low: { model: "gpt-6-luna", effort: "low" },
25
- gpt6_luna_high: { model: "gpt-6-luna", effort: "high" },
26
- gpt61_sol_med: { model: "gpt-6.1-sol", effort: "medium" },
27
- gpt61_sol_high: { model: "gpt-6.1-sol", effort: "high" },
28
- gpt6_astra_xhigh: { model: "gpt-6-astra", effort: "xhigh" },
29
- gpt6_astra_max: { model: "gpt-6-astra", effort: "max" },
30
- gpt6_astra_ultra: { model: "gpt-6-astra", effort: "ultra" },
31
- });
32
-
33
- // GPT-5.6 lane names map to the GPT-6 lane with the same effort. GPT-6 has no
34
- // Terra tier, so Terra lanes move to Sol. GPT-6 Sol lane names map to GPT-6.1
35
- // Sol with the same effort.
36
29
  const LEGACY_CANONICAL_PROFILE_ALIASES = Object.freeze({
37
- gpt56_sol_xhigh: "gpt6_astra_xhigh",
38
- gpt56_sol_max: "gpt6_astra_max",
39
- gpt56_sol_ultra: "gpt6_astra_ultra",
40
- gpt56_terra_high: "gpt61_sol_high",
41
- gpt56_terra_med: "gpt61_sol_med",
42
- gpt56_luna_low: "gpt6_luna_low",
43
- gpt6_sol_high: "gpt61_sol_high",
44
- gpt6_sol_med: "gpt61_sol_med",
30
+ gpt56_sol_xhigh: "astra_xhigh",
31
+ gpt56_sol_max: "astra_max",
32
+ gpt56_sol_ultra: "astra_ultra",
33
+ gpt56_terra_high: "sol_high",
34
+ gpt56_terra_med: "sol_med",
45
35
  });
46
36
 
47
37
  export function normalizeCodexProfileName(profileName) {
48
38
  const normalized = String(profileName ?? "").trim();
49
- return LEGACY_CANONICAL_PROFILE_ALIASES[normalized] || normalized;
39
+ const legacy = LEGACY_CANONICAL_PROFILE_ALIASES[normalized];
40
+ if (legacy) return legacy;
41
+ const familyName = normalized.replace(/^gpt\d+_(sol|astra|luna)_/, "$1_");
42
+ return Object.hasOwn(CODEX_FAMILY_PROFILES, familyName)
43
+ ? familyName
44
+ : normalized;
50
45
  }
51
46
 
52
- /** Model ID of a canonical profile (after legacy normalization), or null. */
53
- export function canonicalCodexProfileModel(profileName) {
54
- return (
55
- CANONICAL_PROFILE_VALUES[normalizeCodexProfileName(profileName)]?.model ??
56
- null
57
- );
47
+ export function ensureManagedCodexProfiles(opts = {}) {
48
+ const { codexHome } = modelFamilyPaths(opts);
49
+ const { profiles } = resolveModelFamilies("codex", opts);
50
+ let changed = 0;
51
+ for (const [name, { model, effort }] of Object.entries(profiles)) {
52
+ if (
53
+ writeModelFile(
54
+ join(codexHome, `${name}.config.toml`),
55
+ `model = "${model}"\nmodel_reasoning_effort = "${effort}"\n`,
56
+ )
57
+ )
58
+ changed++;
59
+ }
60
+ return { changed, profiles };
58
61
  }
59
62
 
60
63
  function readProfileScalar(raw, key) {
@@ -70,7 +73,7 @@ function readProfileScalar(raw, key) {
70
73
  }
71
74
 
72
75
  /**
73
- * Resolve a Codex effort profile name (e.g. "gpt61_sol_high") to the `-c key=value`
76
+ * Resolve a Codex effort profile name (e.g. "sol_high") to the `-c key=value`
74
77
  * config overrides that `codex exec` accepts, so the headless CLI lanes can
75
78
  * select a profile WITHOUT `codex exec --profile <name>`. codex 0.134+ rejects
76
79
  * `--profile X` whenever config.toml still contains an inline `[profiles.X]`
@@ -118,12 +121,20 @@ export function codexProfileConfigOverrides(profileName, opts = {}) {
118
121
  opts,
119
122
  );
120
123
  if (opts.enforceCanonicalProfile === true) {
121
- const canonical = CANONICAL_PROFILE_VALUES[normalizedProfileName];
122
- if (canonical) ({ model, effort } = canonical);
124
+ if (Object.hasOwn(CODEX_FAMILY_PROFILES, normalizedProfileName)) {
125
+ ({ model, effort } = resolveModelFamilies("codex", {
126
+ ...opts,
127
+ allowCommand: false,
128
+ persist: false,
129
+ }).profiles[normalizedProfileName]);
130
+ }
123
131
  }
124
132
  if (opts.disallowUltra === true && effort?.toLowerCase() === "ultra") {
125
- model = "gpt-6-astra";
126
- effort = "max";
133
+ ({ model, effort } = resolveModelFamilies("codex", {
134
+ ...opts,
135
+ allowCommand: false,
136
+ persist: false,
137
+ }).profiles.astra_max);
127
138
  }
128
139
  const overrides = [];
129
140
  if (model) overrides.push(`model="${model}"`);