triflux 10.0.0 → 10.0.2
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 +171 -0
- package/README.md +32 -15
- package/bin/triflux.mjs +62 -5
- package/hooks/agent-route-guard.mjs +109 -0
- package/hooks/cross-review-tracker.mjs +122 -0
- package/hooks/error-context.mjs +148 -0
- package/hooks/hook-adaptive-collector.mjs +86 -0
- package/hooks/hook-manager.mjs +365 -0
- package/hooks/hook-orchestrator.mjs +312 -0
- package/hooks/hook-registry.json +246 -0
- package/hooks/hooks.json +89 -0
- package/hooks/keyword-rules.json +574 -0
- package/hooks/lib/resolve-root.mjs +59 -0
- package/hooks/mcp-config-watcher.mjs +80 -0
- package/hooks/pipeline-stop.mjs +76 -0
- package/hooks/safety-guard.mjs +169 -0
- package/hooks/subagent-verifier.mjs +80 -0
- package/hub/account-broker.mjs +251 -0
- package/hub/adaptive-diagnostic.mjs +323 -0
- package/hub/adaptive-inject.mjs +186 -0
- package/hub/adaptive-memory.mjs +163 -0
- package/hub/adaptive.mjs +143 -0
- package/hub/assign-callbacks.mjs +133 -0
- package/hub/bridge.mjs +799 -0
- package/hub/cli-adapter-base.mjs +280 -0
- package/hub/codex-adapter.mjs +199 -0
- package/hub/codex-compat.mjs +11 -0
- package/hub/codex-preflight.mjs +166 -0
- package/hub/delegator/contracts.mjs +37 -0
- package/hub/delegator/index.mjs +14 -0
- package/hub/delegator/schema/delegator-tools.schema.json +250 -0
- package/hub/delegator/service.mjs +307 -0
- package/hub/delegator/tool-definitions.mjs +35 -0
- package/hub/fullcycle.mjs +96 -0
- package/hub/gemini-adapter.mjs +180 -0
- package/hub/hitl.mjs +143 -0
- package/hub/intent.mjs +193 -0
- package/hub/lib/cache-guard.mjs +114 -0
- package/hub/lib/known-errors.json +72 -0
- package/hub/lib/memory-store.mjs +748 -0
- package/hub/lib/process-utils.mjs +361 -0
- package/hub/lib/ssh-command.mjs +211 -0
- package/hub/lib/ssh-retry.mjs +59 -0
- package/hub/lib/uuidv7.mjs +44 -0
- package/hub/memory-doctor.mjs +480 -0
- package/hub/middleware/request-logger.mjs +161 -0
- package/hub/paths.mjs +30 -0
- package/hub/pipe.mjs +664 -0
- package/hub/pipeline/gates/confidence.mjs +56 -0
- package/hub/pipeline/gates/consensus.mjs +94 -0
- package/hub/pipeline/gates/index.mjs +5 -0
- package/hub/pipeline/gates/selfcheck.mjs +82 -0
- package/hub/pipeline/index.mjs +318 -0
- package/hub/pipeline/state.mjs +191 -0
- package/hub/pipeline/transitions.mjs +124 -0
- package/hub/platform.mjs +225 -0
- package/hub/public/dashboard.html +355 -0
- package/hub/public/tray-icon.ico +0 -0
- package/hub/public/tray-icon.png +0 -0
- package/hub/quality/deslop.mjs +253 -0
- package/hub/reflexion.mjs +372 -0
- package/hub/research.mjs +146 -0
- package/hub/router.mjs +791 -0
- package/hub/routing/complexity.mjs +166 -0
- package/hub/routing/index.mjs +117 -0
- package/hub/routing/q-learning.mjs +336 -0
- package/hub/schema.sql +148 -0
- package/hub/server.mjs +1264 -0
- package/hub/session-fingerprint.mjs +352 -0
- package/hub/state.mjs +258 -0
- package/hub/store-adapter.mjs +118 -0
- package/hub/store.mjs +857 -0
- package/hub/team/agent-map.json +11 -0
- package/hub/team/ansi.mjs +379 -0
- package/hub/team/backend.mjs +90 -0
- package/hub/team/cli/commands/attach.mjs +37 -0
- package/hub/team/cli/commands/control.mjs +43 -0
- package/hub/team/cli/commands/debug.mjs +74 -0
- package/hub/team/cli/commands/focus.mjs +53 -0
- package/hub/team/cli/commands/interrupt.mjs +36 -0
- package/hub/team/cli/commands/kill.mjs +37 -0
- package/hub/team/cli/commands/list.mjs +24 -0
- package/hub/team/cli/commands/send.mjs +37 -0
- package/hub/team/cli/commands/start/index.mjs +106 -0
- package/hub/team/cli/commands/start/parse-args.mjs +130 -0
- package/hub/team/cli/commands/start/start-headless.mjs +109 -0
- package/hub/team/cli/commands/start/start-in-process.mjs +40 -0
- package/hub/team/cli/commands/start/start-mux.mjs +73 -0
- package/hub/team/cli/commands/start/start-wt.mjs +69 -0
- package/hub/team/cli/commands/status.mjs +87 -0
- package/hub/team/cli/commands/stop.mjs +31 -0
- package/hub/team/cli/commands/task.mjs +30 -0
- package/hub/team/cli/commands/tasks.mjs +13 -0
- package/hub/team/cli/help.mjs +42 -0
- package/hub/team/cli/index.mjs +41 -0
- package/hub/team/cli/manifest.mjs +29 -0
- package/hub/team/cli/render.mjs +30 -0
- package/hub/team/cli/services/attach-fallback.mjs +54 -0
- package/hub/team/cli/services/hub-client.mjs +227 -0
- package/hub/team/cli/services/member-selector.mjs +30 -0
- package/hub/team/cli/services/native-control.mjs +117 -0
- package/hub/team/cli/services/runtime-mode.mjs +62 -0
- package/hub/team/cli/services/state-store.mjs +48 -0
- package/hub/team/cli/services/task-model.mjs +30 -0
- package/hub/team/conductor-mesh-bridge.mjs +121 -0
- package/hub/team/conductor.mjs +671 -0
- package/hub/team/dashboard-anchor.mjs +14 -0
- package/hub/team/dashboard-layout.mjs +33 -0
- package/hub/team/dashboard-open.mjs +153 -0
- package/hub/team/dashboard.mjs +274 -0
- package/hub/team/event-log.mjs +76 -0
- package/hub/team/handoff.mjs +303 -0
- package/hub/team/headless.mjs +1156 -0
- package/hub/team/health-probe.mjs +272 -0
- package/hub/team/launcher-template.mjs +95 -0
- package/hub/team/lead-control.mjs +104 -0
- package/hub/team/native-supervisor.mjs +392 -0
- package/hub/team/native.mjs +649 -0
- package/hub/team/nativeProxy.mjs +688 -0
- package/hub/team/notify.mjs +293 -0
- package/hub/team/orchestrator.mjs +161 -0
- package/hub/team/pane.mjs +153 -0
- package/hub/team/process-cleanup.mjs +342 -0
- package/hub/team/psmux.mjs +1354 -0
- package/hub/team/remote-probe.mjs +276 -0
- package/hub/team/remote-session.mjs +299 -0
- package/hub/team/remote-watcher.mjs +478 -0
- package/hub/team/routing.mjs +223 -0
- package/hub/team/session-sync.mjs +169 -0
- package/hub/team/session.mjs +611 -0
- package/hub/team/shared.mjs +13 -0
- package/hub/team/staleState.mjs +361 -0
- package/hub/team/swarm-hypervisor.mjs +589 -0
- package/hub/team/swarm-locks.mjs +204 -0
- package/hub/team/swarm-planner.mjs +260 -0
- package/hub/team/swarm-reconciler.mjs +137 -0
- package/hub/team/tui-lite.mjs +380 -0
- package/hub/team/tui-remote-adapter.mjs +393 -0
- package/hub/team/tui-viewer.mjs +463 -0
- package/hub/team/tui.mjs +1449 -0
- package/hub/team/worktree-lifecycle.mjs +193 -0
- package/hub/team/wt-manager.mjs +407 -0
- package/hub/team/wt-templates.json +43 -0
- package/hub/team-bridge.mjs +27 -0
- package/hub/token-mode.mjs +224 -0
- package/hub/tools.mjs +636 -0
- package/hub/tray.mjs +376 -0
- package/hub/workers/claude-worker.mjs +475 -0
- package/hub/workers/codex-mcp.mjs +507 -0
- package/hub/workers/delegator-mcp.mjs +1076 -0
- package/hub/workers/factory.mjs +21 -0
- package/hub/workers/gemini-worker.mjs +374 -0
- package/hub/workers/interface.mjs +52 -0
- package/hub/workers/worker-utils.mjs +104 -0
- package/hud/colors.mjs +88 -0
- package/hud/constants.mjs +88 -0
- package/hud/context-monitor.mjs +403 -0
- package/hud/hud-qos-status.mjs +210 -0
- package/hud/providers/claude.mjs +314 -0
- package/hud/providers/codex.mjs +151 -0
- package/hud/providers/gemini.mjs +320 -0
- package/hud/renderers.mjs +442 -0
- package/hud/terminal.mjs +140 -0
- package/hud/utils.mjs +313 -0
- package/mesh/index.mjs +63 -0
- package/mesh/mesh-budget.mjs +128 -0
- package/mesh/mesh-heartbeat.mjs +100 -0
- package/mesh/mesh-protocol.mjs +96 -0
- package/mesh/mesh-queue.mjs +165 -0
- package/mesh/mesh-registry.mjs +78 -0
- package/mesh/mesh-router.mjs +76 -0
- package/package.json +8 -1
- package/references/hosts.json +33 -0
- package/scripts/__tests__/gen-skill-docs.test.mjs +87 -0
- package/scripts/__tests__/keyword-detector.test.mjs +234 -0
- package/scripts/__tests__/mcp-guard-engine.test.mjs +118 -0
- package/scripts/__tests__/remote-spawn-transfer.test.mjs +117 -0
- package/scripts/__tests__/remote-spawn.test.mjs +92 -0
- package/scripts/__tests__/skill-template.test.mjs +193 -0
- package/scripts/__tests__/smoke.test.mjs +34 -0
- package/scripts/cache-buildup.mjs +30 -0
- package/scripts/cache-doctor.mjs +149 -0
- package/scripts/cache-warmup.mjs +557 -0
- package/scripts/claudemd-sync.mjs +148 -0
- package/scripts/cli-route.sh +3 -0
- package/scripts/completions/tfx.bash +47 -0
- package/scripts/completions/tfx.fish +44 -0
- package/scripts/completions/tfx.zsh +83 -0
- package/scripts/cross-review-gate.mjs +126 -0
- package/scripts/cross-review-tracker.mjs +238 -0
- package/scripts/gen-skill-docs.mjs +111 -0
- package/scripts/headless-guard-fast.sh +21 -0
- package/scripts/headless-guard.mjs +360 -0
- package/scripts/hub-ensure.mjs +120 -0
- package/scripts/keyword-detector.mjs +272 -0
- package/scripts/keyword-rules-expander.mjs +521 -0
- package/scripts/lib/claudemd-scanner.mjs +218 -0
- package/scripts/lib/context.mjs +67 -0
- package/scripts/lib/cross-review-utils.mjs +51 -0
- package/scripts/lib/env-probe.mjs +241 -0
- package/scripts/lib/gemini-profiles.mjs +85 -0
- package/scripts/lib/handoff.mjs +171 -0
- package/scripts/lib/hook-utils.mjs +14 -0
- package/scripts/lib/keyword-rules.mjs +166 -0
- package/scripts/lib/logger.mjs +105 -0
- package/scripts/lib/mcp-filter.mjs +739 -0
- package/scripts/lib/mcp-guard-engine.mjs +954 -0
- package/scripts/lib/mcp-manifest.mjs +79 -0
- package/scripts/lib/mcp-server-catalog.mjs +118 -0
- package/scripts/lib/psmux-info.mjs +119 -0
- package/scripts/lib/remote-spawn-transfer.mjs +196 -0
- package/scripts/lib/skill-template.mjs +326 -0
- package/scripts/mcp-check.mjs +237 -0
- package/scripts/mcp-cleanup.ps1 +17 -0
- package/scripts/mcp-gateway-config.mjs +207 -0
- package/scripts/mcp-gateway-ensure.mjs +85 -0
- package/scripts/mcp-gateway-integration-test.mjs +228 -0
- package/scripts/mcp-gateway-start.mjs +226 -0
- package/scripts/mcp-gateway-start.ps1 +141 -0
- package/scripts/mcp-gateway-verify.mjs +77 -0
- package/scripts/mcp-safety-guard.mjs +44 -0
- package/scripts/notion-read.mjs +556 -0
- package/scripts/pack.mjs +295 -0
- package/scripts/preflight-cache.mjs +69 -0
- package/scripts/preinstall.mjs +96 -0
- package/scripts/remote-spawn.mjs +1376 -0
- package/scripts/run.cjs +79 -0
- package/scripts/session-spawn-helper.mjs +185 -0
- package/scripts/setup.mjs +1178 -0
- package/scripts/test-lock.mjs +71 -0
- package/scripts/test-tfx-route-no-claude-native.mjs +57 -0
- package/scripts/tfx-batch-stats.mjs +96 -0
- package/scripts/tfx-gate-activate.mjs +89 -0
- package/scripts/tfx-route-post.mjs +505 -0
- package/scripts/tfx-route-worker.mjs +223 -0
- package/scripts/tfx-route.sh +2014 -0
- package/scripts/tmp-cleanup.mjs +103 -0
- package/scripts/token-snapshot.mjs +575 -0
- package/skills/tfx-auto/SKILL.md.tmpl +2 -3
- package/skills/tfx-autoresearch/SKILL.md +6 -5
- package/skills/tfx-codex/SKILL.md.tmpl +2 -3
- package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +33 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/eval_metadata.json +42 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +11 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/analysis.md +87 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/classification.md +35 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/commands.sh +275 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/routing.md +56 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/timing.json +5 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +11 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/analysis.md +92 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/classification.md +71 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/commands.sh +264 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/routing.md +113 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/timing.json +5 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/eval_metadata.json +32 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +9 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/analysis.md +96 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/classification.md +38 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/commands.sh +151 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/routing.md +51 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/timing.json +5 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +9 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/analysis.md +127 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/classification.md +57 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/commands.sh +129 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/routing.md +84 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/timing.json +5 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/eval_metadata.json +27 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +8 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/analysis.md +98 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/classification.md +65 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/commands.sh +123 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/routing.md +66 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/timing.json +5 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +8 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/analysis.md +88 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/classification.md +40 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/commands.sh +130 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/routing.md +61 -0
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/timing.json +5 -0
- package/skills/tfx-deep-interview/SKILL.md +1 -2
- package/skills/tfx-plan/SKILL.md.tmpl +2 -3
- package/skills/tfx-psmux-rules/SKILL.md +11 -2
- package/skills/tfx-qa/SKILL.md.tmpl +2 -3
- package/skills/tfx-remote-spawn/SKILL.md +8 -11
- package/skills/tfx-research/SKILL.md.tmpl +2 -3
- package/skills/tfx-review/SKILL.md.tmpl +2 -3
- package/skills/tfx-workspace/async-tests/run-tests.sh +203 -0
- package/skills/tfx-workspace/evals/evals.json +79 -0
- package/skills/tfx-workspace/iteration-1/benchmark.json +162 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +11 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +9 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +154 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +9 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +126 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +11 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +9 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +119 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +9 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +115 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +10 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +8 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +86 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +8 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +81 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +12 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +10 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +316 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +10 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +352 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/review.html +1325 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +12 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +10 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +97 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +10 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +94 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +12 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +10 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +209 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +10 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +193 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-2/benchmark.json +62 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +13 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +11 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +382 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +11 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +333 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-2/review.html +1325 -0
- package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +217 -0
- package/skills/{tfx-auto-codex/SKILL.md.tmpl → tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md} +3 -31
- package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +65 -0
- package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +94 -0
- package/skills/{tfx-gemini/SKILL.md.tmpl → tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md} +6 -14
- package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +133 -0
- package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +426 -0
- package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +101 -0
- package/skills/merge-worktree/SKILL.md.tmpl +0 -144
- package/skills/shared/arguments-processing.md +0 -2
- package/skills/shared/mandatory-rules.md +0 -6
- package/skills/shared/telemetry-segment.md +0 -6
- package/skills/star-prompt/SKILL.md.tmpl +0 -122
- package/skills/tfx-analysis/SKILL.md.tmpl +0 -106
- package/skills/tfx-analysis/skill.json +0 -11
- package/skills/tfx-auto/skill.json +0 -26
- package/skills/tfx-auto-codex/skill.json +0 -8
- package/skills/tfx-autopilot/SKILL.md.tmpl +0 -115
- package/skills/tfx-autopilot/skill.json +0 -10
- package/skills/tfx-autoresearch/SKILL.md.tmpl +0 -135
- package/skills/tfx-autoresearch/skill.json +0 -14
- package/skills/tfx-autoroute/SKILL.md.tmpl +0 -188
- package/skills/tfx-autoroute/skill.json +0 -12
- package/skills/tfx-codex/skill.json +0 -8
- package/skills/tfx-codex-swarm/SKILL.md.tmpl +0 -16
- package/skills/tfx-codex-swarm/skill.json +0 -5
- package/skills/tfx-consensus/SKILL.md.tmpl +0 -145
- package/skills/tfx-consensus/skill.json +0 -8
- package/skills/tfx-debate/SKILL.md.tmpl +0 -191
- package/skills/tfx-debate/skill.json +0 -12
- package/skills/tfx-deep-analysis/SKILL.md.tmpl +0 -227
- package/skills/tfx-deep-analysis/skill.json +0 -10
- package/skills/tfx-deep-interview/SKILL.md.tmpl +0 -203
- package/skills/tfx-deep-interview/skill.json +0 -12
- package/skills/tfx-deep-plan/SKILL.md.tmpl +0 -281
- package/skills/tfx-deep-plan/skill.json +0 -13
- package/skills/tfx-deep-qa/SKILL.md.tmpl +0 -164
- package/skills/tfx-deep-qa/skill.json +0 -11
- package/skills/tfx-deep-research/SKILL.md.tmpl +0 -216
- package/skills/tfx-deep-research/skill.json +0 -14
- package/skills/tfx-deep-review/SKILL.md.tmpl +0 -178
- package/skills/tfx-deep-review/skill.json +0 -12
- package/skills/tfx-doctor/SKILL.md.tmpl +0 -172
- package/skills/tfx-doctor/skill.json +0 -8
- package/skills/tfx-find/skill.json +0 -12
- package/skills/tfx-forge/SKILL.md.tmpl +0 -187
- package/skills/tfx-forge/skill.json +0 -12
- package/skills/tfx-fullcycle/SKILL.md.tmpl +0 -285
- package/skills/tfx-fullcycle/skill.json +0 -11
- package/skills/tfx-gemini/skill.json +0 -8
- package/skills/tfx-hooks/SKILL.md.tmpl +0 -216
- package/skills/tfx-hooks/skill.json +0 -8
- package/skills/tfx-hub/SKILL.md.tmpl +0 -212
- package/skills/tfx-hub/skill.json +0 -8
- package/skills/tfx-index/skill.json +0 -11
- package/skills/tfx-interview/SKILL.md.tmpl +0 -284
- package/skills/tfx-interview/skill.json +0 -12
- package/skills/tfx-multi/SKILL.md.tmpl +0 -183
- package/skills/tfx-multi/skill.json +0 -8
- package/skills/tfx-panel/SKILL.md.tmpl +0 -188
- package/skills/tfx-panel/skill.json +0 -12
- package/skills/tfx-persist/SKILL.md.tmpl +0 -269
- package/skills/tfx-persist/skill.json +0 -12
- package/skills/tfx-plan/skill.json +0 -11
- package/skills/tfx-profile/SKILL.md.tmpl +0 -239
- package/skills/tfx-profile/skill.json +0 -8
- package/skills/tfx-prune/SKILL.md.tmpl +0 -199
- package/skills/tfx-prune/skill.json +0 -12
- package/skills/tfx-psmux-rules/SKILL.md.tmpl +0 -317
- package/skills/tfx-psmux-rules/skill.json +0 -8
- package/skills/tfx-qa/skill.json +0 -11
- package/skills/tfx-ralph/SKILL.md.tmpl +0 -27
- package/skills/tfx-ralph/skill.json +0 -8
- package/skills/tfx-remote-setup/SKILL.md.tmpl +0 -576
- package/skills/tfx-remote-setup/skill.json +0 -8
- package/skills/tfx-remote-spawn/SKILL.md.tmpl +0 -263
- package/skills/tfx-remote-spawn/skill.json +0 -9
- package/skills/tfx-research/skill.json +0 -13
- package/skills/tfx-review/skill.json +0 -11
- package/skills/tfx-setup/SKILL.md.tmpl +0 -380
- package/skills/tfx-setup/skill.json +0 -8
- package/skills/tfx-swarm/SKILL.md.tmpl +0 -154
- package/skills/tfx-swarm/skill.json +0 -5
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// hub/team/worktree-lifecycle.mjs — Git worktree lifecycle management
|
|
2
|
+
// Replaces shell commands in tfx-codex-swarm Step 5 + merge-worktree Phase 6.
|
|
3
|
+
// Convention: .codex-swarm/wt-{slug} paths, swarm/{runId}/{slug} branches.
|
|
4
|
+
// Remote support: host option → SSH-based git operations via remote-session.mjs.
|
|
5
|
+
|
|
6
|
+
import { execFile } from 'node:child_process';
|
|
7
|
+
import { resolve, normalize } from 'node:path';
|
|
8
|
+
import { mkdir, rm, access } from 'node:fs/promises';
|
|
9
|
+
import { remoteGit } from './remote-session.mjs';
|
|
10
|
+
|
|
11
|
+
const SWARM_ROOT = '.codex-swarm';
|
|
12
|
+
const SLEEP_MS = 2000; // WT race-guard (MEMORY.md: wt-attach-spacing)
|
|
13
|
+
|
|
14
|
+
function git(args, cwd) {
|
|
15
|
+
return new Promise((res, rej) => {
|
|
16
|
+
execFile('git', args, { cwd, windowsHide: true, timeout: 30_000 }, (err, stdout, stderr) => {
|
|
17
|
+
if (err) {
|
|
18
|
+
const msg = `git ${args[0]} failed: ${stderr?.trim() || err.message}`;
|
|
19
|
+
rej(new Error(msg));
|
|
20
|
+
} else {
|
|
21
|
+
res(stdout.trim());
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sleep(ms) {
|
|
28
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Normalize path for Windows compatibility. */
|
|
32
|
+
function normPath(p) {
|
|
33
|
+
return normalize(p).replace(/\\/g, '/');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a worktree for a shard.
|
|
38
|
+
*
|
|
39
|
+
* @param {object} opts
|
|
40
|
+
* @param {string} opts.slug — shard identifier (e.g. "issue-42" or "auth-refactor")
|
|
41
|
+
* @param {string} opts.runId — swarm run ID
|
|
42
|
+
* @param {string} [opts.rootDir=process.cwd()] — repo root
|
|
43
|
+
* @param {string} [opts.baseBranch='main'] — base branch to branch from
|
|
44
|
+
* @param {string} [opts.host] — SSH host for remote worktree creation
|
|
45
|
+
* @param {object} [opts.remoteEnv] — remote environment from probeRemoteEnv()
|
|
46
|
+
* @returns {Promise<{ worktreePath: string, branchName: string, remote: boolean }>}
|
|
47
|
+
*/
|
|
48
|
+
export async function ensureWorktree({ slug, runId, rootDir = process.cwd(), baseBranch = 'main', host, remoteEnv }) {
|
|
49
|
+
const branchName = `swarm/${runId}/${slug}`;
|
|
50
|
+
|
|
51
|
+
// ── Remote path: SSH-based worktree creation ──
|
|
52
|
+
if (host && remoteEnv) {
|
|
53
|
+
const remoteRoot = rootDir.replace(/\\/g, '/');
|
|
54
|
+
const remoteWtDir = `${remoteRoot}/${SWARM_ROOT}/wt-${slug}`;
|
|
55
|
+
|
|
56
|
+
try { remoteGit(host, remoteEnv, ['worktree', 'prune'], remoteRoot); } catch { /* best-effort */ }
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
remoteGit(host, remoteEnv, ['worktree', 'add', remoteWtDir, '-b', branchName, baseBranch], remoteRoot);
|
|
60
|
+
} catch {
|
|
61
|
+
try {
|
|
62
|
+
remoteGit(host, remoteEnv, ['worktree', 'add', remoteWtDir, branchName], remoteRoot);
|
|
63
|
+
} catch { /* already exists — acceptable */ }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { worktreePath: remoteWtDir, branchName, remote: true };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Local path (existing logic) ──
|
|
70
|
+
const wtDir = resolve(rootDir, SWARM_ROOT, `wt-${slug}`);
|
|
71
|
+
|
|
72
|
+
await mkdir(resolve(rootDir, SWARM_ROOT), { recursive: true });
|
|
73
|
+
|
|
74
|
+
// Check if worktree already exists
|
|
75
|
+
try {
|
|
76
|
+
await access(wtDir);
|
|
77
|
+
await git(['rev-parse', '--is-inside-work-tree'], wtDir);
|
|
78
|
+
return { worktreePath: normPath(wtDir), branchName, remote: false };
|
|
79
|
+
} catch {
|
|
80
|
+
// Doesn't exist or invalid — create fresh
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await git(['worktree', 'prune'], rootDir);
|
|
85
|
+
} catch { /* best-effort */ }
|
|
86
|
+
|
|
87
|
+
await sleep(SLEEP_MS);
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
await git(['worktree', 'add', wtDir, '-b', branchName, baseBranch], rootDir);
|
|
91
|
+
} catch {
|
|
92
|
+
await git(['worktree', 'add', wtDir, branchName], rootDir);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { worktreePath: normPath(wtDir), branchName, remote: false };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create a temporary integration branch for merge operations.
|
|
100
|
+
*
|
|
101
|
+
* @param {object} opts
|
|
102
|
+
* @param {string} opts.runId
|
|
103
|
+
* @param {string} opts.baseBranch
|
|
104
|
+
* @param {string} [opts.rootDir=process.cwd()]
|
|
105
|
+
* @returns {Promise<{ integrationBranch: string, baseCommit: string }>}
|
|
106
|
+
*/
|
|
107
|
+
export async function prepareIntegrationBranch({ runId, baseBranch, rootDir = process.cwd() }) {
|
|
108
|
+
const integrationBranch = `swarm/${runId}/merge`;
|
|
109
|
+
|
|
110
|
+
// Record base commit for rollback
|
|
111
|
+
const baseCommit = await git(['rev-parse', baseBranch], rootDir);
|
|
112
|
+
|
|
113
|
+
// Create integration branch from base
|
|
114
|
+
try {
|
|
115
|
+
await git(['branch', integrationBranch, baseBranch], rootDir);
|
|
116
|
+
} catch {
|
|
117
|
+
// Branch may already exist — reset to base
|
|
118
|
+
await git(['branch', '-f', integrationBranch, baseBranch], rootDir);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { integrationBranch, baseCommit };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Rebase a shard branch onto the integration branch.
|
|
126
|
+
* Uses rebase + fast-forward only (no merge commits).
|
|
127
|
+
*
|
|
128
|
+
* @param {object} opts
|
|
129
|
+
* @param {string} opts.shardBranch — the shard's branch name
|
|
130
|
+
* @param {string} opts.integrationBranch — target integration branch
|
|
131
|
+
* @param {string} [opts.rootDir=process.cwd()]
|
|
132
|
+
* @returns {Promise<{ ok: boolean, headCommit?: string, error?: string }>}
|
|
133
|
+
*/
|
|
134
|
+
export async function rebaseShardOntoIntegration({ shardBranch, integrationBranch, rootDir = process.cwd() }) {
|
|
135
|
+
// Backup integration HEAD for rollback
|
|
136
|
+
const backupCommit = await git(['rev-parse', integrationBranch], rootDir);
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// Rebase shard onto integration
|
|
140
|
+
await git(['rebase', integrationBranch, shardBranch], rootDir);
|
|
141
|
+
|
|
142
|
+
// Fast-forward integration to include shard changes
|
|
143
|
+
await git(['checkout', integrationBranch], rootDir);
|
|
144
|
+
await git(['merge', '--ff-only', shardBranch], rootDir);
|
|
145
|
+
|
|
146
|
+
const headCommit = await git(['rev-parse', 'HEAD'], rootDir);
|
|
147
|
+
return { ok: true, headCommit };
|
|
148
|
+
} catch (err) {
|
|
149
|
+
// Abort rebase and restore integration branch
|
|
150
|
+
try { await git(['rebase', '--abort'], rootDir); } catch { /* already clean */ }
|
|
151
|
+
try { await git(['checkout', integrationBranch], rootDir); } catch { /* best-effort */ }
|
|
152
|
+
try { await git(['reset', '--hard', backupCommit], rootDir); } catch { /* best-effort */ }
|
|
153
|
+
|
|
154
|
+
return { ok: false, error: err.message };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Remove a worktree and its branch.
|
|
160
|
+
* Follows WT race-guard: sleep between operations.
|
|
161
|
+
*
|
|
162
|
+
* @param {object} opts
|
|
163
|
+
* @param {string} opts.worktreePath
|
|
164
|
+
* @param {string} [opts.branchName] — optional branch to delete
|
|
165
|
+
* @param {string} [opts.rootDir=process.cwd()]
|
|
166
|
+
* @param {boolean} [opts.force=false]
|
|
167
|
+
*/
|
|
168
|
+
export async function pruneWorktree({ worktreePath, branchName, rootDir = process.cwd(), force = false }) {
|
|
169
|
+
const forceFlag = force ? '--force' : '';
|
|
170
|
+
|
|
171
|
+
// Remove worktree (with retry for Windows file handle issues — E5)
|
|
172
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
173
|
+
try {
|
|
174
|
+
await git(['worktree', 'remove', worktreePath, ...(forceFlag ? [forceFlag] : [])], rootDir);
|
|
175
|
+
break;
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (attempt === 2) {
|
|
178
|
+
// Last resort: rm the directory and prune
|
|
179
|
+
try { await rm(worktreePath, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
180
|
+
}
|
|
181
|
+
await sleep(SLEEP_MS);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Prune stale worktree references
|
|
186
|
+
await sleep(SLEEP_MS);
|
|
187
|
+
try { await git(['worktree', 'prune'], rootDir); } catch { /* best-effort */ }
|
|
188
|
+
|
|
189
|
+
// Delete branch if specified
|
|
190
|
+
if (branchName) {
|
|
191
|
+
try { await git(['branch', '-D', branchName], rootDir); } catch { /* may not exist */ }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import childProcess from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { platform as osPlatform, tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { sendKeysToPane } from "./psmux.mjs";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_WINDOW_NAME = "triflux";
|
|
9
|
+
const DEFAULT_MAX_TABS = 8;
|
|
10
|
+
const DEFAULT_TAB_CREATE_DELAY_MS = 500;
|
|
11
|
+
const DEFAULT_WAIT_TIMEOUT_MS = 5_000;
|
|
12
|
+
const DEFAULT_WAIT_POLL_MS = 300;
|
|
13
|
+
|
|
14
|
+
function sleep(ms) {
|
|
15
|
+
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolvePositiveInteger(...values) {
|
|
19
|
+
for (const value of values) {
|
|
20
|
+
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
21
|
+
if (Number.isInteger(parsed) && parsed > 0) {
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sanitizeTitleForPidFile(title) {
|
|
29
|
+
const sanitized = [...String(title).trim()]
|
|
30
|
+
.map((character) => {
|
|
31
|
+
const code = character.charCodeAt(0);
|
|
32
|
+
return code <= 31 || '<>:"/\\|?*'.includes(character) ? "_" : character;
|
|
33
|
+
})
|
|
34
|
+
.join("");
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
sanitized
|
|
38
|
+
.replace(/\s+/gu, "-")
|
|
39
|
+
.replace(/_+/gu, "_")
|
|
40
|
+
.replace(/-+/gu, "-")
|
|
41
|
+
.replace(/^[-_]+|[-_]+$/gu, "") || "wt-tab"
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function escapePowerShellSingleQuoted(value) {
|
|
46
|
+
return String(value).replace(/'/g, "''");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildPidFilePath(pidDir, title) {
|
|
50
|
+
return join(pidDir, `${sanitizeTitleForPidFile(title)}.pid`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildWrappedCommand(pidFile, command) {
|
|
54
|
+
const escapedPidFile = escapePowerShellSingleQuoted(pidFile);
|
|
55
|
+
const pidWrite = `$PID | Set-Content '${escapedPidFile}'`;
|
|
56
|
+
return command ? `${pidWrite}; ${command}` : pidWrite;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* PowerShell -EncodedCommand용 Base64 인코딩.
|
|
61
|
+
* CreateProcess 이중 쿼팅 문제를 완전히 회피한다.
|
|
62
|
+
*/
|
|
63
|
+
function encodeForPowerShell(script) {
|
|
64
|
+
const buf = Buffer.from(script, 'utf16le');
|
|
65
|
+
return buf.toString('base64');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function matchesTitlePattern(title, pattern) {
|
|
69
|
+
if (!pattern) return true;
|
|
70
|
+
if (pattern instanceof RegExp) return pattern.test(title);
|
|
71
|
+
return String(title).includes(String(pattern));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function defaultIsPidAlive(pid, execFileSyncFn = childProcess.execFileSync) {
|
|
75
|
+
try {
|
|
76
|
+
process.kill(pid, 0);
|
|
77
|
+
return true;
|
|
78
|
+
} catch {
|
|
79
|
+
// tasklist fallback — process.kill(..., 0)이 권한/플랫폼 차이로 실패할 수 있음.
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const output = execFileSyncFn(
|
|
84
|
+
"tasklist",
|
|
85
|
+
["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"],
|
|
86
|
+
{
|
|
87
|
+
encoding: "utf8",
|
|
88
|
+
timeout: 3_000,
|
|
89
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
90
|
+
windowsHide: true,
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
return (
|
|
94
|
+
!/No tasks are running/u.test(String(output)) &&
|
|
95
|
+
String(output).includes(`"${pid}"`)
|
|
96
|
+
);
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Windows Terminal 탭/세션 라이프사이클 관리자.
|
|
104
|
+
*
|
|
105
|
+
* @param {object} [opts]
|
|
106
|
+
* @param {string} [opts.windowName='triflux']
|
|
107
|
+
* @param {number} [opts.maxTabs=8]
|
|
108
|
+
* @param {string} [opts.pidDir=os.tmpdir()/wt-manager-pids]
|
|
109
|
+
* @param {number} [opts.tabCreateDelayMs=500]
|
|
110
|
+
* @param {object} [opts.deps] — 테스트용 의존성 주입
|
|
111
|
+
*/
|
|
112
|
+
export function createWtManager(opts = {}) {
|
|
113
|
+
const deps = opts.deps || {};
|
|
114
|
+
const platform = deps.platform || osPlatform;
|
|
115
|
+
|
|
116
|
+
if (platform() !== "win32") {
|
|
117
|
+
throw new Error("wt-manager.mjs is Windows-only");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const now = deps.now || Date.now;
|
|
121
|
+
const sleepFn = deps.sleep || sleep;
|
|
122
|
+
const spawnFn = deps.spawn || childProcess.spawn;
|
|
123
|
+
const execFileSyncFn = deps.execFileSync || childProcess.execFileSync;
|
|
124
|
+
const killFn = deps.kill || ((pid) => process.kill(pid));
|
|
125
|
+
const sendKeysFn = deps.sendKeysToPane || sendKeysToPane;
|
|
126
|
+
const isPidAlive =
|
|
127
|
+
deps.isPidAlive || ((pid) => defaultIsPidAlive(pid, execFileSyncFn));
|
|
128
|
+
const ensureDir =
|
|
129
|
+
deps.ensureDir || ((dir) => mkdirSync(dir, { recursive: true }));
|
|
130
|
+
const exists = deps.exists || existsSync;
|
|
131
|
+
const readText =
|
|
132
|
+
deps.readText || ((filePath) => readFileSync(filePath, "utf8"));
|
|
133
|
+
const removeFile =
|
|
134
|
+
deps.removeFile || ((filePath) => rmSync(filePath, { force: true }));
|
|
135
|
+
|
|
136
|
+
const windowName = String(opts.windowName || DEFAULT_WINDOW_NAME);
|
|
137
|
+
const maxTabs =
|
|
138
|
+
resolvePositiveInteger(
|
|
139
|
+
opts.maxTabs,
|
|
140
|
+
process.env.WTM_MAX_TABS,
|
|
141
|
+
DEFAULT_MAX_TABS,
|
|
142
|
+
) || DEFAULT_MAX_TABS;
|
|
143
|
+
const pidDir = String(opts.pidDir || join(tmpdir(), "wt-manager-pids"));
|
|
144
|
+
const tabCreateDelayMs =
|
|
145
|
+
resolvePositiveInteger(
|
|
146
|
+
opts.tabCreateDelayMs,
|
|
147
|
+
DEFAULT_TAB_CREATE_DELAY_MS,
|
|
148
|
+
) || DEFAULT_TAB_CREATE_DELAY_MS;
|
|
149
|
+
|
|
150
|
+
ensureDir(pidDir);
|
|
151
|
+
|
|
152
|
+
/** @type {Map<string, { pid: number, createdAt: number, pidFile: string }>} */
|
|
153
|
+
const tabs = new Map();
|
|
154
|
+
let lastTabCreateAt = null;
|
|
155
|
+
|
|
156
|
+
function forgetTab(title) {
|
|
157
|
+
const entry = tabs.get(title);
|
|
158
|
+
if (entry) {
|
|
159
|
+
tabs.delete(title);
|
|
160
|
+
try {
|
|
161
|
+
removeFile(entry.pidFile);
|
|
162
|
+
} catch {
|
|
163
|
+
/* ignore */
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function pruneDeadTabs() {
|
|
169
|
+
for (const [title, entry] of tabs.entries()) {
|
|
170
|
+
if (!isPidAlive(entry.pid)) {
|
|
171
|
+
forgetTab(title);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function throttleTabCreate() {
|
|
177
|
+
if (lastTabCreateAt == null) return;
|
|
178
|
+
const elapsed = now() - lastTabCreateAt;
|
|
179
|
+
if (elapsed < tabCreateDelayMs) {
|
|
180
|
+
await sleepFn(tabCreateDelayMs - elapsed);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function waitTabReady(title, pidFile) {
|
|
185
|
+
const deadline = now() + DEFAULT_WAIT_TIMEOUT_MS;
|
|
186
|
+
|
|
187
|
+
while (now() <= deadline) {
|
|
188
|
+
if (exists(pidFile)) {
|
|
189
|
+
const pid = Number.parseInt(String(readText(pidFile)).trim(), 10);
|
|
190
|
+
if (Number.isInteger(pid) && pid > 0 && isPidAlive(pid)) {
|
|
191
|
+
return pid;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const remaining = deadline - now();
|
|
196
|
+
if (remaining <= 0) break;
|
|
197
|
+
await sleepFn(Math.min(DEFAULT_WAIT_POLL_MS, remaining));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
throw new Error(`WT tab ready timeout: ${title}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function createTab(tab = {}) {
|
|
204
|
+
const title = String(tab.title || "").trim();
|
|
205
|
+
if (!title) {
|
|
206
|
+
throw new Error("title is required");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
pruneDeadTabs();
|
|
210
|
+
|
|
211
|
+
if (tabs.has(title)) {
|
|
212
|
+
throw new Error(`WT tab already exists: ${title}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (tabs.size >= maxTabs) {
|
|
216
|
+
throw new Error(`WT max tabs exceeded (${maxTabs})`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
await throttleTabCreate();
|
|
220
|
+
|
|
221
|
+
const pidFile = buildPidFilePath(pidDir, title);
|
|
222
|
+
try {
|
|
223
|
+
removeFile(pidFile);
|
|
224
|
+
} catch {
|
|
225
|
+
/* ignore */
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const args = ["-w", windowName, "nt", "--title", title];
|
|
229
|
+
if (tab.profile) {
|
|
230
|
+
args.push("--profile", String(tab.profile));
|
|
231
|
+
}
|
|
232
|
+
if (tab.cwd) {
|
|
233
|
+
args.push("-d", String(tab.cwd));
|
|
234
|
+
}
|
|
235
|
+
const script = buildWrappedCommand(pidFile, tab.command);
|
|
236
|
+
args.push(
|
|
237
|
+
"--",
|
|
238
|
+
"powershell.exe",
|
|
239
|
+
"-NoExit",
|
|
240
|
+
"-EncodedCommand",
|
|
241
|
+
encodeForPowerShell(script),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const child = spawnFn("wt.exe", args, {
|
|
245
|
+
detached: true,
|
|
246
|
+
stdio: "ignore",
|
|
247
|
+
});
|
|
248
|
+
child?.unref?.();
|
|
249
|
+
|
|
250
|
+
lastTabCreateAt = now();
|
|
251
|
+
const pid = await waitTabReady(title, pidFile);
|
|
252
|
+
const entry = Object.freeze({
|
|
253
|
+
pid,
|
|
254
|
+
createdAt: now(),
|
|
255
|
+
pidFile,
|
|
256
|
+
});
|
|
257
|
+
tabs.set(title, entry);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function closeTab(title) {
|
|
261
|
+
const normalizedTitle = String(title || "").trim();
|
|
262
|
+
if (!normalizedTitle) return;
|
|
263
|
+
|
|
264
|
+
const entry = tabs.get(normalizedTitle);
|
|
265
|
+
if (!entry) return;
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
killFn(entry.pid);
|
|
269
|
+
} catch {
|
|
270
|
+
// 이미 종료된 PID는 map 정리만 수행한다.
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
forgetTab(normalizedTitle);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function listTabs() {
|
|
277
|
+
pruneDeadTabs();
|
|
278
|
+
return [...tabs.entries()].map(([title, entry]) =>
|
|
279
|
+
Object.freeze({
|
|
280
|
+
title,
|
|
281
|
+
pid: entry.pid,
|
|
282
|
+
createdAt: entry.createdAt,
|
|
283
|
+
}),
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function closeStale(closeOpts = {}) {
|
|
288
|
+
pruneDeadTabs();
|
|
289
|
+
|
|
290
|
+
const olderThanMs = Number.isFinite(closeOpts.olderThanMs)
|
|
291
|
+
? Math.max(0, Math.trunc(closeOpts.olderThanMs))
|
|
292
|
+
: 0;
|
|
293
|
+
const titlePattern = closeOpts.titlePattern;
|
|
294
|
+
const snapshot = [...tabs.entries()];
|
|
295
|
+
let closed = 0;
|
|
296
|
+
|
|
297
|
+
for (const [title, entry] of snapshot) {
|
|
298
|
+
const ageMs = now() - entry.createdAt;
|
|
299
|
+
if (ageMs < olderThanMs) continue;
|
|
300
|
+
if (!matchesTitlePattern(title, titlePattern)) continue;
|
|
301
|
+
await closeTab(title);
|
|
302
|
+
closed += 1;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return closed;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* 현재 탭을 split-pane으로 분할.
|
|
310
|
+
* @param {object} opts
|
|
311
|
+
* @param {'H'|'V'} [opts.direction='V'] — H=좌우, V=상하
|
|
312
|
+
* @param {string} [opts.title]
|
|
313
|
+
* @param {string} [opts.profile]
|
|
314
|
+
* @param {string} [opts.cwd]
|
|
315
|
+
* @param {string} [opts.command] — pane에서 실행할 명령
|
|
316
|
+
* @param {number} [opts.size] — 퍼센트 (0-100)
|
|
317
|
+
*/
|
|
318
|
+
async function splitPane(opts = {}) {
|
|
319
|
+
const direction = opts.direction === 'H' ? '-H' : '-V';
|
|
320
|
+
const args = ['-w', windowName, 'sp', direction];
|
|
321
|
+
|
|
322
|
+
if (opts.title) {
|
|
323
|
+
args.push('--title', String(opts.title));
|
|
324
|
+
}
|
|
325
|
+
if (opts.profile) {
|
|
326
|
+
args.push('--profile', String(opts.profile));
|
|
327
|
+
}
|
|
328
|
+
if (opts.size && Number.isFinite(opts.size)) {
|
|
329
|
+
args.push('-s', String(opts.size / 100));
|
|
330
|
+
}
|
|
331
|
+
if (opts.cwd) {
|
|
332
|
+
args.push('-d', String(opts.cwd));
|
|
333
|
+
}
|
|
334
|
+
if (opts.command) {
|
|
335
|
+
const script = opts.command;
|
|
336
|
+
args.push('--', 'powershell.exe', '-NoExit', '-EncodedCommand', encodeForPowerShell(script));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const child = spawnFn('wt.exe', args, {
|
|
340
|
+
detached: true,
|
|
341
|
+
stdio: 'ignore',
|
|
342
|
+
});
|
|
343
|
+
child?.unref?.();
|
|
344
|
+
|
|
345
|
+
// split-pane은 기존 탭 안에서 분할하므로 throttle 적용
|
|
346
|
+
await sleepFn(tabCreateDelayMs);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 여러 세션을 split-pane 레이아웃으로 한번에 배치.
|
|
351
|
+
* @param {Array<{title: string, command: string, direction?: 'H'|'V', size?: number}>} panes
|
|
352
|
+
*/
|
|
353
|
+
async function applySplitLayout(panes) {
|
|
354
|
+
if (!panes || panes.length === 0) return;
|
|
355
|
+
|
|
356
|
+
// 첫 번째는 새 탭으로 생성
|
|
357
|
+
const first = panes[0];
|
|
358
|
+
await createTab({ title: first.title, command: first.command, profile: first.profile });
|
|
359
|
+
|
|
360
|
+
// 나머지는 split-pane으로 분할
|
|
361
|
+
for (let i = 1; i < panes.length; i++) {
|
|
362
|
+
await splitPane({
|
|
363
|
+
direction: panes[i].direction || 'V',
|
|
364
|
+
title: panes[i].title,
|
|
365
|
+
command: panes[i].command,
|
|
366
|
+
profile: panes[i].profile,
|
|
367
|
+
size: panes[i].size,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function createSession(sessionOpts = {}) {
|
|
373
|
+
const tab =
|
|
374
|
+
typeof sessionOpts.tab === "string"
|
|
375
|
+
? { title: sessionOpts.tab }
|
|
376
|
+
: sessionOpts.tab;
|
|
377
|
+
|
|
378
|
+
if (!tab || typeof tab !== "object") {
|
|
379
|
+
throw new Error("tab is required");
|
|
380
|
+
}
|
|
381
|
+
if (!sessionOpts.pane) {
|
|
382
|
+
throw new Error("pane is required");
|
|
383
|
+
}
|
|
384
|
+
if (!sessionOpts.command) {
|
|
385
|
+
throw new Error("command is required");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
await createTab(tab);
|
|
389
|
+
sendKeysFn(String(sessionOpts.pane), String(sessionOpts.command), true);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function getTabCount() {
|
|
393
|
+
pruneDeadTabs();
|
|
394
|
+
return tabs.size;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return Object.freeze({
|
|
398
|
+
createTab,
|
|
399
|
+
closeTab,
|
|
400
|
+
listTabs,
|
|
401
|
+
closeStale,
|
|
402
|
+
createSession,
|
|
403
|
+
splitPane,
|
|
404
|
+
applySplitLayout,
|
|
405
|
+
getTabCount,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"templates": {
|
|
4
|
+
"swarm-3": {
|
|
5
|
+
"description": "Lead tab plus two worker tabs in the shared triflux window.",
|
|
6
|
+
"windowName": "triflux",
|
|
7
|
+
"tabs": [
|
|
8
|
+
{
|
|
9
|
+
"title": "swarm lead",
|
|
10
|
+
"profile": "triflux"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"title": "swarm worker-1",
|
|
14
|
+
"profile": "triflux"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"title": "swarm worker-2",
|
|
18
|
+
"profile": "triflux"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"monitor": {
|
|
23
|
+
"description": "Single monitoring tab for dashboard, log tailing, or observer flows.",
|
|
24
|
+
"windowName": "triflux",
|
|
25
|
+
"tabs": [
|
|
26
|
+
{
|
|
27
|
+
"title": "swarm monitor",
|
|
28
|
+
"profile": "triflux"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"single": {
|
|
33
|
+
"description": "One focused tab for direct operator work.",
|
|
34
|
+
"windowName": "triflux",
|
|
35
|
+
"tabs": [
|
|
36
|
+
{
|
|
37
|
+
"title": "single worker",
|
|
38
|
+
"profile": "triflux"
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// @triflux/core — team-bridge 인터페이스
|
|
2
|
+
// remote 패키지가 런타임에 구현을 주입한다.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {object} TeamBridge
|
|
6
|
+
* @property {(args?: object) => Promise<object>} teamInfo
|
|
7
|
+
* @property {(args?: object) => Promise<object>} teamTaskList
|
|
8
|
+
* @property {(args?: object) => Promise<object>} teamTaskUpdate
|
|
9
|
+
* @property {(args?: object) => Promise<object>} teamSendMessage
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** @type {TeamBridge | null} */
|
|
13
|
+
let _bridge = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {TeamBridge | null} impl
|
|
17
|
+
*/
|
|
18
|
+
export function registerTeamBridge(impl) {
|
|
19
|
+
_bridge = impl;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @returns {TeamBridge | null}
|
|
24
|
+
*/
|
|
25
|
+
export function getTeamBridge() {
|
|
26
|
+
return _bridge;
|
|
27
|
+
}
|