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
package/hud/utils.mjs
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// 유틸리티 함수
|
|
3
|
+
// ============================================================================
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { dirname } from "node:path";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import {
|
|
8
|
+
PERCENT_CELL_WIDTH, TIME_CELL_INNER_WIDTH, SV_CELL_WIDTH,
|
|
9
|
+
} from "./constants.mjs";
|
|
10
|
+
|
|
11
|
+
export async function readStdinJson() {
|
|
12
|
+
if (process.stdin.isTTY) return {};
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const timeout = setTimeout(() => {
|
|
15
|
+
process.stdin.destroy();
|
|
16
|
+
resolve({});
|
|
17
|
+
}, 200);
|
|
18
|
+
const chunks = [];
|
|
19
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
20
|
+
process.stdin.on("end", () => {
|
|
21
|
+
clearTimeout(timeout);
|
|
22
|
+
const raw = chunks.join("").trim();
|
|
23
|
+
if (!raw) { resolve({}); return; }
|
|
24
|
+
try { resolve(JSON.parse(raw)); } catch { resolve({}); }
|
|
25
|
+
});
|
|
26
|
+
process.stdin.on("error", () => {
|
|
27
|
+
clearTimeout(timeout);
|
|
28
|
+
resolve({});
|
|
29
|
+
});
|
|
30
|
+
process.stdin.resume();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function readJson(filePath, fallback) {
|
|
35
|
+
if (!existsSync(filePath)) return fallback;
|
|
36
|
+
try { return JSON.parse(readFileSync(filePath, "utf-8")); } catch { return fallback; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function writeJsonSafe(filePath, data) {
|
|
40
|
+
try {
|
|
41
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
42
|
+
writeFileSync(filePath, JSON.stringify(data), { mode: 0o600 });
|
|
43
|
+
} catch { /* 쓰기 실패 무시 */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// .omc/ → .claude/cache/ 마이그레이션: 새 경로 우선, 없으면 레거시 읽고 복사
|
|
47
|
+
export function readJsonMigrate(newPath, legacyPath, fallback) {
|
|
48
|
+
const data = readJson(newPath, null);
|
|
49
|
+
if (data != null) return data;
|
|
50
|
+
const legacy = readJson(legacyPath, null);
|
|
51
|
+
if (legacy != null) { writeJsonSafe(newPath, legacy); return legacy; }
|
|
52
|
+
return fallback;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function stripAnsi(text) {
|
|
56
|
+
return String(text).replace(/\x1b\[[0-9;]*m/g, "");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getVisibleLength(text) {
|
|
60
|
+
return stripAnsi(text).length;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function padAnsi(text, width, align = "right") {
|
|
64
|
+
const len = getVisibleLength(text);
|
|
65
|
+
if (len >= width) return text;
|
|
66
|
+
const padding = " ".repeat(width - len);
|
|
67
|
+
return align === "left" ? padding + text : text + padding;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function padAnsiRight(text, width) {
|
|
71
|
+
return padAnsi(text, width, "right");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function padAnsiLeft(text, width) {
|
|
75
|
+
return padAnsi(text, width, "left");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function fitText(text, width) {
|
|
79
|
+
const t = String(text || "");
|
|
80
|
+
if (t.length <= width) return t;
|
|
81
|
+
if (width <= 1) return "…";
|
|
82
|
+
return `${t.slice(0, width - 1)}…`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* ANSI 이스케이프 시퀀스를 보존하면서 visible width 기준으로 truncate.
|
|
87
|
+
* maxWidth 초과 시 잘라내고 RESET(\x1b[0m)을 추가한다.
|
|
88
|
+
*/
|
|
89
|
+
export function truncateAnsi(text, maxWidth) {
|
|
90
|
+
if (maxWidth <= 0) return "";
|
|
91
|
+
const str = String(text);
|
|
92
|
+
let visible = 0;
|
|
93
|
+
let i = 0;
|
|
94
|
+
while (i < str.length) {
|
|
95
|
+
if (str.charCodeAt(i) === 0x1b && str[i + 1] === "[") {
|
|
96
|
+
const mIdx = str.indexOf("m", i + 2);
|
|
97
|
+
if (mIdx !== -1) {
|
|
98
|
+
i = mIdx + 1;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
visible++;
|
|
103
|
+
if (visible > maxWidth) {
|
|
104
|
+
return str.slice(0, i) + "\x1b[0m";
|
|
105
|
+
}
|
|
106
|
+
i++;
|
|
107
|
+
}
|
|
108
|
+
return str;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function makeHash(text) {
|
|
112
|
+
return createHash("sha256").update(String(text || ""), "utf8").digest("hex").slice(0, 16);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function clampPercent(value) {
|
|
116
|
+
const numeric = Number(value);
|
|
117
|
+
if (!Number.isFinite(numeric)) return 0;
|
|
118
|
+
return Math.max(0, Math.min(100, Math.round(numeric)));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function formatPercentCell(value) {
|
|
122
|
+
return `${clampPercent(value)}%`.padStart(PERCENT_CELL_WIDTH, " ");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function formatPlaceholderPercentCell() {
|
|
126
|
+
return "--%".padStart(PERCENT_CELL_WIDTH, " ");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function normalizeTimeToken(value) {
|
|
130
|
+
const text = String(value || "n/a");
|
|
131
|
+
const hourMinute = text.match(/^(\d+)h(\d+)m$/);
|
|
132
|
+
if (hourMinute) {
|
|
133
|
+
return `${Number(hourMinute[1])}h${String(Number(hourMinute[2])).padStart(2, "0")}m`;
|
|
134
|
+
}
|
|
135
|
+
const dayHour = text.match(/^(\d+)d(\d+)h$/);
|
|
136
|
+
if (dayHour) {
|
|
137
|
+
return `${String(Number(dayHour[1])).padStart(2, "0")}d${String(Number(dayHour[2])).padStart(2, "0")}h`;
|
|
138
|
+
}
|
|
139
|
+
return text;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function formatTimeCell(value) {
|
|
143
|
+
const text = normalizeTimeToken(value);
|
|
144
|
+
// 시간값(숫자 포함)은 0패딩, 비시간값(n/a 등)은 공백패딩
|
|
145
|
+
const padChar = /\d/.test(text) ? "0" : " ";
|
|
146
|
+
return `(${text.padStart(TIME_CELL_INNER_WIDTH, padChar)})`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 주간(d/h) 전용 — 최대 7d00h(5자)이므로 공백 불필요
|
|
150
|
+
export function formatTimeCellDH(value) {
|
|
151
|
+
const text = normalizeTimeToken(value);
|
|
152
|
+
return `(${text})`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function getCliArgValue(flag) {
|
|
156
|
+
const idx = process.argv.indexOf(flag);
|
|
157
|
+
if (idx < 0) return null;
|
|
158
|
+
return process.argv[idx + 1] || null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function formatDuration(ms) {
|
|
162
|
+
if (!Number.isFinite(ms) || ms <= 0) return "n/a";
|
|
163
|
+
const totalMinutes = Math.floor(ms / 60000);
|
|
164
|
+
const days = Math.floor(totalMinutes / (60 * 24));
|
|
165
|
+
const hours = Math.floor((totalMinutes % (60 * 24)) / 60);
|
|
166
|
+
const minutes = totalMinutes % 60;
|
|
167
|
+
if (days > 0) return hours > 0 ? `${days}d${hours}h` : `${days}d`;
|
|
168
|
+
if (hours > 0) return minutes > 0 ? `${hours}h${minutes}m` : `${hours}h`;
|
|
169
|
+
return `${minutes}m`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function formatTokenCount(n) {
|
|
173
|
+
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
174
|
+
if (n >= 1_000) return `${Math.round(n / 1_000)}K`;
|
|
175
|
+
return String(n);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function getContextPercent(stdin) {
|
|
179
|
+
const nativePercent = stdin?.context_window?.used_percentage;
|
|
180
|
+
if (typeof nativePercent === "number" && Number.isFinite(nativePercent)) return clampPercent(nativePercent);
|
|
181
|
+
const usage = stdin?.context_window?.current_usage || {};
|
|
182
|
+
const totalTokens = Number(usage.input_tokens || 0)
|
|
183
|
+
+ Number(usage.cache_creation_input_tokens || 0)
|
|
184
|
+
+ Number(usage.cache_read_input_tokens || 0);
|
|
185
|
+
const capacity = Number(stdin?.context_window?.context_window_size || 0);
|
|
186
|
+
if (!capacity || capacity <= 0) return 0;
|
|
187
|
+
return clampPercent((totalTokens / capacity) * 100);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 과거 리셋 시간 → 다음 주기로 순환하여 미래 시점 반환
|
|
191
|
+
export function advanceToNextCycle(epochMs, cycleMs) {
|
|
192
|
+
const now = Date.now();
|
|
193
|
+
if (epochMs >= now || !cycleMs) return epochMs;
|
|
194
|
+
const elapsed = now - epochMs;
|
|
195
|
+
return epochMs + Math.ceil(elapsed / cycleMs) * cycleMs;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function parseResetDate(isoOrUnix) {
|
|
199
|
+
if (!isoOrUnix) return null;
|
|
200
|
+
const date = typeof isoOrUnix === "string"
|
|
201
|
+
? new Date(isoOrUnix)
|
|
202
|
+
: new Date(isoOrUnix * 1000);
|
|
203
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getResetTargetMs(isoOrUnix, cycleMs = 0) {
|
|
207
|
+
const date = parseResetDate(isoOrUnix);
|
|
208
|
+
if (!date) return null;
|
|
209
|
+
return advanceToNextCycle(date.getTime(), cycleMs);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function getRemainingResetMs(isoOrUnix, cycleMs = 0) {
|
|
213
|
+
const targetMs = getResetTargetMs(isoOrUnix, cycleMs);
|
|
214
|
+
if (targetMs == null) return null;
|
|
215
|
+
return targetMs - Date.now();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function formatResetRemaining(isoOrUnix, cycleMs = 0) {
|
|
219
|
+
const diffMs = getRemainingResetMs(isoOrUnix, cycleMs);
|
|
220
|
+
if (diffMs == null || diffMs <= 0) return "";
|
|
221
|
+
const totalMinutes = Math.floor(diffMs / 60000);
|
|
222
|
+
const totalHours = Math.floor(totalMinutes / 60);
|
|
223
|
+
const minutes = totalMinutes % 60;
|
|
224
|
+
return `${totalHours}h${String(minutes).padStart(2, "0")}m`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function isResetPast(isoOrUnix) {
|
|
228
|
+
const date = parseResetDate(isoOrUnix);
|
|
229
|
+
return date != null && date.getTime() <= Date.now();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function formatResetRemainingDayHour(isoOrUnix, cycleMs = 0) {
|
|
233
|
+
const diffMs = getRemainingResetMs(isoOrUnix, cycleMs);
|
|
234
|
+
if (diffMs == null || diffMs <= 0) return "";
|
|
235
|
+
const totalMinutes = Math.floor(diffMs / 60000);
|
|
236
|
+
const days = Math.floor(totalMinutes / (60 * 24));
|
|
237
|
+
const hours = Math.floor((totalMinutes % (60 * 24)) / 60);
|
|
238
|
+
return `${String(days).padStart(2, "0")}d${String(hours).padStart(2, "0")}h`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function calcCooldownLeftSeconds(isoDatetime) {
|
|
242
|
+
if (!isoDatetime) return 0;
|
|
243
|
+
const cooldownMs = new Date(isoDatetime).getTime() - Date.now();
|
|
244
|
+
if (!Number.isFinite(cooldownMs) || cooldownMs <= 0) return 0;
|
|
245
|
+
return Math.ceil(cooldownMs / 1000);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function getProviderAccountId(provider, accountsConfig, accountsState) {
|
|
249
|
+
const providerState = accountsState?.providers?.[provider] || {};
|
|
250
|
+
const selectedId = providerState.last_selected_id;
|
|
251
|
+
if (selectedId) return selectedId;
|
|
252
|
+
const providerConfig = accountsConfig?.providers?.[provider] || [];
|
|
253
|
+
return providerConfig[0]?.id || `${provider}-main`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// JWT base64 디코딩 공통 헬퍼
|
|
257
|
+
export function decodeJwtEmail(idToken) {
|
|
258
|
+
if (!idToken) return null;
|
|
259
|
+
const parts = idToken.split(".");
|
|
260
|
+
if (parts.length < 2) return null;
|
|
261
|
+
let payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
262
|
+
while (payload.length % 4) payload += "=";
|
|
263
|
+
try {
|
|
264
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64").toString("utf-8"));
|
|
265
|
+
return decoded.email || null;
|
|
266
|
+
} catch { return null; }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// HTTPS POST (타임아웃 포함) — https 모듈은 호출자가 주입
|
|
270
|
+
export function createHttpsPost(https, timeoutMs) {
|
|
271
|
+
return function httpsPost(url, body, accessToken) {
|
|
272
|
+
return new Promise((resolve) => {
|
|
273
|
+
const urlObj = new URL(url);
|
|
274
|
+
const data = JSON.stringify(body);
|
|
275
|
+
const req = https.request({
|
|
276
|
+
hostname: urlObj.hostname,
|
|
277
|
+
path: urlObj.pathname + urlObj.search,
|
|
278
|
+
method: "POST",
|
|
279
|
+
headers: {
|
|
280
|
+
"Content-Type": "application/json",
|
|
281
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
282
|
+
"Content-Length": Buffer.byteLength(data),
|
|
283
|
+
},
|
|
284
|
+
timeout: timeoutMs,
|
|
285
|
+
}, (res) => {
|
|
286
|
+
const chunks = [];
|
|
287
|
+
res.on("data", (c) => chunks.push(c));
|
|
288
|
+
res.on("end", () => {
|
|
289
|
+
try { resolve(JSON.parse(Buffer.concat(chunks).toString())); }
|
|
290
|
+
catch { resolve(null); }
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
req.on("error", () => resolve(null));
|
|
294
|
+
req.on("timeout", () => { req.destroy(); resolve(null); });
|
|
295
|
+
req.write(data);
|
|
296
|
+
req.end();
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// sv 퍼센트 포맷 (1000+ → k 표기, 5자 고정폭)
|
|
302
|
+
export function formatSvPct(value) {
|
|
303
|
+
if (value == null) return "--%".padStart(SV_CELL_WIDTH);
|
|
304
|
+
if (value >= 10000) return `${Math.round(value / 1000)}k%`.padStart(SV_CELL_WIDTH);
|
|
305
|
+
if (value >= 1000) return `${(value / 1000).toFixed(1)}k%`.padStart(SV_CELL_WIDTH);
|
|
306
|
+
return `${value}%`.padStart(SV_CELL_WIDTH);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function formatSavings(dollars) {
|
|
310
|
+
if (dollars >= 100) return `$${Math.round(dollars)}`;
|
|
311
|
+
if (dollars >= 10) return `$${dollars.toFixed(1)}`;
|
|
312
|
+
return `$${dollars.toFixed(2)}`;
|
|
313
|
+
}
|
package/mesh/index.mjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export { MSG_TYPES, createMessage, serialize, deserialize, validate } from "./mesh-protocol.mjs";
|
|
5
|
+
export { createRegistry } from "./mesh-registry.mjs";
|
|
6
|
+
export { createMeshBudget } from "./mesh-budget.mjs";
|
|
7
|
+
export { routeMessage, routeOrDeadLetter } from "./mesh-router.mjs";
|
|
8
|
+
export { createMessageQueue } from "./mesh-queue.mjs";
|
|
9
|
+
export { createHeartbeatMonitor } from "./mesh-heartbeat.mjs";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Loads skills assigned to a specific agent from a skills directory.
|
|
13
|
+
* Reuses the same directory-scan approach as generateSkillDocs().
|
|
14
|
+
*
|
|
15
|
+
* @param {string} agentId - The agent identifier
|
|
16
|
+
* @param {string} skillsDir - Path to the skills directory
|
|
17
|
+
* @returns {Promise<string[]>} Array of skill names available to this agent
|
|
18
|
+
*/
|
|
19
|
+
export async function loadSkillsForAgent(agentId, skillsDir) {
|
|
20
|
+
if (!agentId || typeof agentId !== "string") {
|
|
21
|
+
throw new TypeError("agentId must be a non-empty string");
|
|
22
|
+
}
|
|
23
|
+
if (!skillsDir || typeof skillsDir !== "string") {
|
|
24
|
+
throw new TypeError("skillsDir must be a non-empty string");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let entries;
|
|
28
|
+
try {
|
|
29
|
+
entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const skills = [];
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (!entry.isDirectory()) continue;
|
|
37
|
+
const skillName = entry.name;
|
|
38
|
+
const skillPath = join(skillsDir, skillName, "SKILL.md");
|
|
39
|
+
let skillContent = null;
|
|
40
|
+
try {
|
|
41
|
+
const { readFileSync } = await import("node:fs");
|
|
42
|
+
skillContent = readFileSync(skillPath, "utf8");
|
|
43
|
+
} catch {
|
|
44
|
+
// Skill has no SKILL.md — include it anyway
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If SKILL.md mentions the agentId or no agent restriction, include it
|
|
48
|
+
const isRestricted = skillContent
|
|
49
|
+
? /^agents?\s*:/im.test(skillContent)
|
|
50
|
+
: false;
|
|
51
|
+
|
|
52
|
+
if (!isRestricted) {
|
|
53
|
+
skills.push(skillName);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (skillContent && skillContent.includes(agentId)) {
|
|
58
|
+
skills.push(skillName);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return skills;
|
|
63
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Warning level thresholds mirroring context-monitor.mjs classifyContextThreshold().
|
|
3
|
+
* Reproduced here to avoid cross-module dependency.
|
|
4
|
+
*/
|
|
5
|
+
const WARNING_LEVELS = Object.freeze({
|
|
6
|
+
critical: 90,
|
|
7
|
+
warn: 80,
|
|
8
|
+
info: 60,
|
|
9
|
+
ok: 0,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Clamps a percentage value to [0, 100].
|
|
14
|
+
* @param {number} value
|
|
15
|
+
* @returns {number}
|
|
16
|
+
*/
|
|
17
|
+
function clampPercent(value) {
|
|
18
|
+
const n = Number(value);
|
|
19
|
+
if (!Number.isFinite(n)) return 0;
|
|
20
|
+
return Math.min(100, Math.max(0, n));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Classifies a usage percentage into a warning level.
|
|
25
|
+
* Mirrors context-monitor.mjs classifyContextThreshold().
|
|
26
|
+
* @param {number} percent
|
|
27
|
+
* @returns {{ level: string, message: string }}
|
|
28
|
+
*/
|
|
29
|
+
function classifyLevel(percent) {
|
|
30
|
+
const p = clampPercent(percent);
|
|
31
|
+
if (p >= WARNING_LEVELS.critical) return { level: "critical", message: "에이전트 분할 또는 세션 교체 권장" };
|
|
32
|
+
if (p >= WARNING_LEVELS.warn) return { level: "warn", message: "압축 권장" };
|
|
33
|
+
if (p >= WARNING_LEVELS.info) return { level: "info", message: "컨텍스트 절반 이상 사용" };
|
|
34
|
+
return { level: "ok", message: "" };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a per-agent token budget manager.
|
|
39
|
+
* @returns {object} Budget API
|
|
40
|
+
*/
|
|
41
|
+
export function createMeshBudget() {
|
|
42
|
+
// Map<agentId, { allocated: number, consumed: number }>
|
|
43
|
+
const budgets = new Map();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Allocates a token budget to an agent.
|
|
47
|
+
* @param {string} agentId
|
|
48
|
+
* @param {number} tokenLimit
|
|
49
|
+
*/
|
|
50
|
+
function allocate(agentId, tokenLimit) {
|
|
51
|
+
if (!agentId || typeof agentId !== "string") {
|
|
52
|
+
throw new TypeError("agentId must be a non-empty string");
|
|
53
|
+
}
|
|
54
|
+
const limit = Math.max(0, Math.round(Number(tokenLimit) || 0));
|
|
55
|
+
const existing = budgets.get(agentId);
|
|
56
|
+
budgets.set(agentId, {
|
|
57
|
+
allocated: limit,
|
|
58
|
+
consumed: existing?.consumed ?? 0,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Records token consumption for an agent.
|
|
64
|
+
* @param {string} agentId
|
|
65
|
+
* @param {number} tokens
|
|
66
|
+
* @returns {{ remaining: number, percent: number, level: string }}
|
|
67
|
+
*/
|
|
68
|
+
function consume(agentId, tokens) {
|
|
69
|
+
const budget = budgets.get(agentId);
|
|
70
|
+
if (!budget) {
|
|
71
|
+
throw new Error(`No budget allocated for agent: ${agentId}`);
|
|
72
|
+
}
|
|
73
|
+
const amount = Math.max(0, Math.round(Number(tokens) || 0));
|
|
74
|
+
const updated = {
|
|
75
|
+
allocated: budget.allocated,
|
|
76
|
+
consumed: budget.consumed + amount,
|
|
77
|
+
};
|
|
78
|
+
budgets.set(agentId, updated);
|
|
79
|
+
|
|
80
|
+
const remaining = Math.max(0, updated.allocated - updated.consumed);
|
|
81
|
+
const percent = updated.allocated > 0
|
|
82
|
+
? clampPercent((updated.consumed / updated.allocated) * 100)
|
|
83
|
+
: 100;
|
|
84
|
+
const { level } = classifyLevel(percent);
|
|
85
|
+
return { remaining, percent, level };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Returns the budget status for an agent.
|
|
90
|
+
* @param {string} agentId
|
|
91
|
+
* @returns {{ allocated: number, consumed: number, remaining: number, level: string }}
|
|
92
|
+
*/
|
|
93
|
+
function getStatus(agentId) {
|
|
94
|
+
const budget = budgets.get(agentId);
|
|
95
|
+
if (!budget) {
|
|
96
|
+
return { allocated: 0, consumed: 0, remaining: 0, level: "ok" };
|
|
97
|
+
}
|
|
98
|
+
const remaining = Math.max(0, budget.allocated - budget.consumed);
|
|
99
|
+
const percent = budget.allocated > 0
|
|
100
|
+
? clampPercent((budget.consumed / budget.allocated) * 100)
|
|
101
|
+
: 0;
|
|
102
|
+
const { level } = classifyLevel(percent);
|
|
103
|
+
return { allocated: budget.allocated, consumed: budget.consumed, remaining, level };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resets consumed tokens for all agents (keeps allocations).
|
|
108
|
+
*/
|
|
109
|
+
function resetAll() {
|
|
110
|
+
for (const [agentId, budget] of budgets) {
|
|
111
|
+
budgets.set(agentId, { allocated: budget.allocated, consumed: 0 });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Returns a snapshot of all current allocations.
|
|
117
|
+
* @returns {Map<string, { allocated: number, consumed: number }>}
|
|
118
|
+
*/
|
|
119
|
+
function listAllocations() {
|
|
120
|
+
const snap = new Map();
|
|
121
|
+
for (const [id, b] of budgets) {
|
|
122
|
+
snap.set(id, Object.freeze({ ...b }));
|
|
123
|
+
}
|
|
124
|
+
return snap;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { allocate, consume, getStatus, resetAll, listAllocations };
|
|
128
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const DEFAULT_INTERVAL_MS = 30_000;
|
|
2
|
+
const DEFAULT_THRESHOLD_MS = 60_000;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a heartbeat monitor that tracks agent liveness.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} registry - A mesh-registry instance
|
|
8
|
+
* @param {object} [opts]
|
|
9
|
+
* @param {number} [opts.intervalMs=30000] - Scan interval
|
|
10
|
+
* @param {number} [opts.thresholdMs=60000] - Stale threshold
|
|
11
|
+
* @param {function} [opts.onStale] - Called with agentId when stale detected
|
|
12
|
+
* @returns {object} HeartbeatMonitor API
|
|
13
|
+
*/
|
|
14
|
+
export function createHeartbeatMonitor(registry, opts = {}) {
|
|
15
|
+
const {
|
|
16
|
+
intervalMs = DEFAULT_INTERVAL_MS,
|
|
17
|
+
thresholdMs = DEFAULT_THRESHOLD_MS,
|
|
18
|
+
onStale,
|
|
19
|
+
} = opts;
|
|
20
|
+
|
|
21
|
+
/** @type {Map<string, number>} agentId → last heartbeat timestamp */
|
|
22
|
+
const heartbeats = new Map();
|
|
23
|
+
let timer = null;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Records a heartbeat for an agent.
|
|
27
|
+
* @param {string} agentId
|
|
28
|
+
*/
|
|
29
|
+
function recordHeartbeat(agentId) {
|
|
30
|
+
if (!agentId || typeof agentId !== "string") {
|
|
31
|
+
throw new TypeError("agentId must be a non-empty string");
|
|
32
|
+
}
|
|
33
|
+
heartbeats.set(agentId, Date.now());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns agent IDs whose last heartbeat exceeds the threshold.
|
|
38
|
+
* Only considers agents currently registered in the registry.
|
|
39
|
+
*
|
|
40
|
+
* @param {number} [customThresholdMs] - Override default threshold
|
|
41
|
+
* @returns {string[]}
|
|
42
|
+
*/
|
|
43
|
+
function getStaleAgents(customThresholdMs) {
|
|
44
|
+
const threshold = customThresholdMs ?? thresholdMs;
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
const registered = registry.listAll();
|
|
47
|
+
const stale = [];
|
|
48
|
+
|
|
49
|
+
for (const agent of registered) {
|
|
50
|
+
const lastBeat = heartbeats.get(agent.agentId);
|
|
51
|
+
if (lastBeat === undefined || (now - lastBeat) >= threshold) {
|
|
52
|
+
stale.push(agent.agentId);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return stale;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Runs a single scan: finds stale agents and invokes onStale callback.
|
|
60
|
+
*/
|
|
61
|
+
function scan() {
|
|
62
|
+
const stale = getStaleAgents();
|
|
63
|
+
if (typeof onStale === "function") {
|
|
64
|
+
for (const agentId of stale) {
|
|
65
|
+
onStale(agentId);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Starts periodic heartbeat scanning.
|
|
72
|
+
* @param {number} [customIntervalMs]
|
|
73
|
+
*/
|
|
74
|
+
function start(customIntervalMs) {
|
|
75
|
+
stop();
|
|
76
|
+
const interval = customIntervalMs ?? intervalMs;
|
|
77
|
+
timer = setInterval(scan, interval);
|
|
78
|
+
timer.unref?.();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Stops periodic scanning.
|
|
83
|
+
*/
|
|
84
|
+
function stop() {
|
|
85
|
+
if (timer !== null) {
|
|
86
|
+
clearInterval(timer);
|
|
87
|
+
timer = null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Removes heartbeat record for an agent.
|
|
93
|
+
* @param {string} agentId
|
|
94
|
+
*/
|
|
95
|
+
function remove(agentId) {
|
|
96
|
+
heartbeats.delete(agentId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { recordHeartbeat, getStaleAgents, start, stop, scan, remove };
|
|
100
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const MSG_TYPES = Object.freeze({
|
|
4
|
+
REQUEST: "request",
|
|
5
|
+
RESPONSE: "response",
|
|
6
|
+
EVENT: "event",
|
|
7
|
+
HEARTBEAT: "heartbeat",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const VALID_TYPES = new Set(Object.values(MSG_TYPES));
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates an immutable mesh message.
|
|
14
|
+
* @param {string} type - One of MSG_TYPES values
|
|
15
|
+
* @param {string} from - Sender agent ID
|
|
16
|
+
* @param {string} to - Recipient agent ID (or "*" for broadcast)
|
|
17
|
+
* @param {unknown} payload - Message payload
|
|
18
|
+
* @returns {Readonly<object>}
|
|
19
|
+
*/
|
|
20
|
+
export function createMessage(type, from, to, payload = null) {
|
|
21
|
+
if (!VALID_TYPES.has(type)) {
|
|
22
|
+
throw new TypeError(`Invalid message type: ${type}`);
|
|
23
|
+
}
|
|
24
|
+
if (!from || typeof from !== "string") {
|
|
25
|
+
throw new TypeError("from must be a non-empty string");
|
|
26
|
+
}
|
|
27
|
+
if (!to || typeof to !== "string") {
|
|
28
|
+
throw new TypeError("to must be a non-empty string");
|
|
29
|
+
}
|
|
30
|
+
return Object.freeze({
|
|
31
|
+
type,
|
|
32
|
+
from,
|
|
33
|
+
to,
|
|
34
|
+
payload,
|
|
35
|
+
timestamp: new Date().toISOString(),
|
|
36
|
+
correlationId: randomUUID(),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Serializes a message to a JSON string.
|
|
42
|
+
* @param {object} message
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
export function serialize(message) {
|
|
46
|
+
return JSON.stringify(message);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Deserializes a JSON string to a message object.
|
|
51
|
+
* @param {string} raw
|
|
52
|
+
* @returns {object}
|
|
53
|
+
*/
|
|
54
|
+
export function deserialize(raw) {
|
|
55
|
+
if (typeof raw !== "string") {
|
|
56
|
+
throw new TypeError("raw must be a string");
|
|
57
|
+
}
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(raw);
|
|
61
|
+
} catch {
|
|
62
|
+
throw new SyntaxError(`Failed to parse message: ${raw}`);
|
|
63
|
+
}
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Validates a message object.
|
|
69
|
+
* @param {unknown} message
|
|
70
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
71
|
+
*/
|
|
72
|
+
export function validate(message) {
|
|
73
|
+
const errors = [];
|
|
74
|
+
|
|
75
|
+
if (!message || typeof message !== "object") {
|
|
76
|
+
return { valid: false, errors: ["message must be an object"] };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!VALID_TYPES.has(message.type)) {
|
|
80
|
+
errors.push(`Invalid type: ${message.type}`);
|
|
81
|
+
}
|
|
82
|
+
if (!message.from || typeof message.from !== "string") {
|
|
83
|
+
errors.push("from must be a non-empty string");
|
|
84
|
+
}
|
|
85
|
+
if (!message.to || typeof message.to !== "string") {
|
|
86
|
+
errors.push("to must be a non-empty string");
|
|
87
|
+
}
|
|
88
|
+
if (!message.timestamp || typeof message.timestamp !== "string") {
|
|
89
|
+
errors.push("timestamp must be a non-empty string");
|
|
90
|
+
}
|
|
91
|
+
if (!message.correlationId || typeof message.correlationId !== "string") {
|
|
92
|
+
errors.push("correlationId must be a non-empty string");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { valid: errors.length === 0, errors };
|
|
96
|
+
}
|