@triflux/core 10.41.1 → 10.43.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/hooks/keyword-rules.json +39 -5
- package/hooks/pipeline-stop.mjs +1 -1
- package/hub/bridge.mjs +80 -1
- package/hub/cli-adapter-base.mjs +100 -19
- package/hub/codex-adapter.mjs +5 -1
- package/hub/dynamic-routing-engine.mjs +1 -1
- package/hub/gemini-adapter.mjs +3 -1
- package/hub/intent.mjs +13 -9
- package/hub/lib/cto-env.mjs +94 -0
- package/hub/lib/memory-store.mjs +34 -13
- package/hub/lib/timeout-defaults.mjs +45 -0
- package/hub/lib/worker-lifecycle.mjs +101 -0
- package/hub/log-retention.mjs +726 -0
- package/hub/middleware/request-logger.mjs +4 -1
- package/hub/role-contract.mjs +314 -0
- package/hub/role-control-events.mjs +113 -0
- package/hub/router.mjs +135 -30
- package/hub/team/claude-daemon-control.mjs +28 -15
- package/hub/team/intervention.mjs +530 -0
- package/hub/team/retry-state-machine.mjs +6 -2
- package/hud/constants.mjs +21 -0
- package/hud/context-monitor.mjs +18 -21
- package/hud/hud-qos-status.mjs +1 -1
- package/hud/providers/codex-probe.mjs +216 -0
- package/hud/providers/codex.mjs +206 -29
- package/hud/renderers.mjs +6 -9
- package/package.json +1 -1
- package/scripts/lib/cli-agy.mjs +1 -0
- package/scripts/lib/cli-claude.mjs +1 -0
- package/scripts/lib/cli-codex.mjs +22 -21
- package/scripts/lib/codex-profile-config.mjs +2 -2
package/hooks/keyword-rules.json
CHANGED
|
@@ -62,6 +62,40 @@
|
|
|
62
62
|
"state": null,
|
|
63
63
|
"mcp_route": null
|
|
64
64
|
},
|
|
65
|
+
{
|
|
66
|
+
"id": "tfx-harness",
|
|
67
|
+
"patterns": [
|
|
68
|
+
{
|
|
69
|
+
"source": "\\btfx[\\s-]?harness\\b",
|
|
70
|
+
"flags": "i"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"skill": "tfx-harness",
|
|
74
|
+
"priority": 1,
|
|
75
|
+
"supersedes": ["tfx-unified"],
|
|
76
|
+
"exclusive": false,
|
|
77
|
+
"state": null,
|
|
78
|
+
"mcp_route": null
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "tfx-harness-meta",
|
|
82
|
+
"patterns": [
|
|
83
|
+
{
|
|
84
|
+
"source": "(?:어떤\\s*스킬|스킬\\s*추천|스킬\\s*라우팅|작업\\s*라우팅|메타\\s*라우팅)",
|
|
85
|
+
"flags": "i"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"source": "\\bwhich\\s+skill\\b",
|
|
89
|
+
"flags": "i"
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"skill": "tfx-harness",
|
|
93
|
+
"priority": 2,
|
|
94
|
+
"supersedes": ["tfx-unified"],
|
|
95
|
+
"exclusive": false,
|
|
96
|
+
"state": null,
|
|
97
|
+
"mcp_route": null
|
|
98
|
+
},
|
|
65
99
|
{
|
|
66
100
|
"id": "tfx-unified",
|
|
67
101
|
"patterns": [
|
|
@@ -160,7 +194,7 @@
|
|
|
160
194
|
}
|
|
161
195
|
],
|
|
162
196
|
"skill": "tfx-hub",
|
|
163
|
-
"priority":
|
|
197
|
+
"priority": 1,
|
|
164
198
|
"supersedes": [],
|
|
165
199
|
"exclusive": false,
|
|
166
200
|
"state": null,
|
|
@@ -175,7 +209,7 @@
|
|
|
175
209
|
}
|
|
176
210
|
],
|
|
177
211
|
"skill": "tfx-doctor",
|
|
178
|
-
"priority":
|
|
212
|
+
"priority": 1,
|
|
179
213
|
"supersedes": [],
|
|
180
214
|
"exclusive": false,
|
|
181
215
|
"state": null,
|
|
@@ -190,7 +224,7 @@
|
|
|
190
224
|
}
|
|
191
225
|
],
|
|
192
226
|
"skill": "tfx-setup",
|
|
193
|
-
"priority":
|
|
227
|
+
"priority": 1,
|
|
194
228
|
"supersedes": [],
|
|
195
229
|
"exclusive": false,
|
|
196
230
|
"state": null,
|
|
@@ -205,7 +239,7 @@
|
|
|
205
239
|
}
|
|
206
240
|
],
|
|
207
241
|
"skill": "tfx-research",
|
|
208
|
-
"priority":
|
|
242
|
+
"priority": 1,
|
|
209
243
|
"supersedes": [],
|
|
210
244
|
"exclusive": false,
|
|
211
245
|
"state": null,
|
|
@@ -220,7 +254,7 @@
|
|
|
220
254
|
}
|
|
221
255
|
],
|
|
222
256
|
"skill": "tfx-interview",
|
|
223
|
-
"priority":
|
|
257
|
+
"priority": 1,
|
|
224
258
|
"supersedes": [],
|
|
225
259
|
"exclusive": false,
|
|
226
260
|
"state": null,
|
package/hooks/pipeline-stop.mjs
CHANGED
|
@@ -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
|
|
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
|
}
|
package/hub/cli-adapter-base.mjs
CHANGED
|
@@ -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
|
|
|
@@ -397,19 +404,36 @@ export async function terminateChild(pid, opts = {}) {
|
|
|
397
404
|
*
|
|
398
405
|
* @param {string} command — shell command to run
|
|
399
406
|
* @param {string} workdir — cwd for the child process
|
|
400
|
-
* @param {number} timeout —
|
|
407
|
+
* @param {number} timeout — legacy wall-clock duration (TFX_ACTIVITY_LIFECYCLE=0 only)
|
|
401
408
|
* @param {object} [opts]
|
|
402
409
|
* @param {string} [opts.resultFile] — file to read output from (if CLI writes there)
|
|
403
410
|
* @param {function} [opts.inferStallMode] — (stdout, stderr) => string. Default: () => 'timeout'
|
|
404
411
|
* @param {number} [opts.stallCheckIntervalMs] — stall check interval (default 10_000)
|
|
405
|
-
* @param {number} [opts.stallThresholdMs] —
|
|
412
|
+
* @param {number} [opts.stallThresholdMs] — inactivity threshold
|
|
413
|
+
* @param {number} [opts.hardCeilingMs] — activity-independent maximum duration
|
|
414
|
+
* @param {(context: object) => Promise<boolean|string>|boolean|string} [opts.onStallIntervene]
|
|
415
|
+
* T5 intervention seam; true or "resolved" keeps the process alive
|
|
416
|
+
* @param {object} [opts.deps] — clock/process/timer overrides for deterministic tests
|
|
406
417
|
* @returns {Promise<object>} createResult-shaped object
|
|
407
418
|
*/
|
|
408
419
|
export async function runProcess(command, workdir, timeout, opts = {}) {
|
|
409
|
-
const
|
|
420
|
+
const deps = opts.deps || {};
|
|
421
|
+
const now = deps.now || Date.now;
|
|
422
|
+
const spawnProcess = deps.spawn || spawn;
|
|
423
|
+
const scheduleTimeout = deps.setTimeout || setTimeout;
|
|
424
|
+
const cancelTimeout = deps.clearTimeout || clearTimeout;
|
|
425
|
+
const scheduleInterval = deps.setInterval || setInterval;
|
|
426
|
+
const cancelInterval = deps.clearInterval || clearInterval;
|
|
427
|
+
const terminateProcess = deps.terminateChild || terminateChild;
|
|
428
|
+
const startedAt = now();
|
|
410
429
|
const inferStallMode = opts.inferStallMode || (() => "timeout");
|
|
411
430
|
const stallCheckIntervalMs = opts.stallCheckIntervalMs ?? 10_000;
|
|
412
|
-
const
|
|
431
|
+
const legacyLifecycle = !isActivityLifecycleEnabled();
|
|
432
|
+
const stallThresholdMs =
|
|
433
|
+
opts.stallThresholdMs ??
|
|
434
|
+
(legacyLifecycle ? 30_000 : resolveStallInterventionMs());
|
|
435
|
+
const hardCeilingMs = opts.hardCeilingMs ?? resolveHardCeilingMs();
|
|
436
|
+
const earlyClassifyMs = opts.earlyClassifyMs ?? 30_000;
|
|
413
437
|
const resultFile = opts.resultFile || null;
|
|
414
438
|
|
|
415
439
|
let stdout = "";
|
|
@@ -421,7 +445,7 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
|
|
|
421
445
|
try {
|
|
422
446
|
// PRD A1: opts.spawnEnv 가 있으면 그 env 로 spawn (lease 의 authFile/env 적용).
|
|
423
447
|
// undefined 면 spawn 의 default 동작 (부모 process env inherit) 유지.
|
|
424
|
-
child =
|
|
448
|
+
child = spawnProcess(command, {
|
|
425
449
|
cwd: workdir,
|
|
426
450
|
shell: true,
|
|
427
451
|
windowsHide: true,
|
|
@@ -430,7 +454,7 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
|
|
|
430
454
|
} catch (error) {
|
|
431
455
|
return createResult(false, {
|
|
432
456
|
stderr: String(error?.message || error),
|
|
433
|
-
duration:
|
|
457
|
+
duration: now() - startedAt,
|
|
434
458
|
});
|
|
435
459
|
}
|
|
436
460
|
|
|
@@ -446,9 +470,40 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
|
|
|
446
470
|
|
|
447
471
|
let lastBytes = 0;
|
|
448
472
|
let lastResultFileSignature = resultFileSignature();
|
|
449
|
-
let lastChange =
|
|
473
|
+
let lastChange = now();
|
|
474
|
+
const activitySignature = () =>
|
|
475
|
+
`${Buffer.byteLength(stdout) + Buffer.byteLength(stderr)}:${resultFileSignature()}`;
|
|
476
|
+
let ladder = null;
|
|
477
|
+
const lifecycle = createActivityLifecycle({
|
|
478
|
+
enabled: !legacyLifecycle,
|
|
479
|
+
interventionMs: stallThresholdMs,
|
|
480
|
+
hardCeilingMs,
|
|
481
|
+
now,
|
|
482
|
+
onIntervene: async (context) => {
|
|
483
|
+
if (typeof opts.onStallIntervene === "function") {
|
|
484
|
+
const result = await opts.onStallIntervene(context);
|
|
485
|
+
return result === true || result === "resolved";
|
|
486
|
+
}
|
|
487
|
+
ladder ??= createInterventionLadder({
|
|
488
|
+
target: {
|
|
489
|
+
pid: child.pid,
|
|
490
|
+
cli: opts.cli,
|
|
491
|
+
codexHome: opts.codexHome,
|
|
492
|
+
rolloutFile: opts.rolloutFile,
|
|
493
|
+
},
|
|
494
|
+
readActivitySignature: activitySignature,
|
|
495
|
+
deps:
|
|
496
|
+
typeof opts.resumeHandler === "function"
|
|
497
|
+
? { resumeHandler: opts.resumeHandler }
|
|
498
|
+
: {},
|
|
499
|
+
});
|
|
500
|
+
const result = await ladder.intervene();
|
|
501
|
+
return result?.outcome === "reactivated" || result?.outcome === "resumed";
|
|
502
|
+
},
|
|
503
|
+
});
|
|
450
504
|
const touch = () => {
|
|
451
|
-
lastChange =
|
|
505
|
+
lastChange = now();
|
|
506
|
+
lifecycle.observe(activitySignature());
|
|
452
507
|
};
|
|
453
508
|
child.stdout?.on("data", (chunk) => {
|
|
454
509
|
stdout += String(chunk);
|
|
@@ -466,13 +521,15 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
|
|
|
466
521
|
const stopFor = async (mode) => {
|
|
467
522
|
if (failureMode) return;
|
|
468
523
|
failureMode = mode;
|
|
469
|
-
await
|
|
524
|
+
await terminateProcess(child.pid);
|
|
470
525
|
};
|
|
471
526
|
|
|
472
|
-
const timeoutTimer =
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
527
|
+
const timeoutTimer = legacyLifecycle
|
|
528
|
+
? scheduleTimeout(() => {
|
|
529
|
+
void stopFor("timeout");
|
|
530
|
+
}, timeout)
|
|
531
|
+
: null;
|
|
532
|
+
const stallTimer = scheduleInterval(() => {
|
|
476
533
|
const size = Buffer.byteLength(stdout) + Buffer.byteLength(stderr);
|
|
477
534
|
const currentResultFileSignature = resultFileSignature();
|
|
478
535
|
if (
|
|
@@ -482,13 +539,37 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
|
|
|
482
539
|
lastBytes = size;
|
|
483
540
|
lastResultFileSignature = currentResultFileSignature;
|
|
484
541
|
touch();
|
|
542
|
+
}
|
|
543
|
+
if (legacyLifecycle) {
|
|
544
|
+
if (now() - lastChange >= stallThresholdMs)
|
|
545
|
+
void stopFor(inferStallMode(stdout, stderr));
|
|
485
546
|
return;
|
|
486
547
|
}
|
|
487
|
-
|
|
548
|
+
const quietMs = now() - lastChange;
|
|
549
|
+
const mode = inferStallMode(stdout, stderr);
|
|
550
|
+
if (
|
|
551
|
+
quietMs >= earlyClassifyMs &&
|
|
552
|
+
(mode === "rate_limited" || mode === "auth_stall")
|
|
553
|
+
) {
|
|
488
554
|
void stopFor(inferStallMode(stdout, stderr));
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
void lifecycle
|
|
558
|
+
.check({
|
|
559
|
+
pid: child.pid,
|
|
560
|
+
command,
|
|
561
|
+
workdir,
|
|
562
|
+
mode,
|
|
563
|
+
stdoutTail: stdout.slice(-2000),
|
|
564
|
+
stderrTail: stderr.slice(-2000),
|
|
565
|
+
})
|
|
566
|
+
.then((reason) => {
|
|
567
|
+
if (!reason) return;
|
|
568
|
+
void stopFor(reason === "hard_ceiling" ? "timeout" : mode);
|
|
569
|
+
});
|
|
489
570
|
}, stallCheckIntervalMs);
|
|
490
|
-
timeoutTimer
|
|
491
|
-
stallTimer
|
|
571
|
+
timeoutTimer?.unref?.();
|
|
572
|
+
stallTimer?.unref?.();
|
|
492
573
|
|
|
493
574
|
await new Promise((resolve) =>
|
|
494
575
|
child.on("close", (code) => {
|
|
@@ -496,8 +577,8 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
|
|
|
496
577
|
resolve();
|
|
497
578
|
}),
|
|
498
579
|
);
|
|
499
|
-
|
|
500
|
-
|
|
580
|
+
if (timeoutTimer) cancelTimeout(timeoutTimer);
|
|
581
|
+
cancelInterval(stallTimer);
|
|
501
582
|
|
|
502
583
|
const fileOutput =
|
|
503
584
|
resultFile && existsSync(resultFile)
|
|
@@ -509,7 +590,7 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
|
|
|
509
590
|
output,
|
|
510
591
|
stderr,
|
|
511
592
|
exitCode,
|
|
512
|
-
duration:
|
|
593
|
+
duration: now() - startedAt,
|
|
513
594
|
failureMode: ok ? null : failureMode || "crash",
|
|
514
595
|
});
|
|
515
596
|
}
|
package/hub/codex-adapter.mjs
CHANGED
|
@@ -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:
|
|
71
|
+
{ ...base, timeout: retryTimeout, excluded: requested, forceBypass: true },
|
|
70
72
|
];
|
|
71
73
|
}
|
|
72
74
|
|
|
@@ -183,6 +185,8 @@ async function runCodex(prompt, workdir, preflight, attempt, lease) {
|
|
|
183
185
|
resultFile,
|
|
184
186
|
inferStallMode,
|
|
185
187
|
spawnEnv,
|
|
188
|
+
cli: "codex",
|
|
189
|
+
codexHome: spawnEnv?.CODEX_HOME,
|
|
186
190
|
});
|
|
187
191
|
}
|
|
188
192
|
|
package/hub/gemini-adapter.mjs
CHANGED
|
@@ -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
|
-
|
|
92
|
+
const retryTimeout = isActivityLifecycleEnabled() ? timeout : timeout * 2;
|
|
93
|
+
return [base, { ...base, timeout: retryTimeout, allowedMcpServers: [] }];
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
// ── Public: buildExecArgs ───────────────────────────────────────
|
package/hub/intent.mjs
CHANGED
|
@@ -62,21 +62,25 @@ function _tryCodexClassify(prompt) {
|
|
|
62
62
|
*/
|
|
63
63
|
export const INTENT_CATEGORIES = {
|
|
64
64
|
// 모델 정책 (2026-04-25):
|
|
65
|
-
// -
|
|
65
|
+
// - GPT-5.6 family = 기존 semantic tier를 Sol/Terra/Luna lane으로 매핑
|
|
66
66
|
// - gpt-5.4-mini = 자잘/부가/가성비 (fast tier 가능)
|
|
67
67
|
// - gpt-5.3-codex = escalation 가성비 중간 (fast 미지원)
|
|
68
|
-
// -
|
|
69
|
-
implement: {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
// - 기존 effort는 유지하고 모델만 GPT-5.6 family로 교체
|
|
69
|
+
implement: {
|
|
70
|
+
agent: "executor",
|
|
71
|
+
mcp: "implement",
|
|
72
|
+
effort: "gpt56_terra_high",
|
|
73
|
+
},
|
|
74
|
+
debug: { agent: "debugger", mcp: "implement", effort: "gpt56_sol_xhigh" },
|
|
75
|
+
analyze: { agent: "analyst", mcp: "analyze", effort: "gpt56_sol_xhigh" },
|
|
76
|
+
design: { agent: "architect", mcp: "analyze", effort: "gpt56_sol_xhigh" },
|
|
77
|
+
review: { agent: "code-reviewer", mcp: "review", effort: "gpt56_terra_high" },
|
|
74
78
|
document: { agent: "writer", mcp: "docs", effort: "pro" },
|
|
75
|
-
research: { agent: "scientist", mcp: "analyze", effort: "
|
|
79
|
+
research: { agent: "scientist", mcp: "analyze", effort: "gpt56_terra_high" },
|
|
76
80
|
"quick-fix": {
|
|
77
81
|
agent: "build-fixer",
|
|
78
82
|
mcp: "implement",
|
|
79
|
-
effort: "
|
|
83
|
+
effort: "gpt56_luna_low",
|
|
80
84
|
},
|
|
81
85
|
explain: { agent: "writer", mcp: "docs", effort: "flash" },
|
|
82
86
|
test: { agent: "test-engineer", mcp: null, effort: null },
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const OFF_VALUES = new Set(["0", "false", "off", "no"]);
|
|
2
|
+
const ON_VALUES = new Set(["1", "true", "on", "yes"]);
|
|
3
|
+
|
|
4
|
+
function normalizeEnvValue(name) {
|
|
5
|
+
return String(process.env[name] ?? "")
|
|
6
|
+
.trim()
|
|
7
|
+
.toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isExplicitlyOff(name) {
|
|
11
|
+
return OFF_VALUES.has(normalizeEnvValue(name));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isExplicitlyOn(name) {
|
|
15
|
+
return ON_VALUES.has(normalizeEnvValue(name));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeEnv(env, name) {
|
|
19
|
+
return String(env?.[name] ?? "")
|
|
20
|
+
.trim()
|
|
21
|
+
.toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isEnvOff(env, name) {
|
|
25
|
+
return OFF_VALUES.has(normalizeEnv(env, name));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isEnvOn(env, name) {
|
|
29
|
+
return ON_VALUES.has(normalizeEnv(env, name));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the C6a control plane without mutating environment or runtime
|
|
34
|
+
* state. Callers own storing and incrementing the returned generation.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveRoleControlSnapshot(env = process.env, options = {}) {
|
|
37
|
+
const masterEnabled = !isEnvOff(env, "TFX_CTO");
|
|
38
|
+
const ctoManagerEnabled = masterEnabled && isEnvOn(env, "TFX_CTO_MANAGER");
|
|
39
|
+
const leadManagerEnabled =
|
|
40
|
+
ctoManagerEnabled && isEnvOn(env, "TFX_LEAD_MANAGER");
|
|
41
|
+
const northStarEnabled =
|
|
42
|
+
masterEnabled && !isEnvOff(env, "TFX_CTO_NORTH_STAR");
|
|
43
|
+
const autoCollectEnabled =
|
|
44
|
+
masterEnabled && !isEnvOff(env, "TFX_CTO_AUTO_COLLECT");
|
|
45
|
+
const values = {
|
|
46
|
+
master_enabled: masterEnabled,
|
|
47
|
+
cto_manager_enabled: ctoManagerEnabled,
|
|
48
|
+
lead_manager_enabled: leadManagerEnabled,
|
|
49
|
+
north_star_enabled: northStarEnabled,
|
|
50
|
+
auto_collect_enabled: autoCollectEnabled,
|
|
51
|
+
};
|
|
52
|
+
const previous = options.previous;
|
|
53
|
+
const changed = Object.keys(values).some(
|
|
54
|
+
(field) => previous?.[field] !== values[field],
|
|
55
|
+
);
|
|
56
|
+
const generation = previous
|
|
57
|
+
? Number(previous.generation || 0) + (changed ? 1 : 0)
|
|
58
|
+
: Number.isSafeInteger(options.generation) && options.generation >= 0
|
|
59
|
+
? options.generation
|
|
60
|
+
: 0;
|
|
61
|
+
return { generation, ...values };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isCtoDisabled() {
|
|
65
|
+
return isExplicitlyOff("TFX_CTO");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function isCtoManagerEnabled() {
|
|
69
|
+
if (isCtoDisabled()) return false;
|
|
70
|
+
return isExplicitlyOn("TFX_CTO_MANAGER");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getCtoHygieneApplyMode() {
|
|
74
|
+
if (isExplicitlyOff("TFX_CTO")) return "off";
|
|
75
|
+
return normalizeEnvValue("TFX_CTO_HYGIENE_APPLY") === "archive"
|
|
76
|
+
? "archive"
|
|
77
|
+
: "off";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function isCtoRetentionEnabled() {
|
|
81
|
+
if (isExplicitlyOff("TFX_CTO")) return false;
|
|
82
|
+
return isExplicitlyOn("TFX_CTO_RETENTION");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getCtoMode() {
|
|
86
|
+
return normalizeEnvValue("TFX_CTO_MODE") === "bridge" ? "bridge" : "bounded";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getCtoMaxTokens() {
|
|
90
|
+
const raw = normalizeEnvValue("TFX_CTO_MAX_TOKENS");
|
|
91
|
+
if (!raw) return 0;
|
|
92
|
+
const value = Number(raw);
|
|
93
|
+
return Number.isSafeInteger(value) && value > 0 ? value : 0;
|
|
94
|
+
}
|