triflux 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.
- package/README.md +2 -0
- package/adapters/codex/skills/tfx-harness/SKILL.md +30 -0
- package/adapters/codex/skills/tfx-harness/skill.json +5 -0
- package/bin/tfx-live.mjs +3 -1
- package/bin/triflux.mjs +14 -0
- package/config/routing-policy.json +1 -1
- package/hooks/keyword-rules.json +139 -19
- package/hooks/pipeline-stop.mjs +1 -1
- package/hub/bridge.mjs +80 -1
- package/hub/cli-adapter-base.mjs +111 -21
- package/hub/codex-adapter.mjs +8 -1
- package/hub/dynamic-routing-engine.mjs +1 -1
- package/hub/gemini-adapter.mjs +3 -1
- package/hub/intent.mjs +24 -28
- package/hub/lib/cto-env.mjs +46 -0
- package/hub/lib/memory-store.mjs +25 -13
- package/hub/lib/timeout-defaults.mjs +45 -0
- package/hub/lib/worker-lifecycle.mjs +101 -0
- package/hub/middleware/request-logger.mjs +4 -1
- package/hub/pipe.mjs +9 -0
- package/hub/role-contract.mjs +314 -0
- package/hub/role-control-events.mjs +113 -0
- package/hub/router.mjs +383 -29
- package/hub/schema.sql +62 -2
- package/hub/server.mjs +130 -43
- package/hub/store.mjs +609 -19
- package/hub/team/claude-daemon-control.mjs +28 -15
- package/hub/team/cli/commands/start/parse-args.mjs +2 -2
- package/hub/team/conductor.mjs +23 -2
- package/hub/team/execution-mode.mjs +12 -2
- package/hub/team/headless.mjs +311 -24
- package/hub/team/intervention.mjs +530 -0
- package/hub/team/retry-state-machine.mjs +12 -3
- package/hub/team/swarm-hypervisor.mjs +21 -2
- package/hub/team/swarm-locks.mjs +17 -1
- package/hub/team/swarm-preflight.mjs +47 -0
- package/hub/team/synapse-http.mjs +149 -4
- package/hub/team/uds-orchestrator.mjs +62 -9
- package/hub/team/worktree-lifecycle.mjs +56 -59
- package/hub/tools.mjs +19 -3
- package/hub/workers/codex-app-server-worker.mjs +54 -5
- package/hub/workers/codex-mcp.mjs +21 -5
- package/hub/workers/delegator-mcp.mjs +3 -49
- 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 +2 -1
- package/scripts/__tests__/lint-skills.test.mjs +68 -0
- package/scripts/__tests__/tfx-route-bash-node-parity.test.mjs +1 -1
- package/scripts/__tests__/tfx-route-node-entry.test.mjs +42 -6
- package/scripts/headless-guard.mjs +70 -23
- package/scripts/keyword-detector.mjs +55 -8
- package/scripts/lib/agent-route-policy.mjs +360 -0
- package/scripts/lib/cli-agy.mjs +1 -0
- package/scripts/lib/cli-claude.mjs +1 -0
- package/scripts/lib/cli-codex.mjs +23 -160
- package/scripts/lib/codex-profile-config.mjs +31 -5
- package/scripts/lib/keyword-rules.mjs +12 -0
- package/scripts/lint-skills.mjs +65 -0
- package/scripts/pack.mjs +13 -0
- package/scripts/release/check-packages-mirror.mjs +5 -0
- package/scripts/setup.mjs +108 -12
- package/scripts/tfx-gate-activate.mjs +1 -1
- package/scripts/tfx-route-worker.mjs +5 -0
- package/scripts/tfx-route.mjs +14 -1
- package/scripts/tfx-route.sh +333 -118
- package/skills/tfx-analysis/SKILL.md +3 -1
- package/skills/tfx-harness/SKILL.md +19 -76
- package/skills/tfx-hub/SKILL.md +2 -2
- package/skills/tfx-interview/SKILL.md +3 -1
- package/skills/tfx-interview/skill.json +1 -13
- package/skills/tfx-plan/SKILL.md +3 -1
- package/skills/tfx-profile/SKILL.md +32 -22
- package/skills/tfx-prune/SKILL.md +7 -3
- package/skills/tfx-prune/skill.json +1 -8
- 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 +14 -3
- package/tui/setup.mjs +6 -4
package/README.md
CHANGED
|
@@ -264,6 +264,8 @@ state without replaying the entire conversation.
|
|
|
264
264
|
|
|
265
265
|
## Architecture
|
|
266
266
|
|
|
267
|
+
> 상세 구조·패키지 레이아웃·실행 경로는 [ARCHITECTURE.md](ARCHITECTURE.md), 문서 전체 지도는 [docs/README.md](docs/README.md) 참조.
|
|
268
|
+
|
|
267
269
|
<p align="center">
|
|
268
270
|
<img src="docs/assets/architecture.svg" alt="triflux architecture" width="680">
|
|
269
271
|
</p>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tfx-harness
|
|
3
|
+
description: >
|
|
4
|
+
Use for meta-routing questions. Read the canonical routing SSOT and return
|
|
5
|
+
exactly one branch and immediate owner; do not execute the requested work.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# tfx-harness — Codex adapter
|
|
9
|
+
|
|
10
|
+
Read `.claude/rules/tfx-routing.md` D0–D11 before routing. This adapter only
|
|
11
|
+
translates host syntax: use `$skill` for an available owner. Do not copy the
|
|
12
|
+
routing tree, keyword rules, or an owner matrix here.
|
|
13
|
+
|
|
14
|
+
Return:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
[tfx-harness]
|
|
18
|
+
branch: D<n>
|
|
19
|
+
owner: <exactly one immediate owner>
|
|
20
|
+
availability: available | unavailable | unknown
|
|
21
|
+
fallback_notice: <optional>
|
|
22
|
+
status: recommendation-only | dispatching | blocked
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- Never choose more than one branch or immediate owner.
|
|
26
|
+
- With unknown availability, do not infer a fallback. With measured
|
|
27
|
+
unavailability, state `owner unavailable → tfx-X fallback` before fallback.
|
|
28
|
+
- If the SSOT cannot be read, return `blocked: routing SSOT unavailable`.
|
|
29
|
+
- For a “which skill/path?” question, remain recommendation-only.
|
|
30
|
+
- Do not select downstream implementation agents or models.
|
package/bin/tfx-live.mjs
CHANGED
|
@@ -23,7 +23,9 @@ const DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
|
23
23
|
const DEFAULT_READY_TIMEOUT_MS = 30_000;
|
|
24
24
|
const DEFAULT_ANSWER_TIMEOUT_MS = 60_000;
|
|
25
25
|
const FALLBACK_QUIET_POLLS = 3;
|
|
26
|
-
|
|
26
|
+
// Capture the complete pane history so a long TUI response does not lose its
|
|
27
|
+
// leading lines. runTmux's MAX_BUFFER remains the memory/output ceiling.
|
|
28
|
+
const CAPTURE_START = "-";
|
|
27
29
|
const MAX_BUFFER = 10 * 1024 * 1024;
|
|
28
30
|
// execFile timeout for a bridge verb = attach timeout + this buffer, so the
|
|
29
31
|
// helper's own timeoutMs fires first and returns {timedOut:true} rather than
|
package/bin/triflux.mjs
CHANGED
|
@@ -94,6 +94,7 @@ import {
|
|
|
94
94
|
SKILL_ALIASES,
|
|
95
95
|
SYNC_MAP,
|
|
96
96
|
syncAliasedSkillDir,
|
|
97
|
+
syncCodexHarnessAdapter,
|
|
97
98
|
} from "../scripts/setup.mjs";
|
|
98
99
|
import { cleanupTmpFiles } from "../scripts/tmp-cleanup.mjs";
|
|
99
100
|
|
|
@@ -2203,6 +2204,19 @@ function cmdSetup(options = {}) {
|
|
|
2203
2204
|
}
|
|
2204
2205
|
}
|
|
2205
2206
|
|
|
2207
|
+
const codexHarnessSync = syncCodexHarnessAdapter();
|
|
2208
|
+
if (!codexHarnessSync.ok) {
|
|
2209
|
+
fail(`Codex tfx-harness: ${codexHarnessSync.reason}`);
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
if (codexHarnessSync.action === "synced") {
|
|
2213
|
+
ok("Codex tfx-harness adapter: 동기화됨");
|
|
2214
|
+
} else if (codexHarnessSync.action === "skipped") {
|
|
2215
|
+
warn(
|
|
2216
|
+
`Codex tfx-harness adapter: 사용자 스킬 보존 (backup: ${codexHarnessSync.backupDir})`,
|
|
2217
|
+
);
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2206
2220
|
// ── psmux 기본 셸 자동 수정 (cmd.exe → PowerShell) ──
|
|
2207
2221
|
if (process.platform === "win32" && which("psmux")) {
|
|
2208
2222
|
try {
|
package/hooks/keyword-rules.json
CHANGED
|
@@ -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":
|
|
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*(
|
|
756
|
+
"source": "(?:보안\\s*(?:감사|점검|리뷰|검토|스캔)|\\bcso\\b|\\bOWASP\\b|\\bSTRIDE\\b)",
|
|
631
757
|
"flags": "i"
|
|
632
758
|
}
|
|
633
759
|
],
|
|
634
760
|
"skill": "cso",
|
|
635
|
-
"priority":
|
|
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":
|
|
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":
|
|
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
|
}
|
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
|
|
|
@@ -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
|
|
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 —
|
|
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] —
|
|
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
|
|
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
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
|
533
|
+
await terminateProcess(child.pid);
|
|
470
534
|
};
|
|
471
535
|
|
|
472
|
-
const timeoutTimer =
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
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
|
|
491
|
-
stallTimer
|
|
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
|
-
|
|
500
|
-
|
|
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:
|
|
602
|
+
duration: now() - startedAt,
|
|
513
603
|
failureMode: ok ? null : failureMode || "crash",
|
|
514
604
|
});
|
|
515
605
|
}
|