@triflux/remote 10.86.1 → 10.88.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/hub/team/pane.mjs CHANGED
@@ -4,6 +4,7 @@ import { existsSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  import { createRequire } from "node:module";
5
5
  import { dirname, join } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
+ import { judgeComposerState } from "@triflux/core/hub/lib/jev-addon.mjs";
7
8
  import { privateTmpDir } from "@triflux/core/hub/lib/private-tmp.mjs";
8
9
  import { psmuxExec } from "./psmux.mjs";
9
10
  import { detectMultiplexer, tmuxExec } from "./session.mjs";
@@ -251,6 +252,104 @@ export function submitPromptWithVerification({
251
252
  }
252
253
  }
253
254
 
255
+ /**
256
+ * 결정론적 컴포저 상태 판정이 모호할 때 Jev로 컴포저 상태를 판별해 Enter 재전송 여부를 결정한다.
257
+ */
258
+ export async function submitPromptWithJevVerification({
259
+ cli = "claude",
260
+ prompt = "",
261
+ capture,
262
+ sendEnter,
263
+ sleep = sleepMs,
264
+ settleMs = PASTE_SETTLE_MS,
265
+ retryLimit = SUBMIT_RETRY_LIMIT,
266
+ jevOptions = {},
267
+ } = {}) {
268
+ const strictVerification = isAntigravityCli(cli);
269
+ const promptPrefix = promptComposerNeedle(prompt);
270
+ const isConfirmedSubmitted = (text) => {
271
+ if (!promptPrefix) return true;
272
+ if (!text) return !strictVerification;
273
+ if (hasBusyMarkerOutsideComposer(text)) return true;
274
+ const state = composerPromptState(text, promptPrefix);
275
+ if (state === "absent") return true;
276
+ if (state === "unknown" && !strictVerification) return true;
277
+ return false;
278
+ };
279
+
280
+ try {
281
+ submitPromptWithVerification({
282
+ cli,
283
+ prompt,
284
+ capture,
285
+ sendEnter,
286
+ sleep,
287
+ settleMs,
288
+ retryLimit,
289
+ });
290
+ } catch (err) {
291
+ const capturedText =
292
+ typeof capture === "function" ? String(capture() ?? "") : "";
293
+ const verdict = await judgeComposerState(
294
+ { capturedText, promptPrefix },
295
+ jevOptions,
296
+ );
297
+ if (verdict?.state === "submitted_running") {
298
+ return { submitted: true, jevState: verdict.state };
299
+ }
300
+ if (
301
+ verdict?.state === "stuck_in_composer" &&
302
+ typeof sendEnter === "function"
303
+ ) {
304
+ sendEnter();
305
+ sleep(400);
306
+ const afterText =
307
+ typeof capture === "function" ? String(capture() ?? "") : "";
308
+ if (isConfirmedSubmitted(afterText)) {
309
+ return { submitted: true, jevState: "submitted_running" };
310
+ }
311
+ const postVerdict = await judgeComposerState(
312
+ { capturedText: afterText, promptPrefix },
313
+ jevOptions,
314
+ );
315
+ if (postVerdict?.state === "submitted_running") {
316
+ return { submitted: true, jevState: postVerdict.state };
317
+ }
318
+ }
319
+ throw err;
320
+ }
321
+ const capturedText =
322
+ typeof capture === "function" ? String(capture() ?? "") : "";
323
+ const verdict = await judgeComposerState(
324
+ { capturedText, promptPrefix },
325
+ jevOptions,
326
+ );
327
+ if (
328
+ verdict?.state === "stuck_in_composer" &&
329
+ typeof sendEnter === "function"
330
+ ) {
331
+ sendEnter();
332
+ sleep(400);
333
+ const afterText =
334
+ typeof capture === "function" ? String(capture() ?? "") : "";
335
+ if (isConfirmedSubmitted(afterText)) {
336
+ return { submitted: true, jevState: "submitted_running" };
337
+ }
338
+ const postVerdict = await judgeComposerState(
339
+ { capturedText: afterText, promptPrefix },
340
+ jevOptions,
341
+ );
342
+ return {
343
+ submitted: postVerdict?.state === "submitted_running",
344
+ jevState: postVerdict?.state || verdict.state,
345
+ };
346
+ }
347
+ return {
348
+ submitted: verdict?.state !== "modal_blocking",
349
+ jevState: verdict?.state || null,
350
+ };
351
+ }
352
+
254
353
  function confirmSubmit(target, prompt, cli, sendEnter) {
255
354
  submitPromptWithVerification({
256
355
  cli,
@@ -1,6 +1,7 @@
1
1
  // headless 세션 결과 색인. 화면의 HEADLESS_COMPLETE 문자열 대신 읽도록 세션당 JSON 하나를 둔다 (ADR-0034).
2
2
  import { renameSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { judgeInstructionAck } from "@triflux/core/hub/lib/jev-addon.mjs";
4
5
 
5
6
  export const RESULTS_INDEX_VERSION = 1;
6
7
 
@@ -34,6 +35,9 @@ export function createResultsIndex(path, sessionName, workers, canWrite) {
34
35
  startedAt: null,
35
36
  finishedAt: null,
36
37
  resultFile: w.resultFile,
38
+ ackStatus: null,
39
+ semanticState: null,
40
+ ackConfidence: null,
37
41
  })),
38
42
  };
39
43
  const byPane = new Map(index.workers.map((w) => [w.paneName, w]));
@@ -88,5 +92,36 @@ export function createResultsIndex(path, sessionName, workers, canWrite) {
88
92
  index.completed = Boolean(completed);
89
93
  flush();
90
94
  },
95
+ recordInstructionAck(
96
+ paneName,
97
+ { ackStatus = null, semanticState = null, confidence = null } = {},
98
+ ) {
99
+ const w = byPane.get(paneName);
100
+ if (!w) return null;
101
+ w.ackStatus = ackStatus;
102
+ w.semanticState = semanticState;
103
+ w.ackConfidence = confidence;
104
+ flush();
105
+ return w;
106
+ },
107
+ async verifyInstructionAckWithJev(
108
+ paneName,
109
+ { instruction = "", recentOutput = "" } = {},
110
+ jevOptions = {},
111
+ ) {
112
+ const verdict = await judgeInstructionAck(
113
+ { instruction, recentOutput },
114
+ jevOptions,
115
+ );
116
+ if (!verdict) return null;
117
+ this.recordInstructionAck(paneName, {
118
+ ackStatus: verdict.ackStatus,
119
+ semanticState: verdict.acknowledged
120
+ ? "following_instruction"
121
+ : "unverified_instruction",
122
+ confidence: verdict.confidence,
123
+ });
124
+ return verdict;
125
+ },
91
126
  };
92
127
  }
