triflux 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/CLAUDE.md +233 -0
- package/bin/tfx-live.mjs +3 -1
- package/bin/triflux.mjs +48 -1
- package/config/routing-policy.json +1 -1
- package/cto/hygiene-actions.mjs +355 -0
- package/cto/hygiene.mjs +79 -9
- package/cto/index.mjs +14 -2
- package/cto/steward.mjs +362 -0
- 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/pipe.mjs +11 -1
- package/hub/public/tray.html +50 -53
- package/hub/role-contract.mjs +314 -0
- package/hub/role-control-events.mjs +113 -0
- package/hub/router.mjs +135 -30
- package/hub/schema.sql +1 -0
- package/hub/server.mjs +89 -44
- package/hub/store.mjs +50 -19
- package/hub/team/claude-daemon-control.mjs +28 -15
- package/hub/team/cli/commands/start/parse-args.mjs +2 -2
- package/hub/team/headless.mjs +295 -24
- package/hub/team/intervention.mjs +530 -0
- package/hub/team/retry-state-machine.mjs +6 -2
- package/hub/team/swarm-hypervisor.mjs +20 -2
- package/hub/team/swarm-locks.mjs +17 -1
- package/hub/team/uds-orchestrator.mjs +62 -9
- package/hub/tools.mjs +9 -7
- package/hub/workers/codex-app-server-worker.mjs +54 -5
- package/hub/workers/codex-mcp.mjs +1 -1
- package/hub/workers/delegator-mcp.mjs +18 -18
- 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/__tests__/tfx-route-bash-node-parity.test.mjs +1 -1
- package/scripts/__tests__/tfx-route-node-entry.test.mjs +6 -6
- package/scripts/headless-guard.mjs +70 -23
- 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/scripts/pack.mjs +1 -0
- package/scripts/setup.mjs +15 -11
- package/scripts/tfx-gate-activate.mjs +1 -1
- package/scripts/tfx-route-worker.mjs +5 -0
- package/scripts/tfx-route.sh +136 -84
- package/skills/tfx-analysis/SKILL.md +3 -1
- package/skills/tfx-harness/SKILL.md +89 -0
- package/skills/tfx-hub/SKILL.md +1 -1
- package/skills/tfx-plan/SKILL.md +3 -1
- package/skills/tfx-profile/SKILL.md +9 -9
- package/skills/tfx-prune/SKILL.md +4 -2
- package/skills/tfx-qa/SKILL.md +6 -2
- package/skills/tfx-research/SKILL.md +3 -1
- package/skills/tfx-review/SKILL.md +5 -3
- package/skills/tfx-setup/SKILL.md +1 -1
- package/tui/codex-profile.mjs +3 -1
- package/tui/setup.mjs +4 -4
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
|
+
}
|
package/hub/lib/memory-store.mjs
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// hub/lib/memory-store.mjs — In-memory store (SQLite-free fallback for @triflux/core)
|
|
2
2
|
|
|
3
3
|
import { recalcConfidence } from "../reflexion.mjs";
|
|
4
|
+
import {
|
|
5
|
+
clampDurationMs,
|
|
6
|
+
DEFAULT_ASSIGN_TIMEOUT_MS,
|
|
7
|
+
} from "./timeout-defaults.mjs";
|
|
4
8
|
import { uuidv7 } from "./uuidv7.mjs";
|
|
5
9
|
|
|
6
10
|
export function clone(value) {
|
|
@@ -19,12 +23,6 @@ function clampPriority(value, fallback = 5) {
|
|
|
19
23
|
return Math.max(1, Math.min(Math.trunc(num), 9));
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
function clampDuration(value, fallback = 600000, min = 1000, max = 86400000) {
|
|
23
|
-
const num = Number(value);
|
|
24
|
-
if (!Number.isFinite(num)) return fallback;
|
|
25
|
-
return Math.max(min, Math.min(Math.trunc(num), max));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
26
|
export function clampConfidence(value, fallback = 0.5) {
|
|
29
27
|
const num = Number(value);
|
|
30
28
|
if (!Number.isFinite(num)) return fallback;
|
|
@@ -177,6 +175,15 @@ export function createMemoryStore() {
|
|
|
177
175
|
lease_id: uuidv7(),
|
|
178
176
|
lease_expires_ms: next.lease_expires_ms,
|
|
179
177
|
server_time_ms: now,
|
|
178
|
+
effective: {
|
|
179
|
+
updated: true,
|
|
180
|
+
online: next.status === "online",
|
|
181
|
+
status: next.status,
|
|
182
|
+
last_seen_ms: next.last_seen_ms,
|
|
183
|
+
lease_expires_ms: next.lease_expires_ms,
|
|
184
|
+
store_type: "memory",
|
|
185
|
+
db_path: null,
|
|
186
|
+
},
|
|
180
187
|
};
|
|
181
188
|
},
|
|
182
189
|
|
|
@@ -191,6 +198,7 @@ export function createMemoryStore() {
|
|
|
191
198
|
agent_id: agentId,
|
|
192
199
|
lease_expires_ms: Date.now() + ttlMs,
|
|
193
200
|
server_time_ms: Date.now(),
|
|
201
|
+
effective: { updated: false, store_type: "memory", db_path: null },
|
|
194
202
|
};
|
|
195
203
|
const now = Date.now();
|
|
196
204
|
current.last_seen_ms = now;
|
|
@@ -200,6 +208,7 @@ export function createMemoryStore() {
|
|
|
200
208
|
agent_id: agentId,
|
|
201
209
|
lease_expires_ms: current.lease_expires_ms,
|
|
202
210
|
server_time_ms: now,
|
|
211
|
+
effective: { updated: true, store_type: "memory", db_path: null },
|
|
203
212
|
};
|
|
204
213
|
},
|
|
205
214
|
|
|
@@ -455,9 +464,10 @@ export function createMemoryStore() {
|
|
|
455
464
|
retry_count = 0,
|
|
456
465
|
max_retries = 0,
|
|
457
466
|
priority = 5,
|
|
458
|
-
ttl_ms =
|
|
459
|
-
timeout_ms =
|
|
467
|
+
ttl_ms = DEFAULT_ASSIGN_TIMEOUT_MS,
|
|
468
|
+
timeout_ms = DEFAULT_ASSIGN_TIMEOUT_MS,
|
|
460
469
|
deadline_ms,
|
|
470
|
+
dispatched_at_ms = null,
|
|
461
471
|
trace_id,
|
|
462
472
|
correlation_id,
|
|
463
473
|
last_message_id = null,
|
|
@@ -465,7 +475,10 @@ export function createMemoryStore() {
|
|
|
465
475
|
error = null,
|
|
466
476
|
}) {
|
|
467
477
|
const now = Date.now();
|
|
468
|
-
const normalizedTimeout =
|
|
478
|
+
const normalizedTimeout = clampDurationMs(
|
|
479
|
+
timeout_ms,
|
|
480
|
+
DEFAULT_ASSIGN_TIMEOUT_MS,
|
|
481
|
+
);
|
|
469
482
|
const row = {
|
|
470
483
|
job_id: job_id || uuidv7(),
|
|
471
484
|
supervisor_agent,
|
|
@@ -478,7 +491,7 @@ export function createMemoryStore() {
|
|
|
478
491
|
retry_count: Math.max(0, Number(retry_count) || 0),
|
|
479
492
|
max_retries: Math.max(0, Number(max_retries) || 0),
|
|
480
493
|
priority: clampPriority(priority, 5),
|
|
481
|
-
ttl_ms:
|
|
494
|
+
ttl_ms: clampDurationMs(ttl_ms, normalizedTimeout),
|
|
482
495
|
timeout_ms: normalizedTimeout,
|
|
483
496
|
deadline_ms: Number.isFinite(Number(deadline_ms))
|
|
484
497
|
? Math.trunc(Number(deadline_ms))
|
|
@@ -491,6 +504,10 @@ export function createMemoryStore() {
|
|
|
491
504
|
created_at_ms: now,
|
|
492
505
|
updated_at_ms: now,
|
|
493
506
|
started_at_ms: status === "running" ? now : null,
|
|
507
|
+
dispatched_at_ms:
|
|
508
|
+
dispatched_at_ms == null || !Number.isFinite(Number(dispatched_at_ms))
|
|
509
|
+
? null
|
|
510
|
+
: Math.trunc(Number(dispatched_at_ms)),
|
|
494
511
|
completed_at_ms: ["succeeded", "failed", "timed_out"].includes(status)
|
|
495
512
|
? now
|
|
496
513
|
: null,
|
|
@@ -515,7 +532,7 @@ export function createMemoryStore() {
|
|
|
515
532
|
const isTerminal = ["succeeded", "failed", "timed_out"].includes(
|
|
516
533
|
nextStatus,
|
|
517
534
|
);
|
|
518
|
-
const nextTimeout =
|
|
535
|
+
const nextTimeout = clampDurationMs(
|
|
519
536
|
patch.timeout_ms ?? current.timeout_ms,
|
|
520
537
|
current.timeout_ms,
|
|
521
538
|
);
|
|
@@ -543,7 +560,7 @@ export function createMemoryStore() {
|
|
|
543
560
|
patch.priority ?? current.priority,
|
|
544
561
|
current.priority || 5,
|
|
545
562
|
),
|
|
546
|
-
ttl_ms:
|
|
563
|
+
ttl_ms: clampDurationMs(
|
|
547
564
|
patch.ttl_ms ?? current.ttl_ms,
|
|
548
565
|
current.ttl_ms || nextTimeout,
|
|
549
566
|
),
|
|
@@ -580,6 +597,9 @@ export function createMemoryStore() {
|
|
|
580
597
|
: nextStatus === "running"
|
|
581
598
|
? current.started_at_ms || now
|
|
582
599
|
: current.started_at_ms,
|
|
600
|
+
dispatched_at_ms: Object.hasOwn(patch, "dispatched_at_ms")
|
|
601
|
+
? patch.dispatched_at_ms
|
|
602
|
+
: current.dispatched_at_ms,
|
|
583
603
|
completed_at_ms: Object.hasOwn(patch, "completed_at_ms")
|
|
584
604
|
? patch.completed_at_ms
|
|
585
605
|
: isTerminal
|
|
@@ -645,7 +665,7 @@ export function createMemoryStore() {
|
|
|
645
665
|
current.attempt + 1,
|
|
646
666
|
Number(patch.attempt ?? current.attempt + 1) || 1,
|
|
647
667
|
);
|
|
648
|
-
const nextTimeout =
|
|
668
|
+
const nextTimeout = clampDurationMs(
|
|
649
669
|
patch.timeout_ms ?? current.timeout_ms,
|
|
650
670
|
current.timeout_ms,
|
|
651
671
|
);
|
|
@@ -657,6 +677,7 @@ export function createMemoryStore() {
|
|
|
657
677
|
deadline_ms: Date.now() + nextTimeout,
|
|
658
678
|
completed_at_ms: null,
|
|
659
679
|
started_at_ms: null,
|
|
680
|
+
dispatched_at_ms: null,
|
|
660
681
|
last_retry_at_ms: Date.now(),
|
|
661
682
|
result: patch.result ?? null,
|
|
662
683
|
error: Object.hasOwn(patch, "error") ? patch.error : current.error,
|