triflux 10.3.4 → 10.7.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/LICENSE +21 -21
- package/bin/tfx-doctor-tui.mjs +1 -1
- package/bin/tfx-doctor.mjs +6 -1
- package/bin/tfx-profile.mjs +1 -1
- package/bin/tfx-setup-tui.mjs +1 -1
- package/bin/tfx-setup.mjs +6 -1
- package/bin/triflux.mjs +2396 -1140
- package/hooks/agent-route-guard.mjs +12 -8
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +19 -7
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +108 -24
- package/hooks/hook-registry.json +11 -0
- package/hooks/keyword-rules.json +6 -10
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/pipeline-stop.mjs +3 -6
- package/hooks/safety-guard.mjs +99 -28
- package/hooks/session-start-fast.mjs +143 -0
- package/hooks/subagent-verifier.mjs +5 -4
- package/hub/account-broker.mjs +256 -60
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +95 -57
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +67 -30
- package/hub/bridge.mjs +0 -1
- package/hub/cli-adapter-base.mjs +200 -48
- package/hub/codex-adapter.mjs +76 -96
- package/hub/codex-compat.mjs +3 -3
- package/hub/codex-preflight.mjs +63 -37
- package/hub/delegator/contracts.mjs +19 -23
- package/hub/delegator/index.mjs +3 -3
- package/hub/delegator/service.mjs +88 -64
- package/hub/delegator/tool-definitions.mjs +5 -5
- package/hub/fullcycle.mjs +33 -17
- package/hub/gemini-adapter.mjs +69 -94
- package/hub/hitl.mjs +89 -30
- package/hub/intent.mjs +161 -38
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/mcp-response-cache.mjs +66 -32
- package/hub/lib/memory-store.mjs +285 -111
- package/hub/lib/path-utils.mjs +35 -37
- package/hub/lib/process-utils.mjs +106 -37
- package/hub/lib/spawn-trace.mjs +527 -0
- package/hub/lib/ssh-command.mjs +34 -4
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +57 -48
- package/hub/promote-penalties.mjs +25 -7
- package/hub/quality/deslop.mjs +70 -49
- package/hub/research.mjs +32 -25
- package/hub/router.mjs +240 -107
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +76 -28
- package/hub/server.mjs +4 -4
- package/hub/session-fingerprint.mjs +126 -60
- package/hub/state.mjs +84 -43
- package/hub/store-adapter.mjs +59 -26
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +22 -7
- package/hub/team/ansi.mjs +186 -122
- package/hub/team/backend.mjs +28 -10
- package/hub/team/cli/commands/attach.mjs +29 -9
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +32 -11
- package/hub/team/cli/commands/focus.mjs +38 -11
- package/hub/team/cli/commands/interrupt.mjs +18 -6
- package/hub/team/cli/commands/kill.mjs +16 -5
- package/hub/team/cli/commands/list.mjs +11 -4
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +154 -31
- package/hub/team/cli/commands/start/parse-args.mjs +38 -11
- package/hub/team/cli/commands/start/start-headless.mjs +112 -36
- package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
- package/hub/team/cli/commands/start/start-mux.mjs +70 -21
- package/hub/team/cli/commands/start/start-wt.mjs +29 -12
- package/hub/team/cli/commands/status.mjs +43 -14
- package/hub/team/cli/commands/stop.mjs +11 -4
- package/hub/team/cli/commands/task.mjs +8 -3
- package/hub/team/cli/commands/tasks.mjs +1 -1
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +30 -8
- package/hub/team/cli/services/attach-fallback.mjs +31 -11
- package/hub/team/cli/services/hub-client.mjs +42 -14
- package/hub/team/cli/services/member-selector.mjs +11 -4
- package/hub/team/cli/services/native-control.mjs +48 -21
- package/hub/team/cli/services/runtime-mode.mjs +2 -1
- package/hub/team/cli/services/state-store.mjs +25 -8
- package/hub/team/cli/services/task-model.mjs +16 -6
- package/hub/team/conductor-mesh-bridge.mjs +24 -23
- package/hub/team/conductor.mjs +8 -4
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +41 -21
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/handoff.mjs +31 -15
- package/hub/team/headless.mjs +2 -1
- package/hub/team/health-probe.mjs +69 -54
- package/hub/team/launcher-template.mjs +16 -13
- package/hub/team/native-supervisor.mjs +65 -21
- package/hub/team/native.mjs +74 -35
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +119 -76
- package/hub/team/orchestrator.mjs +9 -4
- package/hub/team/pane.mjs +12 -7
- package/hub/team/process-cleanup.mjs +25 -16
- package/hub/team/psmux.mjs +491 -201
- package/hub/team/remote-probe.mjs +68 -52
- package/hub/team/remote-session.mjs +117 -59
- package/hub/team/remote-watcher.mjs +61 -33
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +3 -1
- package/hub/team/session.mjs +98 -34
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-locks.mjs +15 -13
- package/hub/team/swarm-planner.mjs +32 -21
- package/hub/team/swarm-reconciler.mjs +48 -23
- package/hub/team/tui-lite.mjs +266 -68
- package/hub/team/tui-remote-adapter.mjs +14 -10
- package/hub/team/tui-viewer.mjs +99 -43
- package/hub/team/tui.mjs +708 -271
- package/hub/team/worktree-lifecycle.mjs +152 -58
- package/hub/team/wt-manager.mjs +24 -14
- package/hub/token-mode.mjs +71 -71
- package/hub/tray.mjs +66 -23
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-mcp.mjs +192 -141
- package/hub/workers/delegator-mcp.mjs +507 -333
- package/hub/workers/factory.mjs +8 -8
- package/hub/workers/gemini-worker.mjs +115 -84
- package/hub/workers/interface.mjs +6 -1
- package/hub/workers/worker-utils.mjs +21 -14
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +162 -26
- package/hud/context-monitor.mjs +82 -41
- package/hud/hud-qos-status.mjs +129 -49
- package/hud/mission-board.mjs +6 -3
- package/hud/providers/claude.mjs +226 -115
- package/hud/providers/codex.mjs +62 -22
- package/hud/providers/gemini.mjs +168 -56
- package/hud/renderers.mjs +384 -119
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +78 -38
- package/mesh/index.mjs +11 -5
- package/mesh/mesh-budget.mjs +18 -9
- package/mesh/mesh-heartbeat.mjs +1 -1
- package/mesh/mesh-queue.mjs +3 -5
- package/mesh/mesh-router.mjs +5 -4
- package/package.json +2 -1
- package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
- package/scripts/__tests__/keyword-detector.test.mjs +77 -28
- package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
- package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
- package/scripts/__tests__/remote-spawn.test.mjs +10 -4
- package/scripts/__tests__/session-start-fast.test.mjs +36 -0
- package/scripts/__tests__/skill-template.test.mjs +98 -50
- package/scripts/__tests__/smoke.test.mjs +1 -1
- package/scripts/__tests__/spawn-trace.test.mjs +102 -0
- package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
- package/scripts/cache-doctor.mjs +11 -4
- package/scripts/cache-warmup.mjs +96 -37
- package/scripts/claudemd-sync.mjs +27 -17
- package/scripts/codex-gateway-preflight.mjs +52 -37
- package/scripts/codex-mcp-gateway-sync.mjs +59 -39
- package/scripts/completions/tfx.bash +47 -47
- package/scripts/completions/tfx.fish +44 -44
- package/scripts/completions/tfx.zsh +83 -83
- package/scripts/config-audit.mjs +232 -0
- package/scripts/convert-to-tmpl.mjs +54 -0
- package/scripts/cross-review-gate.mjs +35 -12
- package/scripts/cross-review-tracker.mjs +21 -8
- package/scripts/demo.mjs +35 -17
- package/scripts/doctor-diagnose.mjs +284 -0
- package/scripts/gen-skill-docs.mjs +7 -2
- package/scripts/gen-skill-manifest.mjs +2 -1
- package/scripts/headless-guard.mjs +86 -48
- package/scripts/hub-ensure.mjs +45 -26
- package/scripts/keyword-detector.mjs +41 -20
- package/scripts/keyword-rules-expander.mjs +47 -30
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +6 -3
- package/scripts/lib/env-probe.mjs +47 -28
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/keyword-rules.mjs +43 -19
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +377 -239
- package/scripts/lib/mcp-guard-engine.mjs +194 -79
- package/scripts/lib/mcp-manifest.mjs +23 -13
- package/scripts/lib/mcp-server-catalog.mjs +300 -63
- package/scripts/lib/psmux-info.mjs +11 -6
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-template.mjs +30 -7
- package/scripts/mcp-check.mjs +58 -39
- package/scripts/mcp-gateway-config.mjs +83 -39
- package/scripts/mcp-gateway-ensure.mjs +43 -35
- package/scripts/mcp-gateway-integration-test.mjs +70 -58
- package/scripts/mcp-gateway-start.mjs +126 -60
- package/scripts/mcp-gateway-verify.mjs +24 -22
- package/scripts/mcp-safety-guard.mjs +44 -11
- package/scripts/notion-read.mjs +199 -84
- package/scripts/pack.mjs +94 -89
- package/scripts/preflight-cache.mjs +27 -10
- package/scripts/preinstall.mjs +42 -13
- package/scripts/remote-spawn.mjs +309 -94
- package/scripts/run.cjs +8 -5
- package/scripts/session-spawn-helper.mjs +130 -39
- package/scripts/session-stale-cleanup.mjs +123 -0
- package/scripts/setup.mjs +941 -492
- package/scripts/test-lock.mjs +20 -7
- package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
- package/scripts/tfx-batch-stats.mjs +32 -11
- package/scripts/tfx-gate-activate.mjs +11 -4
- package/scripts/tfx-route-post.mjs +87 -20
- package/scripts/tfx-route-worker.mjs +57 -51
- package/scripts/tfx-route.sh +41 -124
- package/scripts/tmp-cleanup.mjs +21 -7
- package/scripts/token-snapshot.mjs +204 -85
- package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
- package/skills/.omc/state/idle-notif-cooldown.json +3 -0
- package/skills/.omc/state/last-tool-error.json +7 -0
- package/skills/.omc/state/subagent-tracking.json +7 -0
- package/skills/_templates/base.md +1 -6
- package/skills/merge-worktree/SKILL.md.tmpl +144 -0
- package/skills/shared/telemetry-segment.md +6 -0
- package/skills/star-prompt/SKILL.md.tmpl +222 -0
- package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
- package/skills/tfx-analysis/skill.json +1 -6
- package/skills/tfx-auto/SKILL.md +1 -0
- package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
- package/skills/tfx-auto-codex/skill.json +1 -3
- package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
- package/skills/tfx-autopilot/skill.json +1 -5
- package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
- package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
- package/skills/tfx-autoroute/skill.json +1 -7
- package/skills/tfx-codex/SKILL.md +1 -0
- package/skills/tfx-codex/skill.json +1 -3
- package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
- package/skills/tfx-codex-swarm/evals/evals.json +1 -1
- package/skills/tfx-codex-swarm/skill.json +1 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
- package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
- package/skills/tfx-debate/SKILL.md.tmpl +192 -0
- package/skills/tfx-debate/skill.json +1 -7
- package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
- package/skills/tfx-deep-analysis/skill.json +1 -5
- package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
- package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
- package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
- package/skills/tfx-deep-qa/skill.json +1 -6
- package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
- package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
- package/skills/tfx-doctor/SKILL.md +21 -0
- package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
- package/skills/tfx-doctor/skill.json +1 -3
- package/skills/tfx-find/SKILL.md +1 -0
- package/skills/tfx-forge/SKILL.md.tmpl +187 -0
- package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
- package/skills/tfx-fullcycle/skill.json +1 -6
- package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
- package/skills/tfx-gemini/skill.json +1 -3
- package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
- package/skills/tfx-hooks/skill.json +1 -3
- package/skills/tfx-hub/SKILL.md.tmpl +212 -0
- package/skills/tfx-hub/skill.json +1 -3
- package/skills/tfx-index/SKILL.md +1 -0
- package/skills/tfx-index/skill.json +1 -6
- package/skills/tfx-interview/SKILL.md.tmpl +285 -0
- package/skills/tfx-multi/SKILL.md.tmpl +183 -0
- package/skills/tfx-multi/skill.json +1 -3
- package/skills/tfx-panel/SKILL.md.tmpl +189 -0
- package/skills/tfx-panel/skill.json +1 -7
- package/skills/tfx-persist/SKILL.md.tmpl +270 -0
- package/skills/tfx-persist/skill.json +1 -7
- package/skills/tfx-plan/SKILL.md +1 -0
- package/skills/tfx-plan/skill.json +1 -6
- package/skills/tfx-profile/SKILL.md.tmpl +239 -0
- package/skills/tfx-profile/skill.json +1 -3
- package/skills/tfx-prune/SKILL.md.tmpl +200 -0
- package/skills/tfx-prune/skill.json +1 -7
- package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
- package/skills/tfx-psmux-rules/skill.json +1 -4
- package/skills/tfx-qa/SKILL.md +1 -0
- package/skills/tfx-qa/skill.json +1 -6
- package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
- package/skills/tfx-ralph/skill.json +1 -4
- package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
- package/skills/tfx-remote-setup/skill.json +1 -3
- package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
- package/skills/tfx-remote-spawn/references/hosts.json +16 -0
- package/skills/tfx-remote-spawn/skill.json +1 -4
- package/skills/tfx-research/SKILL.md +1 -0
- package/skills/tfx-review/SKILL.md +1 -0
- package/skills/tfx-review/skill.json +1 -6
- package/skills/tfx-setup/SKILL.md.tmpl +504 -0
- package/skills/tfx-setup/skill.json +1 -3
- package/skills/tfx-swarm/SKILL.md +22 -0
- package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
- package/tui/codex-profile.mjs +88 -33
- package/tui/core.mjs +45 -15
- package/tui/doctor.mjs +75 -28
- package/tui/gemini-profile.mjs +74 -29
- package/tui/monitor-data.mjs +8 -4
- package/tui/monitor.mjs +71 -27
- package/tui/setup.mjs +133 -42
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* L3: Prompt acknowledged (첫 tool call/텍스트, 120s)
|
|
12
12
|
*/
|
|
13
13
|
export const PROBE_LEVELS = Object.freeze({
|
|
14
|
-
L0:
|
|
15
|
-
L1:
|
|
16
|
-
L2:
|
|
17
|
-
L3:
|
|
14
|
+
L0: "alive",
|
|
15
|
+
L1: "advancing",
|
|
16
|
+
L2: "mcp_connected",
|
|
17
|
+
L3: "prompt_ack",
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
/** 기본 설정 (기존 stallThresholdMs/stallTimeout 값 계승) */
|
|
@@ -32,13 +32,13 @@ export const PROBE_DEFAULTS = Object.freeze({
|
|
|
32
32
|
* Codex가 질문하며 stdin을 기다리는 경우 stall이 아니라 INPUT_WAIT로 분류.
|
|
33
33
|
*/
|
|
34
34
|
const INPUT_WAIT_PATTERNS = [
|
|
35
|
-
/\?\s*$/m,
|
|
36
|
-
/\b(y\/n|yes\/no)\b/i,
|
|
37
|
-
/\b(choose|select|pick)\b.*:/i,
|
|
38
|
-
/\b(confirm|approve|proceed)\b/i,
|
|
39
|
-
/\b(enter|input|type)\b.*:/i,
|
|
40
|
-
/\[.*\]:\s*$/m,
|
|
41
|
-
/>\s*$/m,
|
|
35
|
+
/\?\s*$/m, // 물음표로 끝나는 줄
|
|
36
|
+
/\b(y\/n|yes\/no)\b/i, // y/n 프롬프트
|
|
37
|
+
/\b(choose|select|pick)\b.*:/i, // choose/select 프롬프트
|
|
38
|
+
/\b(confirm|approve|proceed)\b/i, // confirm 프롬프트
|
|
39
|
+
/\b(enter|input|type)\b.*:/i, // 입력 요청
|
|
40
|
+
/\[.*\]:\s*$/m, // [default]: 형태
|
|
41
|
+
/>\s*$/m, // > 프롬프트
|
|
42
42
|
];
|
|
43
43
|
|
|
44
44
|
/**
|
|
@@ -49,7 +49,11 @@ const INPUT_WAIT_PATTERNS = [
|
|
|
49
49
|
export function detectInputWait(recentOutput) {
|
|
50
50
|
if (!recentOutput) return { detected: false, pattern: null };
|
|
51
51
|
// 마지막 5줄만 검사 (전체 output이 아닌 최근 출력)
|
|
52
|
-
const lines = recentOutput
|
|
52
|
+
const lines = recentOutput
|
|
53
|
+
.split(/\r?\n/)
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.slice(-5)
|
|
56
|
+
.join("\n");
|
|
53
57
|
for (const re of INPUT_WAIT_PATTERNS) {
|
|
54
58
|
if (re.test(lines)) {
|
|
55
59
|
return { detected: true, pattern: re.source };
|
|
@@ -84,10 +88,10 @@ export function createHealthProbe(session, opts = {}) {
|
|
|
84
88
|
let spawnedAt = Date.now();
|
|
85
89
|
|
|
86
90
|
const status = {
|
|
87
|
-
l0: null,
|
|
88
|
-
l1: null,
|
|
89
|
-
l2: null,
|
|
90
|
-
l3: null,
|
|
91
|
+
l0: null, // 'ok' | 'fail'
|
|
92
|
+
l1: null, // 'ok' | 'stall' | 'input_wait'
|
|
93
|
+
l2: null, // 'ok' | 'fail' | 'skip'
|
|
94
|
+
l3: null, // 'ok' | 'timeout'
|
|
91
95
|
lastProbeAt: null,
|
|
92
96
|
inputWaitPattern: null,
|
|
93
97
|
};
|
|
@@ -96,10 +100,11 @@ export function createHealthProbe(session, opts = {}) {
|
|
|
96
100
|
* L0: Process alive check.
|
|
97
101
|
*/
|
|
98
102
|
function probeL0() {
|
|
99
|
-
const alive =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
const alive =
|
|
104
|
+
session.alive !== undefined
|
|
105
|
+
? session.alive
|
|
106
|
+
: session.pid != null && session.pid > 0;
|
|
107
|
+
status.l0 = alive ? "ok" : "fail";
|
|
103
108
|
return status.l0;
|
|
104
109
|
}
|
|
105
110
|
|
|
@@ -107,43 +112,45 @@ export function createHealthProbe(session, opts = {}) {
|
|
|
107
112
|
* L1 + L1.5: Output advancing + INPUT_WAIT 감지.
|
|
108
113
|
*/
|
|
109
114
|
function probeL1() {
|
|
110
|
-
const currentBytes =
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
const currentBytes =
|
|
116
|
+
typeof session.getOutputBytes === "function"
|
|
117
|
+
? session.getOutputBytes()
|
|
118
|
+
: 0;
|
|
113
119
|
|
|
114
120
|
const now = Date.now();
|
|
115
121
|
|
|
116
122
|
if (currentBytes !== lastOutputBytes) {
|
|
117
123
|
lastOutputBytes = currentBytes;
|
|
118
124
|
lastOutputChangeAt = now;
|
|
119
|
-
status.l1 =
|
|
125
|
+
status.l1 = "ok";
|
|
120
126
|
status.inputWaitPattern = null;
|
|
121
|
-
return
|
|
127
|
+
return "ok";
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
const silenceMs = now - lastOutputChangeAt;
|
|
125
131
|
|
|
126
132
|
if (silenceMs >= config.l1ThresholdMs) {
|
|
127
133
|
// L1.5: INPUT_WAIT 감지 — stall 전에 질문 패턴 체크
|
|
128
|
-
const recentOutput =
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
const recentOutput =
|
|
135
|
+
typeof session.getRecentOutput === "function"
|
|
136
|
+
? session.getRecentOutput()
|
|
137
|
+
: "";
|
|
131
138
|
const inputWait = detectInputWait(recentOutput);
|
|
132
139
|
|
|
133
140
|
if (inputWait.detected) {
|
|
134
|
-
status.l1 =
|
|
141
|
+
status.l1 = "input_wait";
|
|
135
142
|
status.inputWaitPattern = inputWait.pattern;
|
|
136
|
-
return
|
|
143
|
+
return "input_wait";
|
|
137
144
|
}
|
|
138
145
|
|
|
139
|
-
status.l1 =
|
|
146
|
+
status.l1 = "stall";
|
|
140
147
|
status.inputWaitPattern = null;
|
|
141
|
-
return
|
|
148
|
+
return "stall";
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
// 아직 threshold 미달
|
|
145
|
-
status.l1 =
|
|
146
|
-
return
|
|
152
|
+
status.l1 = "ok";
|
|
153
|
+
return "ok";
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
/**
|
|
@@ -151,23 +158,26 @@ export function createHealthProbe(session, opts = {}) {
|
|
|
151
158
|
*/
|
|
152
159
|
async function probeL2() {
|
|
153
160
|
if (!config.enableL2) {
|
|
154
|
-
status.l2 =
|
|
155
|
-
return
|
|
161
|
+
status.l2 = "skip";
|
|
162
|
+
return "skip";
|
|
156
163
|
}
|
|
157
|
-
if (typeof config.checkMcp !==
|
|
158
|
-
status.l2 =
|
|
159
|
-
return
|
|
164
|
+
if (typeof config.checkMcp !== "function") {
|
|
165
|
+
status.l2 = "skip";
|
|
166
|
+
return "skip";
|
|
160
167
|
}
|
|
161
168
|
try {
|
|
162
169
|
const connected = await Promise.race([
|
|
163
170
|
config.checkMcp(),
|
|
164
171
|
new Promise((_, reject) =>
|
|
165
|
-
setTimeout(
|
|
172
|
+
setTimeout(
|
|
173
|
+
() => reject(new Error("probe_timeout")),
|
|
174
|
+
config.probeTimeoutMs,
|
|
175
|
+
),
|
|
166
176
|
),
|
|
167
177
|
]);
|
|
168
|
-
status.l2 = connected ?
|
|
178
|
+
status.l2 = connected ? "ok" : "fail";
|
|
169
179
|
} catch {
|
|
170
|
-
status.l2 =
|
|
180
|
+
status.l2 = "fail";
|
|
171
181
|
}
|
|
172
182
|
return status.l2;
|
|
173
183
|
}
|
|
@@ -178,24 +188,25 @@ export function createHealthProbe(session, opts = {}) {
|
|
|
178
188
|
*/
|
|
179
189
|
function probeL3() {
|
|
180
190
|
if (promptAcked) {
|
|
181
|
-
status.l3 =
|
|
182
|
-
return
|
|
191
|
+
status.l3 = "ok";
|
|
192
|
+
return "ok";
|
|
183
193
|
}
|
|
184
194
|
|
|
185
|
-
const currentBytes =
|
|
186
|
-
|
|
187
|
-
|
|
195
|
+
const currentBytes =
|
|
196
|
+
typeof session.getOutputBytes === "function"
|
|
197
|
+
? session.getOutputBytes()
|
|
198
|
+
: 0;
|
|
188
199
|
|
|
189
200
|
if (currentBytes > 0) {
|
|
190
201
|
promptAcked = true;
|
|
191
|
-
status.l3 =
|
|
192
|
-
return
|
|
202
|
+
status.l3 = "ok";
|
|
203
|
+
return "ok";
|
|
193
204
|
}
|
|
194
205
|
|
|
195
206
|
const elapsed = Date.now() - spawnedAt;
|
|
196
207
|
if (elapsed >= config.l3ThresholdMs) {
|
|
197
|
-
status.l3 =
|
|
198
|
-
return
|
|
208
|
+
status.l3 = "timeout";
|
|
209
|
+
return "timeout";
|
|
199
210
|
}
|
|
200
211
|
|
|
201
212
|
status.l3 = null; // 아직 판정 전
|
|
@@ -217,7 +228,7 @@ export function createHealthProbe(session, opts = {}) {
|
|
|
217
228
|
};
|
|
218
229
|
status.lastProbeAt = result.ts;
|
|
219
230
|
|
|
220
|
-
if (typeof config.onProbe ===
|
|
231
|
+
if (typeof config.onProbe === "function") {
|
|
221
232
|
config.onProbe(result);
|
|
222
233
|
}
|
|
223
234
|
|
|
@@ -232,7 +243,9 @@ export function createHealthProbe(session, opts = {}) {
|
|
|
232
243
|
lastOutputBytes = 0;
|
|
233
244
|
promptAcked = false;
|
|
234
245
|
|
|
235
|
-
timer = setInterval(() => {
|
|
246
|
+
timer = setInterval(() => {
|
|
247
|
+
void probe();
|
|
248
|
+
}, config.intervalMs);
|
|
236
249
|
timer.unref?.();
|
|
237
250
|
|
|
238
251
|
// 즉시 첫 probe 실행
|
|
@@ -267,6 +280,8 @@ export function createHealthProbe(session, opts = {}) {
|
|
|
267
280
|
probe,
|
|
268
281
|
resetTracking,
|
|
269
282
|
getStatus: () => ({ ...status }),
|
|
270
|
-
get started() {
|
|
283
|
+
get started() {
|
|
284
|
+
return started;
|
|
285
|
+
},
|
|
271
286
|
});
|
|
272
287
|
}
|
|
@@ -5,28 +5,28 @@
|
|
|
5
5
|
// F4 해결: codex exec "prompt" 인라인 (파이프/리다이렉트 아님)
|
|
6
6
|
// F5 해결: 동일 입력 → 동일 args 배열 (런타임 분기 없음)
|
|
7
7
|
|
|
8
|
-
import { buildExecArgs as buildCodexArgs } from
|
|
9
|
-
import { buildExecArgs as buildGeminiArgs } from
|
|
8
|
+
import { buildExecArgs as buildCodexArgs } from "../codex-adapter.mjs";
|
|
9
|
+
import { buildExecArgs as buildGeminiArgs } from "../gemini-adapter.mjs";
|
|
10
10
|
|
|
11
11
|
/** CLI별 adapter 레지스트리 */
|
|
12
12
|
const ADAPTERS = Object.freeze({
|
|
13
13
|
codex: {
|
|
14
|
-
bin:
|
|
14
|
+
bin: "codex",
|
|
15
15
|
buildArgs: buildCodexArgs,
|
|
16
16
|
env: (profile) => (profile ? { CODEX_PROFILE: profile } : {}),
|
|
17
17
|
},
|
|
18
18
|
gemini: {
|
|
19
|
-
bin:
|
|
19
|
+
bin: "gemini",
|
|
20
20
|
buildArgs: buildGeminiArgs,
|
|
21
21
|
env: () => ({}),
|
|
22
22
|
},
|
|
23
23
|
claude: {
|
|
24
|
-
bin:
|
|
24
|
+
bin: "claude",
|
|
25
25
|
buildArgs: (opts = {}) => {
|
|
26
|
-
const parts = [
|
|
27
|
-
if (opts.model) parts.push(
|
|
28
|
-
parts.push(
|
|
29
|
-
return parts.join(
|
|
26
|
+
const parts = ["claude"];
|
|
27
|
+
if (opts.model) parts.push("--model", opts.model);
|
|
28
|
+
parts.push("-p", JSON.stringify(opts.prompt || ""));
|
|
29
|
+
return parts.join(" ");
|
|
30
30
|
},
|
|
31
31
|
env: () => ({}),
|
|
32
32
|
},
|
|
@@ -41,7 +41,9 @@ const ADAPTERS = Object.freeze({
|
|
|
41
41
|
export function getAdapter(agent) {
|
|
42
42
|
const adapter = ADAPTERS[agent];
|
|
43
43
|
if (!adapter) {
|
|
44
|
-
throw new Error(
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Unknown agent: "${agent}". Supported: ${Object.keys(ADAPTERS).join(", ")}`,
|
|
46
|
+
);
|
|
45
47
|
}
|
|
46
48
|
return adapter;
|
|
47
49
|
}
|
|
@@ -60,10 +62,11 @@ export function getAdapter(agent) {
|
|
|
60
62
|
* @returns {{ bin: string, command: string, env: object, agent: string }}
|
|
61
63
|
*/
|
|
62
64
|
export function buildLauncher(opts) {
|
|
63
|
-
const { agent, profile, prompt, workdir, model, resultFile, mcpServers } =
|
|
65
|
+
const { agent, profile, prompt, workdir, model, resultFile, mcpServers } =
|
|
66
|
+
opts;
|
|
64
67
|
|
|
65
|
-
if (!agent) throw new Error(
|
|
66
|
-
if (!prompt && prompt !==
|
|
68
|
+
if (!agent) throw new Error("agent is required");
|
|
69
|
+
if (!prompt && prompt !== "") throw new Error("prompt is required");
|
|
67
70
|
|
|
68
71
|
const adapter = getAdapter(agent);
|
|
69
72
|
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
// hub/team/native-supervisor.mjs — tmux 없이 멀티 CLI를 직접 띄우는 네이티브 팀 런타임
|
|
2
|
+
|
|
3
|
+
import { execSync as execSyncSupervisor, spawn } from "node:child_process";
|
|
4
|
+
import {
|
|
5
|
+
createWriteStream,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
2
10
|
import { createServer } from "node:http";
|
|
3
|
-
import { spawn, execSync as execSyncSupervisor } from "node:child_process";
|
|
4
|
-
import { mkdirSync, readFileSync, writeFileSync, createWriteStream } from "node:fs";
|
|
5
11
|
import { dirname, join } from "node:path";
|
|
6
12
|
import { verifySlimWrapperRouteExecution } from "./native.mjs";
|
|
7
13
|
import { forceCleanupTeam } from "./nativeProxy.mjs";
|
|
@@ -130,12 +136,14 @@ const SAFE_COMMAND_RE = /^[a-zA-Z0-9 _./:@"'=\-\\]+$/;
|
|
|
130
136
|
|
|
131
137
|
function validateMemberCommand(command, memberName) {
|
|
132
138
|
if (typeof command !== "string" || command.trim().length === 0) {
|
|
133
|
-
throw new Error(
|
|
139
|
+
throw new Error(
|
|
140
|
+
`member "${memberName}": command must be a non-empty string`,
|
|
141
|
+
);
|
|
134
142
|
}
|
|
135
143
|
if (!SAFE_COMMAND_RE.test(command)) {
|
|
136
144
|
throw new Error(
|
|
137
145
|
`member "${memberName}": command contains disallowed characters — ` +
|
|
138
|
-
|
|
146
|
+
`shell metacharacters (;&|$\`()<>{}\\n\\r) are not permitted`,
|
|
139
147
|
);
|
|
140
148
|
}
|
|
141
149
|
}
|
|
@@ -153,7 +161,10 @@ function spawnMember(member) {
|
|
|
153
161
|
shell: true,
|
|
154
162
|
env: {
|
|
155
163
|
...process.env,
|
|
156
|
-
TERM:
|
|
164
|
+
TERM:
|
|
165
|
+
process.env.TERM && process.env.TERM !== "dumb"
|
|
166
|
+
? process.env.TERM
|
|
167
|
+
: "xterm-256color",
|
|
157
168
|
},
|
|
158
169
|
stdio: ["pipe", "pipe", "pipe"],
|
|
159
170
|
windowsHide: true,
|
|
@@ -176,7 +187,8 @@ function spawnMember(member) {
|
|
|
176
187
|
const txt = safeText(buf).trim();
|
|
177
188
|
if (txt) {
|
|
178
189
|
const lines = txt.split(/\r?\n/).filter(Boolean);
|
|
179
|
-
if (lines.length)
|
|
190
|
+
if (lines.length)
|
|
191
|
+
state.lastPreview = lines[lines.length - 1].slice(0, 280);
|
|
180
192
|
}
|
|
181
193
|
});
|
|
182
194
|
|
|
@@ -185,7 +197,8 @@ function spawnMember(member) {
|
|
|
185
197
|
const txt = safeText(buf).trim();
|
|
186
198
|
if (txt) {
|
|
187
199
|
const lines = txt.split(/\r?\n/).filter(Boolean);
|
|
188
|
-
if (lines.length)
|
|
200
|
+
if (lines.length)
|
|
201
|
+
state.lastPreview = `[err] ${lines[lines.length - 1].slice(0, 260)}`;
|
|
189
202
|
}
|
|
190
203
|
});
|
|
191
204
|
|
|
@@ -193,8 +206,12 @@ function spawnMember(member) {
|
|
|
193
206
|
state.status = "exited";
|
|
194
207
|
state.exitCode = code;
|
|
195
208
|
finalizeRouteVerification(state);
|
|
196
|
-
try {
|
|
197
|
-
|
|
209
|
+
try {
|
|
210
|
+
outWs.end();
|
|
211
|
+
} catch {}
|
|
212
|
+
try {
|
|
213
|
+
errWs.end();
|
|
214
|
+
} catch {}
|
|
198
215
|
maybeAutoShutdown();
|
|
199
216
|
});
|
|
200
217
|
|
|
@@ -202,8 +219,12 @@ function spawnMember(member) {
|
|
|
202
219
|
state.status = "exited";
|
|
203
220
|
state.exitCode = -1;
|
|
204
221
|
state.lastPreview = `[spawn error] ${err.message}`;
|
|
205
|
-
try {
|
|
206
|
-
|
|
222
|
+
try {
|
|
223
|
+
outWs.end();
|
|
224
|
+
} catch {}
|
|
225
|
+
try {
|
|
226
|
+
errWs.end();
|
|
227
|
+
} catch {}
|
|
207
228
|
maybeAutoShutdown();
|
|
208
229
|
});
|
|
209
230
|
|
|
@@ -213,7 +234,8 @@ function spawnMember(member) {
|
|
|
213
234
|
function sendInput(memberName, text) {
|
|
214
235
|
const state = processMap.get(memberName);
|
|
215
236
|
if (!state) return { ok: false, error: "member_not_found" };
|
|
216
|
-
if (state.status !== "running")
|
|
237
|
+
if (state.status !== "running")
|
|
238
|
+
return { ok: false, error: "member_not_running" };
|
|
217
239
|
try {
|
|
218
240
|
state.child.stdin.write(`${safeText(text)}\n`);
|
|
219
241
|
return { ok: true };
|
|
@@ -225,7 +247,8 @@ function sendInput(memberName, text) {
|
|
|
225
247
|
function interruptMember(memberName) {
|
|
226
248
|
const state = processMap.get(memberName);
|
|
227
249
|
if (!state) return { ok: false, error: "member_not_found" };
|
|
228
|
-
if (state.status !== "running")
|
|
250
|
+
if (state.status !== "running")
|
|
251
|
+
return { ok: false, error: "member_not_running" };
|
|
229
252
|
|
|
230
253
|
let signaled = false;
|
|
231
254
|
try {
|
|
@@ -250,7 +273,9 @@ let isShuttingDown = false;
|
|
|
250
273
|
|
|
251
274
|
function maybeAutoShutdown() {
|
|
252
275
|
if (isShuttingDown) return;
|
|
253
|
-
const allExited = [...processMap.values()].every(
|
|
276
|
+
const allExited = [...processMap.values()].every(
|
|
277
|
+
(s) => s.status === "exited",
|
|
278
|
+
);
|
|
254
279
|
if (!allExited) return;
|
|
255
280
|
shutdown();
|
|
256
281
|
}
|
|
@@ -261,11 +286,19 @@ async function shutdown() {
|
|
|
261
286
|
|
|
262
287
|
for (const state of processMap.values()) {
|
|
263
288
|
if (state.status === "running") {
|
|
264
|
-
try {
|
|
265
|
-
|
|
289
|
+
try {
|
|
290
|
+
state.child.stdin.write("exit\n");
|
|
291
|
+
} catch {}
|
|
292
|
+
try {
|
|
293
|
+
state.child.kill("SIGTERM");
|
|
294
|
+
} catch {}
|
|
266
295
|
}
|
|
267
|
-
try {
|
|
268
|
-
|
|
296
|
+
try {
|
|
297
|
+
state.outWs.end();
|
|
298
|
+
} catch {}
|
|
299
|
+
try {
|
|
300
|
+
state.errWs.end();
|
|
301
|
+
} catch {}
|
|
269
302
|
}
|
|
270
303
|
|
|
271
304
|
try {
|
|
@@ -278,9 +311,17 @@ async function shutdown() {
|
|
|
278
311
|
const pid = state.child?.pid;
|
|
279
312
|
if (process.platform === "win32" && Number.isInteger(pid) && pid > 0) {
|
|
280
313
|
// Windows: 프로세스 트리 전체 강제 종료 (손자 MCP 서버 포함)
|
|
281
|
-
try {
|
|
314
|
+
try {
|
|
315
|
+
execSyncSupervisor(`taskkill /T /F /PID ${pid}`, {
|
|
316
|
+
stdio: "pipe",
|
|
317
|
+
windowsHide: true,
|
|
318
|
+
timeout: 5000,
|
|
319
|
+
});
|
|
320
|
+
} catch {}
|
|
282
321
|
} else {
|
|
283
|
-
try {
|
|
322
|
+
try {
|
|
323
|
+
state.child.kill("SIGKILL");
|
|
324
|
+
} catch {}
|
|
284
325
|
}
|
|
285
326
|
}
|
|
286
327
|
}
|
|
@@ -321,7 +362,10 @@ const server = createServer(async (req, res) => {
|
|
|
321
362
|
let totalLen = 0;
|
|
322
363
|
for await (const c of req) {
|
|
323
364
|
totalLen += c.length;
|
|
324
|
-
if (totalLen > MAX_BODY) {
|
|
365
|
+
if (totalLen > MAX_BODY) {
|
|
366
|
+
send(413, { ok: false, error: "payload_too_large" });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
325
369
|
chunks.push(c);
|
|
326
370
|
}
|
|
327
371
|
const raw = Buffer.concat(chunks).toString("utf8") || "{}";
|