@@ -24,7 +24,14 @@ import {
24
24
  import { dirname, join } from "node:path";
25
25
 
26
26
  import { normalizeCodexProfileName } from "../../scripts/lib/codex-profile-config.mjs";
27
- import { CODEX_FAMILY_PROFILES } from "../../scripts/lib/model-family-resolver.mjs";
27
+ import {
28
+ EFFORT_ORDER,
29
+ modelEffortFromProfile,
30
+ profileFromModelEffort,
31
+ resolveCodexModelEffort,
32
+ } from "../../scripts/lib/model-effort.mjs";
33
+ import { AGY_FAMILY_PROFILES } from "../../scripts/lib/model-family-resolver.mjs";
34
+ import { judgeSameFailureCause } from "@triflux/core/hub/lib/jev-addon.mjs";
28
35
 
29
36
  export const STATES = Object.freeze({
30
37
  PLANNING: "PLANNING",
@@ -50,7 +57,8 @@ const DEFAULT_CLAUDE_MODEL = "fable";
50
57
  const DEFAULT_ESCALATION_CHAIN = Object.freeze([
51
58
  Object.freeze({
52
59
  cli: "codex",
53
- profile: "astra_max",
60
+ family: "astra",
61
+ effort: "max",
54
62
  }),
55
63
  Object.freeze({ cli: "claude", model: DEFAULT_CLAUDE_MODEL }),
56
64
  ]);
@@ -119,10 +127,14 @@ export function createRetryStateMachine(options = {}) {
119
127
  return transition(STATES.DONE);
120
128
  }
121
129
 
122
- function reportVerifyFail(failureReason) {
130
+ function reportVerifyFail(failureReason, failOptions = {}) {
123
131
  const reason = String(failureReason || "unknown");
124
- if (reason === state.lastFailureReason) {
132
+ const isSameCause =
133
+ reason === state.lastFailureReason ||
134
+ (Boolean(state.lastFailureReason) && failOptions.sameCause === true);
135
+ if (isSameCause) {
125
136
  state.stuckCounter += 1;
137
+ state.lastFailureReason = reason;
126
138
  } else {
127
139
  state.stuckCounter = 1;
128
140
  state.lastFailureReason = reason;
@@ -148,6 +160,20 @@ export function createRetryStateMachine(options = {}) {
148
160
  return transition(STATES.DIAGNOSING, { reason });
149
161
  }
150
162
 
163
+ async function reportVerifyFailAsync(failureReason, jevOptions = {}) {
164
+ const reason = String(failureReason || "unknown");
165
+ let sameCause = false;
166
+ if (state.lastFailureReason && reason !== state.lastFailureReason) {
167
+ const verdict = await judgeSameFailureCause(
168
+ state.lastFailureReason,
169
+ reason,
170
+ jevOptions,
171
+ );
172
+ sameCause = verdict?.sameCause === true;
173
+ }
174
+ return reportVerifyFail(reason, { sameCause });
175
+ }
176
+
151
177
  function escalate() {
152
178
  if (state.cliIndex + 1 >= state.cliChain.length) {
153
179
  return transition(STATES.BUDGET_EXCEEDED, {
@@ -155,6 +181,39 @@ export function createRetryStateMachine(options = {}) {
155
181
  chain: state.cliChain,
156
182
  });
157
183
  }
184
+ const current = state.cliChain[state.cliIndex];
185
+ const next = state.cliChain[state.cliIndex + 1];
186
+ if (
187
+ current.cli === "codex" &&
188
+ next.cli === "codex" &&
189
+ current.family &&
190
+ next.family
191
+ ) {
192
+ try {
193
+ const base = resolveCodexModelEffort(current, options);
194
+ const promoted = resolveCodexModelEffort(next, options);
195
+ if (
196
+ promoted.effort !== next.effort &&
197
+ base.model === promoted.model &&
198
+ base.effort === promoted.effort
199
+ ) {
200
+ return transition(STATES.BUDGET_EXCEEDED, {
201
+ reason: "escalation-not-promoted",
202
+ escalated: false,
203
+ model: promoted.model,
204
+ requestedEffort: next.effort,
205
+ effort: promoted.effort,
206
+ });
207
+ }
208
+ } catch (error) {
209
+ if (error.code !== "unsupported-effort") throw error;
210
+ return transition(STATES.BUDGET_EXCEEDED, {
211
+ reason: "unsupported-escalation-effort",
212
+ escalated: false,
213
+ error: error.message,
214
+ });
215
+ }
216
+ }
158
217
  state.cliIndex += 1;
159
218
  state.iterations = 0;
160
219
  state.stuckCounter = 0;
@@ -231,6 +290,7 @@ export function createRetryStateMachine(options = {}) {
231
290
  startIteration,
232
291
  reportVerifySuccess,
233
292
  reportVerifyFail,
293
+ reportVerifyFailAsync,
234
294
  escalate,
235
295
  on,
236
296
  serialize,
@@ -306,11 +366,34 @@ function normalizeEscalationChainEntry(entry, index, source) {
306
366
  }
307
367
  const profile = typeof entry.profile === "string" ? entry.profile.trim() : "";
308
368
  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 };
369
+ const provider = entry.cli === "agy" ? "antigravity" : entry.cli;
370
+ if (provider === "codex" || provider === "antigravity") {
371
+ const explicit = entry.family !== undefined || entry.effort !== undefined;
372
+ let spec = explicit
373
+ ? typeof entry.family === "string" && typeof entry.effort === "string"
374
+ ? modelEffortFromProfile(provider, entry.family, entry.effort)
375
+ : null
376
+ : modelEffortFromProfile(
377
+ provider,
378
+ provider === "codex" ? normalizedProfile : profile,
379
+ );
380
+ if (explicit && !spec)
381
+ throw new Error(`${source}: chain[${index}] has invalid family/effort`);
382
+ if (spec) {
383
+ if (provider === "codex" && spec.effort === "ultra")
384
+ spec = { family: "astra", effort: "max" };
385
+ const legacy = profileFromModelEffort(provider, spec);
386
+ const normalized = { cli: entry.cli, ...spec };
387
+ if (legacy) normalized.profile = legacy;
388
+ if (provider === "antigravity" && legacy) {
389
+ normalized.model = entry.model || AGY_FAMILY_PROFILES[legacy]?.model;
390
+ if (typeof normalized.model !== "string" || !normalized.model.trim())
391
+ throw new Error(
392
+ `${source}: chain[${index}].model must be a non-empty string`,
393
+ );
394
+ }
395
+ return normalized;
396
+ }
314
397
  }
315
398
  if (typeof entry.model !== "string" || entry.model.trim() === "") {
316
399
  throw new Error(
@@ -319,6 +402,11 @@ function normalizeEscalationChainEntry(entry, index, source) {
319
402
  }
320
403
  const normalized = { cli: entry.cli, model: entry.model };
321
404
  if (profile) normalized.profile = profile;
405
+ if (entry.effort !== undefined) {
406
+ if (!EFFORT_ORDER.includes(entry.effort))
407
+ throw new Error(`${source}: chain[${index}] has invalid effort`);
408
+ normalized.effort = entry.effort;
409
+ }
322
410
  return normalized;
323
411
  }
324
412
 
@@ -1,5 +1,6 @@
1
1
  import { realpath } from "node:fs/promises";
2
2
  import { resolve as pathResolve } from "node:path";
3
+ import { judgeBusyAction } from "@triflux/core/hub/lib/jev-addon.mjs";
3
4
  import {
4
5
  createActivityLifecycle,
5
6
  isActivityLifecycleEnabled,
@@ -299,12 +300,13 @@ export async function askCodexAppServerThread({
299
300
  noWait = false,
300
301
  warnContextPct,
301
302
  maxContextPct,
303
+ jevOptions,
302
304
  clientFactory = (opts) => new JsonRpcWsUdsClient(opts),
303
305
  } = {}) {
304
306
  if (typeof prompt !== "string" || !prompt.trim())
305
307
  throw new Error("codex UDS ask requires a non-empty prompt");
306
308
  if (!threadId) throw new Error("codex UDS ask requires --thread <id|auto>");
307
- if (!["wait", "fail", "steer"].includes(ifBusy))
309
+ if (!["wait", "fail", "steer", "auto"].includes(ifBusy))
308
310
  throw new Error(`invalid --if-busy: ${ifBusy}`);
309
311
  const client = codexClient(
310
312
  socketPath,
@@ -327,11 +329,13 @@ export async function askCodexAppServerThread({
327
329
  throw new Error(
328
330
  "no loaded Codex threads; open the TUI and send its first message before using --thread auto",
329
331
  );
330
- if (candidates.length !== 1)
332
+ if (candidates.length === 1) {
333
+ selectedId = candidates[0].threadId;
334
+ } else {
331
335
  throw new Error(
332
336
  `multiple loaded Codex threads; pass --thread ID: ${JSON.stringify(candidates)}`,
333
337
  );
334
- selectedId = candidates[0].threadId;
338
+ }
335
339
  }
336
340
  const busyStarted = Date.now();
337
341
  let thread = await readCodexThread(
@@ -340,14 +344,30 @@ export async function askCodexAppServerThread({
340
344
  DEFAULT_CODEX_APP_SERVER_UDS_BOOTSTRAP_MS,
341
345
  );
342
346
  const initiallyBusy = thread.status?.type === "active";
343
- if (thread.status?.type === "active" && ifBusy === "fail")
347
+ let effectiveIfBusy = ifBusy;
348
+ if (initiallyBusy && ifBusy === "auto") {
349
+ const latestTurnSummary =
350
+ thread.turns
351
+ ?.slice(-2)
352
+ .map((t) => JSON.stringify(t))
353
+ .join("\n")
354
+ .slice(0, 800) || "";
355
+ const verdict = await judgeBusyAction(
356
+ { prompt, threadSummary: latestTurnSummary },
357
+ jevOptions,
358
+ );
359
+ effectiveIfBusy = verdict?.action ?? "wait";
360
+ } else if (ifBusy === "auto") {
361
+ effectiveIfBusy = "wait";
362
+ }
363
+ if (thread.status?.type === "active" && effectiveIfBusy === "fail")
344
364
  throw new Error(`target busy (--if-busy fail): ${selectedId}`);
345
365
  const busyDeadline = busyStarted + busyTimeoutMs;
346
366
  const busyError = () =>
347
367
  new Error(
348
368
  `target busy after ${Math.ceil(busyTimeoutMs / 1000)}s (--if-busy wait)`,
349
369
  );
350
- while (thread.status?.type === "active" && ifBusy === "wait") {
370
+ while (thread.status?.type === "active" && effectiveIfBusy === "wait") {
351
371
  const waitMs = Math.min(
352
372
  pollIntervalMs,
353
373
  Math.max(0, busyDeadline - Date.now()),
@@ -373,7 +393,9 @@ export async function askCodexAppServerThread({
373
393
  throw busyError();
374
394
  }
375
395
  const busyWaitedMs =
376
- ifBusy === "wait" && initiallyBusy ? Date.now() - busyStarted : 0;
396
+ effectiveIfBusy === "wait" && initiallyBusy
397
+ ? Date.now() - busyStarted
398
+ : 0;
377
399
  const resumed = await client.request(
378
400
  "thread/resume",
379
401
  { threadId: selectedId, excludeTurns: true },
@@ -405,7 +427,7 @@ export async function askCodexAppServerThread({
405
427
  else if (!resumed?.thread && thread.status?.type === "active")
406
428
  activeTurns = thread.turns;
407
429
  const expectedTurnId =
408
- ifBusy === "steer"
430
+ effectiveIfBusy === "steer"
409
431
  ? activeTurns?.findLast((turn) => turn.status === "inProgress")?.id
410
432
  : undefined;
411
433
  const turn = await runCodexAppServerTurn({
@@ -434,7 +456,7 @@ export async function askCodexAppServerThread({
434
456
  status: turn.status,
435
457
  commentary: turn.commentary,
436
458
  meta: { retries: turn.retries },
437
- ifBusy,
459
+ ifBusy: effectiveIfBusy,
438
460
  busyWaitedMs,
439
461
  steered: turn.steered,
440
462
  socketPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triflux/remote",
3
- "version": "10.86.1",
3
+ "version": "10.88.0",
4
4
  "description": "triflux remote — team mode, psmux, MCP workers.",
5
5
  "type": "module",
6
6
  "main": "hub/index.mjs",
@@ -9,130 +9,134 @@ import {
9
9
  resolveCodexProfileConfigValues,
10
10
  } from "./codex-profile-config.mjs";
11
11
  import { resolveGeminiProfileForPurpose } from "./gemini-profiles.mjs";
12
+ import {
13
+ modelEffortFromProfile,
14
+ profileFromModelEffort,
15
+ } from "./model-effort.mjs";
12
16
 
13
17
  const policy = {
14
18
  executor: {
15
- profile: "sol_high",
19
+ codex: { family: "sol", effort: "high" },
16
20
  timeoutSec: 1080,
17
21
  mcpHint: "implement",
18
22
  subcommand: "exec",
19
23
  },
20
24
  codex: {
21
- profile: "sol_high",
25
+ codex: { family: "sol", effort: "high" },
22
26
  timeoutSec: 1080,
23
27
  mcpHint: "implement",
24
28
  subcommand: "exec",
25
29
  },
26
30
  "build-fixer": {
27
- profile: "luna_high",
31
+ codex: { family: "luna", effort: "high" },
28
32
  timeoutSec: 540,
29
33
  mcpHint: "implement",
30
34
  subcommand: "exec",
31
35
  },
32
36
  cleanup: {
33
- profile: "sol_med",
37
+ codex: { family: "sol", effort: "medium" },
34
38
  timeoutSec: 540,
35
39
  mcpHint: "implement",
36
40
  subcommand: "exec",
37
41
  },
38
42
  deslop: {
39
- profile: "sol_med",
43
+ codex: { family: "sol", effort: "medium" },
40
44
  timeoutSec: 540,
41
45
  mcpHint: "implement",
42
46
  subcommand: "exec",
43
47
  },
44
48
  debugger: {
45
- profile: "astra_xhigh",
49
+ codex: { family: "astra", effort: "xhigh" },
46
50
  timeoutSec: 900,
47
51
  mcpHint: "implement",
48
52
  subcommand: "exec",
49
53
  },
50
54
  "deep-executor": {
51
- profile: "astra_xhigh",
55
+ codex: { family: "astra", effort: "xhigh" },
52
56
  timeoutSec: 3600,
53
57
  mcpHint: "implement",
54
58
  subcommand: "exec",
55
59
  },
56
60
  architect: {
57
- profile: "astra_xhigh",
61
+ codex: { family: "astra", effort: "xhigh" },
58
62
  timeoutSec: 3600,
59
63
  mcpHint: "analyze",
60
64
  subcommand: "exec",
61
65
  },
62
66
  critic: {
63
- profile: "astra_xhigh",
67
+ codex: { family: "astra", effort: "xhigh" },
64
68
  timeoutSec: 3600,
65
69
  mcpHint: "analyze",
66
70
  subcommand: "exec",
67
71
  },
68
72
  planner: {
69
- profile: "astra_xhigh",
73
+ codex: { family: "astra", effort: "xhigh" },
70
74
  timeoutSec: 3600,
71
75
  mcpHint: "analyze",
72
76
  subcommand: "exec",
73
77
  },
74
78
  analyst: {
75
- profile: "astra_xhigh",
79
+ codex: { family: "astra", effort: "xhigh" },
76
80
  timeoutSec: 3600,
77
81
  mcpHint: "analyze",
78
82
  subcommand: "exec",
79
83
  },
80
84
  "code-reviewer": {
81
- profile: "sol_high",
85
+ codex: { family: "sol", effort: "high" },
82
86
  timeoutSec: 1800,
83
87
  mcpHint: "review",
84
88
  subcommand: "review",
85
89
  },
86
90
  "quality-reviewer": {
87
- profile: "sol_high",
91
+ codex: { family: "sol", effort: "high" },
88
92
  timeoutSec: 1800,
89
93
  mcpHint: "review",
90
94
  subcommand: "review",
91
95
  },
92
96
  "security-reviewer": {
93
- profile: "astra_xhigh",
97
+ codex: { family: "astra", effort: "xhigh" },
94
98
  timeoutSec: 1800,
95
99
  mcpHint: "review",
96
100
  subcommand: "review",
97
101
  },
98
102
  scientist: {
99
- profile: "sol_high",
103
+ codex: { family: "sol", effort: "high" },
100
104
  timeoutSec: 1440,
101
105
  mcpHint: "analyze",
102
106
  subcommand: "exec",
103
107
  },
104
108
  "document-specialist": {
105
- profile: "terra_high",
109
+ codex: { family: "terra", effort: "high" },
106
110
  timeoutSec: 1440,
107
111
  mcpHint: "analyze",
108
112
  subcommand: "exec",
109
113
  },
110
114
  "scientist-deep": {
111
- profile: "astra_xhigh",
115
+ codex: { family: "astra", effort: "xhigh" },
112
116
  timeoutSec: 3600,
113
117
  mcpHint: "analyze",
114
118
  subcommand: "exec",
115
119
  },
116
120
  verifier: {
117
- profile: "sol_high",
121
+ codex: { family: "sol", effort: "high" },
118
122
  timeoutSec: 1200,
119
123
  mcpHint: "review",
120
124
  subcommand: "review",
121
125
  },
122
126
  "test-engineer": {
123
- profile: "sol_high",
127
+ codex: { family: "sol", effort: "high" },
124
128
  timeoutSec: 1200,
125
129
  mcpHint: "implement",
126
130
  subcommand: "exec",
127
131
  },
128
132
  "qa-tester": {
129
- profile: "sol_high",
133
+ codex: { family: "sol", effort: "high" },
130
134
  timeoutSec: 1200,
131
135
  mcpHint: "review",
132
136
  subcommand: "review",
133
137
  },
134
138
  spark: {
135
- profile: "luna_low",
139
+ codex: { family: "luna", effort: "low" },
136
140
  timeoutSec: 180,
137
141
  mcpHint: "implement",
138
142
  subcommand: "exec",
@@ -140,13 +144,13 @@ const policy = {
140
144
  // These entries are only for explicit direct-Codex overrides. agent-map.json
141
145
  // remains the provider-selection authority for their normal execution.
142
146
  designer: {
143
- profile: "astra_xhigh",
147
+ codex: { family: "astra", effort: "xhigh" },
144
148
  timeoutSec: 900,
145
149
  mcpHint: "implement",
146
150
  subcommand: "exec",
147
151
  },
148
152
  writer: {
149
- profile: "terra_high",
153
+ codex: { family: "terra", effort: "high" },
150
154
  timeoutSec: 900,
151
155
  mcpHint: "implement",
152
156
  subcommand: "exec",
@@ -154,7 +158,11 @@ const policy = {
154
158
  };
155
159
 
156
160
  function freezePolicy(entry) {
157
- return Object.freeze({ ...entry });
161
+ return Object.freeze({
162
+ ...entry,
163
+ codex: Object.freeze({ ...entry.codex }),
164
+ profile: profileFromModelEffort("codex", entry.codex),
165
+ });
158
166
  }
159
167
 
160
168
  const CODEX_AGENT_POLICY = Object.freeze(
@@ -264,16 +272,16 @@ export function resolveNestedCodexAgentProfile(
264
272
  : NESTED_GLOBAL_PROFILE_OVERRIDES.has(globalOverride)
265
273
  ? globalOverride
266
274
  : "auto";
267
- const fallback = resolveCodexAgentProfile(agent);
268
275
  const candidate = resolveCodexAgentProfile(agent, {
269
276
  profileOverride: effectiveOverride,
270
277
  retryProfile,
271
278
  nested: true,
272
279
  allowCustom: true,
273
280
  });
274
- const { effort } = resolveCodexProfileConfigValues(candidate, { codexHome });
275
- if (!effort) return fallback;
276
- return effort.toLowerCase() === "ultra" ? "astra_max" : candidate;
281
+ const { effort } =
282
+ modelEffortFromProfile("codex", candidate) ||
283
+ resolveCodexProfileConfigValues(candidate, { codexHome });
284
+ return effort?.toLowerCase() === "ultra" ? "astra_max" : candidate;
277
285
  }
278
286
 
279
287
  const EXPECTED_DURATION_BY_AGENT = Object.freeze({
@@ -365,6 +373,7 @@ export function resolveAgentRoute(
365
373
  );
366
374
  }
367
375
  }
376
+ const { family, effort } = modelEffortFromProfile("codex", resolvedOverride);
368
377
  return [
369
378
  provider,
370
379
  policy.profile,
@@ -378,6 +387,8 @@ export function resolveAgentRoute(
378
387
  NATIVE_CODEX_AGENTS.has(agent),
379
388
  byAuthor ? reviewAuthor : "",
380
389
  usageSwitch,
390
+ effort,
391
+ family,
381
392
  ];
382
393
  }
383
394