@triflux/core 10.42.0 → 10.44.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.
@@ -96,6 +96,102 @@
96
96
  "state": null,
97
97
  "mcp_route": null
98
98
  },
99
+ {
100
+ "id": "tfx-review",
101
+ "patterns": [
102
+ {
103
+ "source": "\\btfx[\\s-]?review\\b",
104
+ "flags": "i"
105
+ }
106
+ ],
107
+ "skill": "tfx-review",
108
+ "priority": 1,
109
+ "supersedes": ["tfx-unified"],
110
+ "exclusive": false,
111
+ "state": null,
112
+ "mcp_route": null,
113
+ "explicit": true
114
+ },
115
+ {
116
+ "id": "tfx-analysis",
117
+ "patterns": [
118
+ {
119
+ "source": "\\btfx[\\s-]?analysis\\b",
120
+ "flags": "i"
121
+ }
122
+ ],
123
+ "skill": "tfx-analysis",
124
+ "priority": 1,
125
+ "supersedes": ["tfx-unified"],
126
+ "exclusive": false,
127
+ "state": null,
128
+ "mcp_route": null,
129
+ "explicit": true
130
+ },
131
+ {
132
+ "id": "tfx-plan",
133
+ "patterns": [
134
+ {
135
+ "source": "\\btfx[\\s-]?plan\\b",
136
+ "flags": "i"
137
+ }
138
+ ],
139
+ "skill": "tfx-plan",
140
+ "priority": 1,
141
+ "supersedes": ["tfx-unified"],
142
+ "exclusive": false,
143
+ "state": null,
144
+ "mcp_route": null,
145
+ "explicit": true
146
+ },
147
+ {
148
+ "id": "tfx-qa",
149
+ "patterns": [
150
+ {
151
+ "source": "\\btfx[\\s-]?qa\\b",
152
+ "flags": "i"
153
+ }
154
+ ],
155
+ "skill": "tfx-qa",
156
+ "priority": 1,
157
+ "supersedes": ["tfx-unified"],
158
+ "exclusive": false,
159
+ "state": null,
160
+ "mcp_route": null,
161
+ "explicit": true
162
+ },
163
+ {
164
+ "id": "tfx-research",
165
+ "patterns": [
166
+ {
167
+ "source": "\\btfx[\\s-]?research\\b",
168
+ "flags": "i"
169
+ }
170
+ ],
171
+ "skill": "tfx-research",
172
+ "priority": 1,
173
+ "supersedes": ["tfx-unified"],
174
+ "exclusive": false,
175
+ "state": null,
176
+ "mcp_route": null,
177
+ "explicit": true
178
+ },
179
+ {
180
+ "id": "tfx-find",
181
+ "patterns": [
182
+ {
183
+ "source": "\\btfx[\\s-]?find\\b",
184
+ "flags": "i"
185
+ }
186
+ ],
187
+ "skill": "tfx-find",
188
+ "priority": 1,
189
+ "supersedes": ["tfx-unified"],
190
+ "exclusive": false,
191
+ "state": null,
192
+ "mcp_route": null,
193
+ "explicit": true
194
+ },
99
195
  {
100
196
  "id": "tfx-unified",
101
197
  "patterns": [
@@ -127,10 +223,6 @@
127
223
  "source": "(?:찾아봐|조사해|검색해)",
128
224
  "flags": "i"
129
225
  },
130
- {
131
- "source": "(?:정리해|슬롭|클린업)",
132
- "flags": "i"
133
- },
134
226
  {
135
227
  "source": "(?:병렬|동시에|parallel|concurrent)",
136
228
  "flags": "i"
@@ -155,6 +247,41 @@
155
247
  "state": null,
156
248
  "mcp_route": null
157
249
  },
250
+ {
251
+ "id": "host-ai-slop-cleaner",
252
+ "patterns": [
253
+ {
254
+ "source": "(?:AI\\s*슬롭|ai\\s*slop|anti[\\s-]?slop|deslop)",
255
+ "flags": "i"
256
+ },
257
+ {
258
+ "source": "(?:슬롭|클린업)",
259
+ "flags": "i"
260
+ }
261
+ ],
262
+ "skill": "ai-slop-cleaner",
263
+ "priority": 1,
264
+ "supersedes": [],
265
+ "exclusive": false,
266
+ "state": null,
267
+ "mcp_route": null
268
+ },
269
+ {
270
+ "id": "tfx-prune",
271
+ "patterns": [
272
+ {
273
+ "source": "\\btfx[\\s-]?prune\\b",
274
+ "flags": "i"
275
+ }
276
+ ],
277
+ "skill": "tfx-prune",
278
+ "priority": 1,
279
+ "supersedes": ["tfx-unified"],
280
+ "exclusive": false,
281
+ "state": null,
282
+ "mcp_route": null,
283
+ "explicit": true
284
+ },
158
285
  {
159
286
  "id": "tfx-codex",
160
287
  "patterns": [
@@ -586,7 +713,6 @@
586
713
  },
587
714
  {
588
715
  "id": "gstack-ship",
589
- "disabled": true,
590
716
  "patterns": [
591
717
  {
592
718
  "source": "(?:배포해|PR\\s*만들|릴리스\\s*해|머지하고\\s*배포)",
@@ -598,7 +724,7 @@
598
724
  }
599
725
  ],
600
726
  "skill": "ship",
601
- "priority": 5,
727
+ "priority": 1,
602
728
  "supersedes": [],
603
729
  "exclusive": false,
604
730
  "state": null,
@@ -627,12 +753,12 @@
627
753
  "id": "gstack-cso",
628
754
  "patterns": [
629
755
  {
630
- "source": "(?:보안\\s*(?:감사|점검|리뷰|스캔)|\\bcso\\b|\\bOWASP\\b|\\bSTRIDE\\b)",
756
+ "source": "(?:보안\\s*(?:감사|점검|리뷰|검토|스캔)|\\bcso\\b|\\bOWASP\\b|\\bSTRIDE\\b)",
631
757
  "flags": "i"
632
758
  }
633
759
  ],
634
760
  "skill": "cso",
635
- "priority": 5,
761
+ "priority": 1,
636
762
  "supersedes": [],
637
763
  "exclusive": false,
638
764
  "state": null,
@@ -651,7 +777,7 @@
651
777
  }
652
778
  ],
653
779
  "skill": "qa",
654
- "priority": 5,
780
+ "priority": 1,
655
781
  "supersedes": [],
656
782
  "exclusive": false,
657
783
  "state": null,
@@ -722,14 +848,6 @@
722
848
  "source": "/ship\\b",
723
849
  "flags": "i"
724
850
  },
725
- {
726
- "source": "\\brelease\\b",
727
- "flags": "i"
728
- },
729
- {
730
- "source": "\\bpublish\\b",
731
- "flags": "i"
732
- },
733
851
  {
734
852
  "source": "배포(?!자|사|장|처)",
735
853
  "flags": ""
@@ -744,11 +862,13 @@
744
862
  }
745
863
  ],
746
864
  "skill": "tfx-ship",
747
- "priority": 3,
865
+ "priority": 1,
748
866
  "supersedes": ["gstack-ship"],
749
867
  "exclusive": false,
750
868
  "state": null,
751
- "mcp_route": null
869
+ "mcp_route": null,
870
+ "repo_scope": ["triflux"],
871
+ "explicit": true
752
872
  }
753
873
  ]
754
874
  }
@@ -152,7 +152,7 @@ function contextPercentsFromObject(value) {
152
152
  value.total_tokens ??
153
153
  value.totalTokens;
154
154
  // Fall back to a 1M context window when the payload omits one — Opus 4.x
155
- // [1M] and Codex gpt-5.5 both run on a 1M window, and assuming 200K there
155
+ // [1M] and Codex GPT-5.6 family run on a 1M window, and assuming 200K there
156
156
  // false-positives at ~15% real usage. Override via
157
157
  // TFX_CONTEXT_DEFAULT_MAX_TOKENS for narrower setups.
158
158
  const envFallback = Number(process.env.TFX_CONTEXT_DEFAULT_MAX_TOKENS);
package/hub/bridge.mjs CHANGED
@@ -99,6 +99,11 @@ const HUB_OPERATIONS = Object.freeze({
99
99
  action: "register",
100
100
  httpPath: "/bridge/register",
101
101
  },
102
+ heartbeat: {
103
+ transport: "command",
104
+ action: "heartbeat",
105
+ httpPath: "/bridge/heartbeat",
106
+ },
102
107
  result: {
103
108
  transport: "command",
104
109
  action: "result",
@@ -637,6 +642,14 @@ async function cmdRegister(args) {
637
642
  return emitJson(result || unavailableResult());
638
643
  }
639
644
 
645
+ async function cmdHeartbeat(args) {
646
+ const outcome = await requestHub(HUB_OPERATIONS.heartbeat, {
647
+ agent_id: args.agent,
648
+ ttl_ms: args["ttl-ms"] != null ? Number(args["ttl-ms"]) : undefined,
649
+ });
650
+ return emitJson(outcome?.result || unavailableResult());
651
+ }
652
+
640
653
  async function cmdResult(args) {
641
654
  let output = "";
642
655
  if (args.file && existsSync(args.file)) {
@@ -1559,6 +1572,68 @@ async function cmdRetryStatus(args) {
1559
1572
  return true;
1560
1573
  }
1561
1574
 
1575
+ // intervene-run은 hub transport를 거치지 않는다. heartbeat가 hub 장애 중에도
1576
+ // process/daemon/pane 사다리를 실행할 수 있도록 순수 로컬 모듈만 호출한다.
1577
+ async function cmdInterveneRun(args) {
1578
+ try {
1579
+ const payload = readBridgePayload(args);
1580
+ const intervention = await import("./team/intervention.mjs");
1581
+ let rolloutFile = payload.rolloutFile || null;
1582
+ if (!rolloutFile && payload.cli === "codex" && payload.pid) {
1583
+ rolloutFile = await intervention.resolveCodexRolloutFile({
1584
+ pid: numericOption(payload.pid, undefined),
1585
+ codexHome: payload.codexHome,
1586
+ });
1587
+ }
1588
+ const seconds = (value) => {
1589
+ const parsed = numericOption(value, undefined);
1590
+ return parsed > 0 ? parsed * 1_000 : undefined;
1591
+ };
1592
+ const readActivitySignature = intervention.createFileActivitySource({
1593
+ files: [payload.stdoutLog, payload.stderrLog, payload.resultFile].filter(
1594
+ Boolean,
1595
+ ),
1596
+ rolloutFile,
1597
+ });
1598
+ const ladder = intervention.createInterventionLadder({
1599
+ target: {
1600
+ channel: payload.channel,
1601
+ pid: numericOption(payload.pid, undefined),
1602
+ paneId: payload.paneId,
1603
+ interactive: payload.interactive === true,
1604
+ cli: payload.cli,
1605
+ sessionId: payload.sessionId,
1606
+ codexHome: payload.codexHome,
1607
+ rolloutFile,
1608
+ daemon: payload.daemon,
1609
+ },
1610
+ reinstructPrompt: payload.reinstructPrompt || undefined,
1611
+ readActivitySignature,
1612
+ config: {
1613
+ ...(seconds(payload.reinstructWaitSec)
1614
+ ? { reinstructWaitMs: seconds(payload.reinstructWaitSec) }
1615
+ : {}),
1616
+ ...(seconds(payload.resumeWaitSec)
1617
+ ? { resumeWaitMs: seconds(payload.resumeWaitSec) }
1618
+ : {}),
1619
+ ...(seconds(payload.sigtermGraceSec)
1620
+ ? { sigtermGraceMs: seconds(payload.sigtermGraceSec) }
1621
+ : {}),
1622
+ },
1623
+ log: (event) =>
1624
+ process.stderr.write(`[tfx-intervene] ${JSON.stringify(event)}\n`),
1625
+ });
1626
+ return emitJson(await ladder.intervene());
1627
+ } catch (error) {
1628
+ return emitJson({
1629
+ ok: false,
1630
+ outcome: "failed",
1631
+ step: null,
1632
+ error: error?.message || String(error),
1633
+ });
1634
+ }
1635
+ }
1636
+
1562
1637
  export async function main(argv = process.argv.slice(2)) {
1563
1638
  const cmd = argv[0];
1564
1639
  const args = parseArgs(argv.slice(1));
@@ -1566,6 +1641,8 @@ export async function main(argv = process.argv.slice(2)) {
1566
1641
  switch (cmd) {
1567
1642
  case "register":
1568
1643
  return await cmdRegister(args);
1644
+ case "heartbeat":
1645
+ return await cmdHeartbeat(args);
1569
1646
  case "result":
1570
1647
  return await cmdResult(args);
1571
1648
  case "control":
@@ -1630,9 +1707,11 @@ export async function main(argv = process.argv.slice(2)) {
1630
1707
  return await cmdRetryRun(args);
1631
1708
  case "retry-status":
1632
1709
  return await cmdRetryStatus(args);
1710
+ case "intervene-run":
1711
+ return await cmdInterveneRun(args);
1633
1712
  default:
1634
1713
  console.error(
1635
- "사용법: bridge.mjs <register|result|control|handoff|publish|takeover-role|send-input|context|deregister|assign-async|assign-result|assign-status|assign-retry|team-info|team-task-list|team-task-update|team-send-message|pipeline-state|pipeline-advance|pipeline-init|pipeline-list|ping|delegator-delegate|delegator-reply|delegator-status|hitl-request|hitl-submit|hitl-pending|daemon-probe|daemon-attach|daemon-interrupt|retry-run|retry-status> [--옵션]",
1714
+ "사용법: bridge.mjs <register|heartbeat|result|control|handoff|publish|takeover-role|send-input|context|deregister|assign-async|assign-result|assign-status|assign-retry|team-info|team-task-list|team-task-update|team-send-message|pipeline-state|pipeline-advance|pipeline-init|pipeline-list|ping|delegator-delegate|delegator-reply|delegator-status|hitl-request|hitl-submit|hitl-pending|daemon-probe|daemon-attach|daemon-interrupt|retry-run|retry-status|intervene-run> [--옵션]",
1636
1715
  );
1637
1716
  process.exit(1);
1638
1717
  }
@@ -6,7 +6,14 @@ import { existsSync, readFileSync, statSync } from "node:fs";
6
6
 
7
7
  import { codexProfileConfigOverrides } from "../scripts/lib/codex-profile-config.mjs";
8
8
  import { writePromptToTmpFile } from "./lib/prompt-tmp.mjs";
9
+ import {
10
+ createActivityLifecycle,
11
+ isActivityLifecycleEnabled,
12
+ resolveHardCeilingMs,
13
+ resolveStallInterventionMs,
14
+ } from "./lib/worker-lifecycle.mjs";
9
15
  import { IS_WINDOWS, killProcess } from "./platform.mjs";
16
+ import { createInterventionLadder } from "./team/intervention.mjs";
10
17
 
11
18
  // ── Quota retry-after 파싱 ──────────────────────────────────────
12
19
 
@@ -160,7 +167,7 @@ export const CODEX_MCP_EXECUTION_EXIT_CODE = 1;
160
167
  *
161
168
  * @param {string} prompt
162
169
  * @param {string|null} resultFile — null이면 --output-last-message 생략
163
- * @param {{ profile?: string, skipGitRepoCheck?: boolean, sandboxBypass?: boolean, cwd?: string, mcpServers?: string[], stdinPrompt?: boolean }} [opts]
170
+ * @param {{ profile?: string, codexHome?: string, disallowUltra?: boolean, enforceCanonicalProfile?: boolean, skipGitRepoCheck?: boolean, sandboxBypass?: boolean, cwd?: string, mcpServers?: string[], stdinPrompt?: boolean }} [opts]
164
171
  * @returns {string} 실행할 셸 커맨드
165
172
  */
166
173
  export function buildExecCommand(prompt, resultFile = null, opts = {}) {
@@ -170,6 +177,9 @@ export function buildExecCommand(prompt, resultFile = null, opts = {}) {
170
177
  sandboxBypass = true,
171
178
  mcpServers,
172
179
  stdinPrompt,
180
+ codexHome,
181
+ disallowUltra,
182
+ enforceCanonicalProfile,
173
183
  } = opts;
174
184
 
175
185
  const parts = ["codex"];
@@ -178,7 +188,13 @@ export function buildExecCommand(prompt, resultFile = null, opts = {}) {
178
188
  // still contains an inline [profiles.X] table (and codex re-injects such
179
189
  // tables when it rewrites config.toml), so the `-c model=.. -c
180
190
  // model_reasoning_effort=..` form is immune and mutates no config.
181
- const profileOverrides = profile ? codexProfileConfigOverrides(profile) : [];
191
+ const profileOverrides = profile
192
+ ? codexProfileConfigOverrides(profile, {
193
+ codexHome,
194
+ disallowUltra,
195
+ enforceCanonicalProfile,
196
+ })
197
+ : [];
182
198
 
183
199
  if (FEATURES.execSubcommand) {
184
200
  parts.push("exec");
@@ -397,19 +413,36 @@ export async function terminateChild(pid, opts = {}) {
397
413
  *
398
414
  * @param {string} command — shell command to run
399
415
  * @param {string} workdir — cwd for the child process
400
- * @param {number} timeout — max duration in ms
416
+ * @param {number} timeout — legacy wall-clock duration (TFX_ACTIVITY_LIFECYCLE=0 only)
401
417
  * @param {object} [opts]
402
418
  * @param {string} [opts.resultFile] — file to read output from (if CLI writes there)
403
419
  * @param {function} [opts.inferStallMode] — (stdout, stderr) => string. Default: () => 'timeout'
404
420
  * @param {number} [opts.stallCheckIntervalMs] — stall check interval (default 10_000)
405
- * @param {number} [opts.stallThresholdMs] — stall threshold (default 30_000)
421
+ * @param {number} [opts.stallThresholdMs] — inactivity threshold
422
+ * @param {number} [opts.hardCeilingMs] — activity-independent maximum duration
423
+ * @param {(context: object) => Promise<boolean|string>|boolean|string} [opts.onStallIntervene]
424
+ * T5 intervention seam; true or "resolved" keeps the process alive
425
+ * @param {object} [opts.deps] — clock/process/timer overrides for deterministic tests
406
426
  * @returns {Promise<object>} createResult-shaped object
407
427
  */
408
428
  export async function runProcess(command, workdir, timeout, opts = {}) {
409
- const startedAt = Date.now();
429
+ const deps = opts.deps || {};
430
+ const now = deps.now || Date.now;
431
+ const spawnProcess = deps.spawn || spawn;
432
+ const scheduleTimeout = deps.setTimeout || setTimeout;
433
+ const cancelTimeout = deps.clearTimeout || clearTimeout;
434
+ const scheduleInterval = deps.setInterval || setInterval;
435
+ const cancelInterval = deps.clearInterval || clearInterval;
436
+ const terminateProcess = deps.terminateChild || terminateChild;
437
+ const startedAt = now();
410
438
  const inferStallMode = opts.inferStallMode || (() => "timeout");
411
439
  const stallCheckIntervalMs = opts.stallCheckIntervalMs ?? 10_000;
412
- const stallThresholdMs = opts.stallThresholdMs ?? 30_000;
440
+ const legacyLifecycle = !isActivityLifecycleEnabled();
441
+ const stallThresholdMs =
442
+ opts.stallThresholdMs ??
443
+ (legacyLifecycle ? 30_000 : resolveStallInterventionMs());
444
+ const hardCeilingMs = opts.hardCeilingMs ?? resolveHardCeilingMs();
445
+ const earlyClassifyMs = opts.earlyClassifyMs ?? 30_000;
413
446
  const resultFile = opts.resultFile || null;
414
447
 
415
448
  let stdout = "";
@@ -421,7 +454,7 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
421
454
  try {
422
455
  // PRD A1: opts.spawnEnv 가 있으면 그 env 로 spawn (lease 의 authFile/env 적용).
423
456
  // undefined 면 spawn 의 default 동작 (부모 process env inherit) 유지.
424
- child = spawn(command, {
457
+ child = spawnProcess(command, {
425
458
  cwd: workdir,
426
459
  shell: true,
427
460
  windowsHide: true,
@@ -430,7 +463,7 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
430
463
  } catch (error) {
431
464
  return createResult(false, {
432
465
  stderr: String(error?.message || error),
433
- duration: Date.now() - startedAt,
466
+ duration: now() - startedAt,
434
467
  });
435
468
  }
436
469
 
@@ -446,9 +479,40 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
446
479
 
447
480
  let lastBytes = 0;
448
481
  let lastResultFileSignature = resultFileSignature();
449
- let lastChange = Date.now();
482
+ let lastChange = now();
483
+ const activitySignature = () =>
484
+ `${Buffer.byteLength(stdout) + Buffer.byteLength(stderr)}:${resultFileSignature()}`;
485
+ let ladder = null;
486
+ const lifecycle = createActivityLifecycle({
487
+ enabled: !legacyLifecycle,
488
+ interventionMs: stallThresholdMs,
489
+ hardCeilingMs,
490
+ now,
491
+ onIntervene: async (context) => {
492
+ if (typeof opts.onStallIntervene === "function") {
493
+ const result = await opts.onStallIntervene(context);
494
+ return result === true || result === "resolved";
495
+ }
496
+ ladder ??= createInterventionLadder({
497
+ target: {
498
+ pid: child.pid,
499
+ cli: opts.cli,
500
+ codexHome: opts.codexHome,
501
+ rolloutFile: opts.rolloutFile,
502
+ },
503
+ readActivitySignature: activitySignature,
504
+ deps:
505
+ typeof opts.resumeHandler === "function"
506
+ ? { resumeHandler: opts.resumeHandler }
507
+ : {},
508
+ });
509
+ const result = await ladder.intervene();
510
+ return result?.outcome === "reactivated" || result?.outcome === "resumed";
511
+ },
512
+ });
450
513
  const touch = () => {
451
- lastChange = Date.now();
514
+ lastChange = now();
515
+ lifecycle.observe(activitySignature());
452
516
  };
453
517
  child.stdout?.on("data", (chunk) => {
454
518
  stdout += String(chunk);
@@ -466,13 +530,15 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
466
530
  const stopFor = async (mode) => {
467
531
  if (failureMode) return;
468
532
  failureMode = mode;
469
- await terminateChild(child.pid);
533
+ await terminateProcess(child.pid);
470
534
  };
471
535
 
472
- const timeoutTimer = setTimeout(() => {
473
- void stopFor("timeout");
474
- }, timeout);
475
- const stallTimer = setInterval(() => {
536
+ const timeoutTimer = legacyLifecycle
537
+ ? scheduleTimeout(() => {
538
+ void stopFor("timeout");
539
+ }, timeout)
540
+ : null;
541
+ const stallTimer = scheduleInterval(() => {
476
542
  const size = Buffer.byteLength(stdout) + Buffer.byteLength(stderr);
477
543
  const currentResultFileSignature = resultFileSignature();
478
544
  if (
@@ -482,13 +548,37 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
482
548
  lastBytes = size;
483
549
  lastResultFileSignature = currentResultFileSignature;
484
550
  touch();
551
+ }
552
+ if (legacyLifecycle) {
553
+ if (now() - lastChange >= stallThresholdMs)
554
+ void stopFor(inferStallMode(stdout, stderr));
485
555
  return;
486
556
  }
487
- if (Date.now() - lastChange >= stallThresholdMs)
557
+ const quietMs = now() - lastChange;
558
+ const mode = inferStallMode(stdout, stderr);
559
+ if (
560
+ quietMs >= earlyClassifyMs &&
561
+ (mode === "rate_limited" || mode === "auth_stall")
562
+ ) {
488
563
  void stopFor(inferStallMode(stdout, stderr));
564
+ return;
565
+ }
566
+ void lifecycle
567
+ .check({
568
+ pid: child.pid,
569
+ command,
570
+ workdir,
571
+ mode,
572
+ stdoutTail: stdout.slice(-2000),
573
+ stderrTail: stderr.slice(-2000),
574
+ })
575
+ .then((reason) => {
576
+ if (!reason) return;
577
+ void stopFor(reason === "hard_ceiling" ? "timeout" : mode);
578
+ });
489
579
  }, stallCheckIntervalMs);
490
- timeoutTimer.unref?.();
491
- stallTimer.unref?.();
580
+ timeoutTimer?.unref?.();
581
+ stallTimer?.unref?.();
492
582
 
493
583
  await new Promise((resolve) =>
494
584
  child.on("close", (code) => {
@@ -496,8 +586,8 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
496
586
  resolve();
497
587
  }),
498
588
  );
499
- clearTimeout(timeoutTimer);
500
- clearInterval(stallTimer);
589
+ if (timeoutTimer) cancelTimeout(timeoutTimer);
590
+ cancelInterval(stallTimer);
501
591
 
502
592
  const fileOutput =
503
593
  resultFile && existsSync(resultFile)
@@ -509,7 +599,7 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
509
599
  output,
510
600
  stderr,
511
601
  exitCode,
512
- duration: Date.now() - startedAt,
602
+ duration: now() - startedAt,
513
603
  failureMode: ok ? null : failureMode || "crash",
514
604
  });
515
605
  }
@@ -11,6 +11,7 @@ import {
11
11
  shellQuote,
12
12
  } from "./cli-adapter-base.mjs";
13
13
  import { runPreflight } from "./codex-preflight.mjs";
14
+ import { isActivityLifecycleEnabled } from "./lib/worker-lifecycle.mjs";
14
15
 
15
16
  // ── Codex-specific stall inference ──────────────────────────────
16
17
 
@@ -64,9 +65,10 @@ function buildAttempts(opts, preflight) {
64
65
  forceBypass: preflight.needsBypass,
65
66
  };
66
67
  if (opts.retryOnFail === false) return [base];
68
+ const retryTimeout = isActivityLifecycleEnabled() ? timeout : timeout * 2;
67
69
  return [
68
70
  base,
69
- { ...base, timeout: timeout * 2, excluded: requested, forceBypass: true },
71
+ { ...base, timeout: retryTimeout, excluded: requested, forceBypass: true },
70
72
  ];
71
73
  }
72
74
 
@@ -116,6 +118,9 @@ export function buildExecArgs(opts = {}) {
116
118
  const prompt = typeof opts.prompt === "string" ? opts.prompt : "";
117
119
  const command = buildExecCommand(prompt, opts.resultFile || null, {
118
120
  profile: opts.profile,
121
+ codexHome: opts.codexHome,
122
+ disallowUltra: opts.disallowUltra,
123
+ enforceCanonicalProfile: opts.enforceCanonicalProfile,
119
124
  skipGitRepoCheck: true,
120
125
  sandboxBypass: true,
121
126
  cwd: opts.cwd,
@@ -183,6 +188,8 @@ async function runCodex(prompt, workdir, preflight, attempt, lease) {
183
188
  resultFile,
184
189
  inferStallMode,
185
190
  spawnEnv,
191
+ cli: "codex",
192
+ codexHome: spawnEnv?.CODEX_HOME,
186
193
  });
187
194
  }
188
195
 
@@ -45,7 +45,7 @@ function makeFallback(policy, trigger, inputsHash, now) {
45
45
  {
46
46
  cli: fb.default_cli ?? "codex",
47
47
  accountId: null,
48
- model: fb.default_model ?? "gpt-5.5",
48
+ model: fb.default_model ?? "gpt-5.6-terra",
49
49
  riskTier: "safe",
50
50
  reason: trigger,
51
51
  },
@@ -9,6 +9,7 @@ import {
9
9
  normalizePathForShell,
10
10
  runProcess,
11
11
  } from "./cli-adapter-base.mjs";
12
+ import { isActivityLifecycleEnabled } from "./lib/worker-lifecycle.mjs";
12
13
  import { whichCommandAsync } from "./platform.mjs";
13
14
 
14
15
  function shellSingleQuote(value) {
@@ -88,7 +89,8 @@ function buildAttempts(opts, preflight) {
88
89
  excludeMcpServers: [...(preflight.excludeMcpServers || [])],
89
90
  };
90
91
  if (opts.retryOnFail === false) return [base];
91
- return [base, { ...base, timeout: timeout * 2, allowedMcpServers: [] }];
92
+ const retryTimeout = isActivityLifecycleEnabled() ? timeout : timeout * 2;
93
+ return [base, { ...base, timeout: retryTimeout, allowedMcpServers: [] }];
92
94
  }
93
95
 
94
96
  // ── Public: buildExecArgs ───────────────────────────────────────