@triflux/core 10.1.0 → 10.25.1

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.
Files changed (143) hide show
  1. package/hooks/agent-route-guard.mjs +14 -10
  2. package/hooks/cross-review-tracker.mjs +21 -8
  3. package/hooks/error-context.mjs +20 -8
  4. package/hooks/hook-adaptive-collector.mjs +18 -16
  5. package/hooks/hook-manager.mjs +93 -32
  6. package/hooks/hook-orchestrator.mjs +267 -35
  7. package/hooks/hook-registry.json +343 -256
  8. package/hooks/hooks.json +49 -1
  9. package/hooks/keyword-rules.json +183 -23
  10. package/hooks/lib/resolve-root.mjs +1 -1
  11. package/hooks/mcp-config-watcher.mjs +6 -2
  12. package/hooks/permission-safe-allow.mjs +233 -0
  13. package/hooks/pipeline-stop.mjs +369 -22
  14. package/hooks/post-tool-tips.mjs +306 -0
  15. package/hooks/pre-compact-snapshot.mjs +181 -0
  16. package/hooks/safety-guard.mjs +254 -33
  17. package/hooks/session-end-cleanup.mjs +345 -0
  18. package/hooks/session-start-fast.mjs +214 -0
  19. package/hooks/subagent-tracker.mjs +267 -0
  20. package/hooks/subagent-verifier.mjs +9 -4
  21. package/hub/account-broker.mjs +1036 -66
  22. package/hub/adaptive-diagnostic.mjs +75 -48
  23. package/hub/adaptive-inject.mjs +96 -58
  24. package/hub/adaptive-memory.mjs +156 -42
  25. package/hub/adaptive.mjs +60 -31
  26. package/hub/assign-callbacks.mjs +163 -126
  27. package/hub/bridge.mjs +831 -290
  28. package/hub/cli-adapter-base.mjs +321 -97
  29. package/hub/codex-adapter.mjs +179 -93
  30. package/hub/codex-compat.mjs +4 -4
  31. package/hub/codex-preflight.mjs +113 -37
  32. package/hub/delegator/contracts.mjs +19 -23
  33. package/hub/delegator/index.mjs +14 -14
  34. package/hub/delegator/service.mjs +88 -64
  35. package/hub/delegator/tool-definitions.mjs +35 -35
  36. package/hub/dynamic-routing-engine.mjs +511 -0
  37. package/hub/fullcycle.mjs +34 -18
  38. package/hub/gemini-adapter.mjs +77 -95
  39. package/hub/hitl.mjs +202 -143
  40. package/hub/intent.mjs +167 -38
  41. package/hub/lib/bash-path.mjs +73 -0
  42. package/hub/lib/cache-guard.mjs +43 -17
  43. package/hub/lib/env-detect.mjs +145 -0
  44. package/hub/lib/hosts-compat.mjs +319 -0
  45. package/hub/lib/mcp-response-cache.mjs +239 -0
  46. package/hub/lib/memory-store.mjs +285 -111
  47. package/hub/lib/path-utils.mjs +165 -0
  48. package/hub/lib/phase-manager.mjs +493 -0
  49. package/hub/lib/process-utils.mjs +797 -104
  50. package/hub/lib/prompt-tmp.mjs +33 -0
  51. package/hub/lib/risk-tier.mjs +53 -0
  52. package/hub/lib/spawn-trace.mjs +561 -0
  53. package/hub/lib/ssh-command.mjs +44 -24
  54. package/hub/lib/ssh-retry.mjs +5 -1
  55. package/hub/lib/staged-file-detect.mjs +43 -0
  56. package/hub/lib/state-snapshot.mjs +293 -0
  57. package/hub/lib/tfx-route-args.mjs +309 -0
  58. package/hub/lib/timeline-adapter.mjs +190 -0
  59. package/hub/lib/trace-recorder.mjs +153 -0
  60. package/hub/lib/uuidv7.mjs +4 -3
  61. package/hub/memory-doctor.mjs +266 -106
  62. package/hub/middleware/quota-middleware.mjs +70 -0
  63. package/hub/middleware/request-logger.mjs +61 -34
  64. package/hub/paths.mjs +9 -9
  65. package/hub/pipeline/gates/confidence.mjs +34 -15
  66. package/hub/pipeline/gates/consensus.mjs +27 -15
  67. package/hub/pipeline/gates/index.mjs +7 -3
  68. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  69. package/hub/pipeline/index.mjs +77 -42
  70. package/hub/pipeline/state.mjs +10 -10
  71. package/hub/pipeline/transitions.mjs +40 -23
  72. package/hub/platform.mjs +65 -48
  73. package/hub/quality/deslop.mjs +70 -49
  74. package/hub/reflexion.mjs +87 -13
  75. package/hub/research.mjs +33 -25
  76. package/hub/router.mjs +941 -791
  77. package/hub/routing/complexity.mjs +132 -29
  78. package/hub/routing/index.mjs +17 -12
  79. package/hub/routing/q-learning.mjs +76 -28
  80. package/hub/routing-snapshot.mjs +261 -0
  81. package/hub/session-fingerprint.mjs +127 -61
  82. package/hub/state.mjs +86 -44
  83. package/hub/team/retry-state-machine.mjs +267 -0
  84. package/hub/token-mode.mjs +72 -71
  85. package/hub/workers/worker-utils.mjs +22 -16
  86. package/hud/colors.mjs +27 -9
  87. package/hud/constants.mjs +184 -27
  88. package/hud/context-monitor.mjs +133 -44
  89. package/hud/hud-qos-status.mjs +153 -50
  90. package/hud/mission-board.mjs +56 -0
  91. package/hud/providers/claude.mjs +398 -114
  92. package/hud/providers/codex.mjs +96 -21
  93. package/hud/providers/gemini.mjs +184 -51
  94. package/hud/renderers.mjs +413 -123
  95. package/hud/terminal.mjs +101 -31
  96. package/hud/utils.mjs +81 -39
  97. package/mesh/index.mjs +69 -0
  98. package/mesh/mesh-budget.mjs +137 -0
  99. package/mesh/mesh-heartbeat.mjs +100 -0
  100. package/mesh/mesh-protocol.mjs +96 -0
  101. package/mesh/mesh-queue.mjs +163 -0
  102. package/mesh/mesh-registry.mjs +78 -0
  103. package/mesh/mesh-router.mjs +77 -0
  104. package/package.json +5 -1
  105. package/scripts/lib/agent-json.mjs +27 -0
  106. package/scripts/lib/async.mjs +174 -0
  107. package/scripts/lib/claudemd-scanner.mjs +6 -1
  108. package/scripts/lib/cli-agy.mjs +62 -0
  109. package/scripts/lib/cli-claude.mjs +78 -0
  110. package/scripts/lib/cli-codex.mjs +199 -0
  111. package/scripts/lib/cli-gemini.mjs +67 -0
  112. package/scripts/lib/codex-recovery.sh +50 -0
  113. package/scripts/lib/context.mjs +3 -3
  114. package/scripts/lib/cross-review-utils.mjs +13 -5
  115. package/scripts/lib/doctor-env-checks.mjs +121 -0
  116. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  117. package/scripts/lib/env-probe.mjs +58 -30
  118. package/scripts/lib/env.mjs +14 -0
  119. package/scripts/lib/gemini-profiles.mjs +44 -10
  120. package/scripts/lib/handoff.mjs +33 -17
  121. package/scripts/lib/hook-utils.mjs +8 -6
  122. package/scripts/lib/hub.mjs +48 -0
  123. package/scripts/lib/keyword-rules.mjs +48 -19
  124. package/scripts/lib/logger.mjs +24 -24
  125. package/scripts/lib/mcp-filter.mjs +391 -247
  126. package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
  127. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  128. package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
  129. package/scripts/lib/mcp-guard-engine.mjs +1281 -115
  130. package/scripts/lib/mcp-health.mjs +773 -0
  131. package/scripts/lib/mcp-manifest.mjs +24 -14
  132. package/scripts/lib/mcp-server-catalog.mjs +355 -118
  133. package/scripts/lib/pid.mjs +63 -0
  134. package/scripts/lib/process-utils.mjs +26 -0
  135. package/scripts/lib/psmux-info.mjs +70 -10
  136. package/scripts/lib/quota.mjs +47 -0
  137. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  138. package/scripts/lib/skill-state.mjs +220 -0
  139. package/scripts/lib/skill-template.mjs +32 -7
  140. package/scripts/lib/team.mjs +75 -0
  141. package/scripts/lib/timeout.mjs +66 -0
  142. package/scripts/lib/tmp.mjs +26 -0
  143. package/scripts/lib/toml.mjs +113 -0
@@ -2,83 +2,84 @@
2
2
  // GAP 분석 P2 #7: 심볼 통신 + 약어로 30-50% 토큰 절감
3
3
 
4
4
  /**
5
+ * @experimental 런타임 미연결 — 향후 통합 예정
5
6
  * @experimental
6
7
  * @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>}
7
8
  */
8
9
  export const COMPACT_RULES = [
9
10
  // ── 심볼 치환 (긴 매칭 우선 정렬) ──
10
- { from: ['greater than or equal'], to: '≥', type: 'symbol' },
11
- { from: ['less than or equal'], to: '≤', type: 'symbol' },
12
- { from: ['results in', '결과적으로'], to: '→', type: 'symbol' },
13
- { from: ['therefore', '따라서'], to: '∴', type: 'symbol' },
14
- { from: ['because', '왜냐하면'], to: '∵', type: 'symbol' },
15
- { from: ['approximately', '대략'], to: '≈', type: 'symbol' },
16
- { from: ['not equal', '같지 않'], to: '≠', type: 'symbol' },
17
- { from: ['in progress', '진행 중'], to: '⏳', type: 'symbol' },
18
- { from: ['completed', '완료'], to: '✓', type: 'symbol' },
19
- { from: ['success', '성공'], to: '✓', type: 'symbol' },
20
- { from: ['failure', '실패'], to: '✗', type: 'symbol' },
21
- { from: ['warning', '경고'], to: '⚠', type: 'symbol' },
22
- { from: ['error', '에러'], to: '✗', type: 'symbol' },
23
- { from: ['pending', '대기'], to: '⏸', type: 'symbol' },
11
+ { from: ["greater than or equal"], to: "≥", type: "symbol" },
12
+ { from: ["less than or equal"], to: "≤", type: "symbol" },
13
+ { from: ["results in", "결과적으로"], to: "→", type: "symbol" },
14
+ { from: ["therefore", "따라서"], to: "∴", type: "symbol" },
15
+ { from: ["because", "왜냐하면"], to: "∵", type: "symbol" },
16
+ { from: ["approximately", "대략"], to: "≈", type: "symbol" },
17
+ { from: ["not equal", "같지 않"], to: "≠", type: "symbol" },
18
+ { from: ["in progress", "진행 중"], to: "⏳", type: "symbol" },
19
+ { from: ["completed", "완료"], to: "✓", type: "symbol" },
20
+ { from: ["success", "성공"], to: "✓", type: "symbol" },
21
+ { from: ["failure", "실패"], to: "✗", type: "symbol" },
22
+ { from: ["warning", "경고"], to: "⚠", type: "symbol" },
23
+ { from: ["error", "에러"], to: "✗", type: "symbol" },
24
+ { from: ["pending", "대기"], to: "⏸", type: "symbol" },
24
25
 
25
26
  // ── 약어 (긴 매칭 우선 정렬) ──
26
- { from: ['configuration', '설정'], to: 'cfg', type: 'abbrev' },
27
- { from: ['implementation', '구현'], to: 'impl', type: 'abbrev' },
28
- { from: ['architecture', '아키텍처'], to: 'arch', type: 'abbrev' },
29
- { from: ['dependency', '의존성'], to: 'dep', type: 'abbrev' },
30
- { from: ['function', '함수'], to: 'fn', type: 'abbrev' },
31
- { from: ['parameter', '파라미터'], to: 'param', type: 'abbrev' },
32
- { from: ['repository', '저장소'], to: 'repo', type: 'abbrev' },
33
- { from: ['environment', '환경'], to: 'env', type: 'abbrev' },
34
- { from: ['variable', '변수'], to: 'var', type: 'abbrev' },
35
- { from: ['directory', '디렉토리'], to: 'dir', type: 'abbrev' },
27
+ { from: ["configuration", "설정"], to: "cfg", type: "abbrev" },
28
+ { from: ["implementation", "구현"], to: "impl", type: "abbrev" },
29
+ { from: ["architecture", "아키텍처"], to: "arch", type: "abbrev" },
30
+ { from: ["dependency", "의존성"], to: "dep", type: "abbrev" },
31
+ { from: ["function", "함수"], to: "fn", type: "abbrev" },
32
+ { from: ["parameter", "파라미터"], to: "param", type: "abbrev" },
33
+ { from: ["repository", "저장소"], to: "repo", type: "abbrev" },
34
+ { from: ["environment", "환경"], to: "env", type: "abbrev" },
35
+ { from: ["variable", "변수"], to: "var", type: "abbrev" },
36
+ { from: ["directory", "디렉토리"], to: "dir", type: "abbrev" },
36
37
 
37
38
  // ── 한국어 동사/명령형 약어 ──
38
- { from: ['구현해'], to: 'impl', type: 'abbrev' },
39
- { from: ['확인해'], to: 'check', type: 'abbrev' },
40
- { from: ['수정해'], to: 'fix', type: 'abbrev' },
41
- { from: ['테스트'], to: 'test', type: 'abbrev' },
42
- { from: ['리뷰'], to: 'review', type: 'abbrev' },
43
- { from: ['분석'], to: 'analyze', type: 'abbrev' },
44
- { from: ['설계'], to: 'design', type: 'abbrev' },
45
- { from: ['문서화'], to: 'docs', type: 'abbrev' },
39
+ { from: ["구현해"], to: "impl", type: "abbrev" },
40
+ { from: ["확인해"], to: "check", type: "abbrev" },
41
+ { from: ["수정해"], to: "fix", type: "abbrev" },
42
+ { from: ["테스트"], to: "test", type: "abbrev" },
43
+ { from: ["리뷰"], to: "review", type: "abbrev" },
44
+ { from: ["분석"], to: "analyze", type: "abbrev" },
45
+ { from: ["설계"], to: "design", type: "abbrev" },
46
+ { from: ["문서화"], to: "docs", type: "abbrev" },
46
47
  ];
47
48
 
48
49
  /** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
49
50
  export const REVIEW_RULES = [
50
- { from: ['looks good to me', 'lgtm'], to: '✓lgtm', type: 'abbrev' },
51
- { from: ['needs changes', '수정 필요'], to: '✗chg', type: 'abbrev' },
52
- { from: ['nitpick', '사소한'], to: 'nit', type: 'abbrev' },
53
- { from: ['blocking', '블로킹'], to: 'blk', type: 'abbrev' },
54
- { from: ['suggestion', '제안'], to: 'sug', type: 'abbrev' },
55
- { from: ['question', '질문'], to: 'q', type: 'abbrev' },
56
- { from: ['approved', '승인'], to: '✓apv', type: 'abbrev' },
57
- { from: ['request changes', '변경 요청'], to: '✗req', type: 'abbrev' },
51
+ { from: ["looks good to me", "lgtm"], to: "✓lgtm", type: "abbrev" },
52
+ { from: ["needs changes", "수정 필요"], to: "✗chg", type: "abbrev" },
53
+ { from: ["nitpick", "사소한"], to: "nit", type: "abbrev" },
54
+ { from: ["blocking", "블로킹"], to: "blk", type: "abbrev" },
55
+ { from: ["suggestion", "제안"], to: "sug", type: "abbrev" },
56
+ { from: ["question", "질문"], to: "q", type: "abbrev" },
57
+ { from: ["approved", "승인"], to: "✓apv", type: "abbrev" },
58
+ { from: ["request changes", "변경 요청"], to: "✗req", type: "abbrev" },
58
59
  ];
59
60
 
60
61
  /** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
61
62
  export const DESIGN_RULES = [
62
- { from: ['component', '컴포넌트'], to: 'cmp', type: 'abbrev' },
63
- { from: ['interface', '인터페이스'], to: 'iface', type: 'abbrev' },
64
- { from: ['abstraction', '추상화'], to: 'abs', type: 'abbrev' },
65
- { from: ['pattern', '패턴'], to: 'ptn', type: 'abbrev' },
66
- { from: ['dependency injection', '의존성 주입'], to: 'di', type: 'abbrev' },
67
- { from: ['single responsibility', '단일 책임'], to: 'srp', type: 'abbrev' },
68
- { from: ['open closed', '개방 폐쇄'], to: 'ocp', type: 'abbrev' },
69
- { from: ['inheritance', '상속'], to: 'inh', type: 'abbrev' },
63
+ { from: ["component", "컴포넌트"], to: "cmp", type: "abbrev" },
64
+ { from: ["interface", "인터페이스"], to: "iface", type: "abbrev" },
65
+ { from: ["abstraction", "추상화"], to: "abs", type: "abbrev" },
66
+ { from: ["pattern", "패턴"], to: "ptn", type: "abbrev" },
67
+ { from: ["dependency injection", "의존성 주입"], to: "di", type: "abbrev" },
68
+ { from: ["single responsibility", "단일 책임"], to: "srp", type: "abbrev" },
69
+ { from: ["open closed", "개방 폐쇄"], to: "ocp", type: "abbrev" },
70
+ { from: ["inheritance", "상속"], to: "inh", type: "abbrev" },
70
71
  ];
71
72
 
72
73
  /** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
73
74
  export const DOCS_RULES = [
74
- { from: ['description', '설명'], to: 'desc', type: 'abbrev' },
75
- { from: ['example', '예시'], to: 'ex', type: 'abbrev' },
76
- { from: ['reference', '참조'], to: 'ref', type: 'abbrev' },
77
- { from: ['introduction', '소개'], to: 'intro', type: 'abbrev' },
78
- { from: ['deprecated', '사용 중단'], to: 'dep', type: 'abbrev' },
79
- { from: ['optional', '선택적'], to: 'opt', type: 'abbrev' },
80
- { from: ['required', '필수'], to: 'req', type: 'abbrev' },
81
- { from: ['returns', '반환'], to: 'ret', type: 'abbrev' },
75
+ { from: ["description", "설명"], to: "desc", type: "abbrev" },
76
+ { from: ["example", "예시"], to: "ex", type: "abbrev" },
77
+ { from: ["reference", "참조"], to: "ref", type: "abbrev" },
78
+ { from: ["introduction", "소개"], to: "intro", type: "abbrev" },
79
+ { from: ["deprecated", "사용 중단"], to: "dep", type: "abbrev" },
80
+ { from: ["optional", "선택적"], to: "opt", type: "abbrev" },
81
+ { from: ["required", "필수"], to: "req", type: "abbrev" },
82
+ { from: ["returns", "반환"], to: "ret", type: "abbrev" },
82
83
  ];
83
84
 
84
85
  // ── 프로필 맵 ──
@@ -86,9 +87,9 @@ export const DOCS_RULES = [
86
87
  /** @type {Record<string, Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>>} */
87
88
  const PROFILE_MAP = {
88
89
  default: COMPACT_RULES,
89
- review: [...COMPACT_RULES, ...REVIEW_RULES],
90
- design: [...COMPACT_RULES, ...DESIGN_RULES],
91
- docs: [...COMPACT_RULES, ...DOCS_RULES],
90
+ review: [...COMPACT_RULES, ...REVIEW_RULES],
91
+ design: [...COMPACT_RULES, ...DESIGN_RULES],
92
+ docs: [...COMPACT_RULES, ...DOCS_RULES],
92
93
  };
93
94
 
94
95
  // ── 내부: 정렬된 치환 쌍 빌드 헬퍼 ──
@@ -134,7 +135,7 @@ const CODE_BLOCK_RE = /```[\s\S]*?```/g;
134
135
  */
135
136
  function withCodeProtection(text, transform) {
136
137
  const blocks = [];
137
- const placeholder = '\x00CB';
138
+ const placeholder = "\x00CB";
138
139
  let idx = 0;
139
140
  const masked = text.replace(CODE_BLOCK_RE, (match) => {
140
141
  blocks.push(match);
@@ -145,7 +146,7 @@ function withCodeProtection(text, transform) {
145
146
 
146
147
  // 플레이스홀더 복원
147
148
  return transformed.replace(
148
- new RegExp(`${placeholder}(\\d+)${placeholder}`, 'g'),
149
+ new RegExp(`${placeholder}(\\d+)${placeholder}`, "g"),
149
150
  (_, i) => blocks[Number(i)],
150
151
  );
151
152
  }
@@ -169,14 +170,14 @@ export function isCompactMode() {
169
170
  * @returns {string}
170
171
  */
171
172
  export function compactify(text) {
172
- if (!text || typeof text !== 'string') return text ?? '';
173
+ if (!text || typeof text !== "string") return text ?? "";
173
174
  _compactMode = true;
174
175
 
175
176
  return withCodeProtection(text, (segment) => {
176
177
  let result = segment;
177
178
  for (const { pattern, to } of _compactPairs) {
178
- const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
179
- const re = new RegExp(escaped, 'gi');
179
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
180
+ const re = new RegExp(escaped, "gi");
180
181
  result = result.replace(re, to);
181
182
  }
182
183
  return result;
@@ -189,8 +190,8 @@ export function compactify(text) {
189
190
  * @param {'default'|'review'|'design'|'docs'} [profile='default'] — 도메인 프로필
190
191
  * @returns {string}
191
192
  */
192
- export function applyCompactRules(text, profile = 'default') {
193
- if (!text || typeof text !== 'string') return text ?? '';
193
+ export function applyCompactRules(text, profile = "default") {
194
+ if (!text || typeof text !== "string") return text ?? "";
194
195
 
195
196
  const rules = PROFILE_MAP[profile] ?? COMPACT_RULES;
196
197
  const pairs = buildCompactPairs(rules);
@@ -198,8 +199,8 @@ export function applyCompactRules(text, profile = 'default') {
198
199
  return withCodeProtection(text, (segment) => {
199
200
  let result = segment;
200
201
  for (const { pattern, to } of pairs) {
201
- const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
202
- const re = new RegExp(escaped, 'gi');
202
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
203
+ const re = new RegExp(escaped, "gi");
203
204
  result = result.replace(re, to);
204
205
  }
205
206
  return result;
@@ -212,14 +213,14 @@ export function applyCompactRules(text, profile = 'default') {
212
213
  * @returns {string}
213
214
  */
214
215
  export function expand(text) {
215
- if (!text || typeof text !== 'string') return text ?? '';
216
+ if (!text || typeof text !== "string") return text ?? "";
216
217
  _compactMode = false;
217
218
 
218
219
  return withCodeProtection(text, (segment) => {
219
220
  let result = segment;
220
221
  for (const { symbol, restore } of _expandPairs) {
221
- const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
222
- const re = new RegExp(escaped, 'g');
222
+ const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
223
+ const re = new RegExp(escaped, "g");
223
224
  result = result.replace(re, restore);
224
225
  }
225
226
  return result;
@@ -6,9 +6,7 @@ export const DEFAULT_KILL_GRACE_MS = 1000;
6
6
 
7
7
  export function toStringList(value) {
8
8
  if (!Array.isArray(value)) return [];
9
- return value
10
- .map((item) => String(item ?? '').trim())
11
- .filter(Boolean);
9
+ return value.map((item) => String(item ?? "").trim()).filter(Boolean);
12
10
  }
13
11
 
14
12
  export function safeJsonParse(line) {
@@ -27,8 +25,7 @@ export function createWorkerError(message, details = {}) {
27
25
 
28
26
  function sleep(delayMs) {
29
27
  return new Promise((resolve) => {
30
- const timer = setTimeout(resolve, Math.max(0, delayMs));
31
- timer.unref?.();
28
+ setTimeout(resolve, Math.max(0, delayMs));
32
29
  });
33
30
  }
34
31
 
@@ -51,8 +48,9 @@ export async function withRetry(fn, opts = {}) {
51
48
  throw error;
52
49
  }
53
50
 
54
- const delay = Math.min(baseDelayMs * 2 ** (attempt - 1), maxDelayMs)
55
- * (0.5 + Math.random() * 0.5);
51
+ const delay =
52
+ Math.min(baseDelayMs * 2 ** (attempt - 1), maxDelayMs) *
53
+ (0.5 + Math.random() * 0.5);
56
54
  await sleep(delay);
57
55
  }
58
56
  }
@@ -63,7 +61,7 @@ export async function withRetry(fn, opts = {}) {
63
61
  export function appendTextFragments(value, parts) {
64
62
  if (value == null) return;
65
63
 
66
- if (typeof value === 'string') {
64
+ if (typeof value === "string") {
67
65
  const trimmed = value.trim();
68
66
  if (trimmed) parts.push(trimmed);
69
67
  return;
@@ -74,11 +72,13 @@ export function appendTextFragments(value, parts) {
74
72
  return;
75
73
  }
76
74
 
77
- if (typeof value !== 'object') return;
75
+ if (typeof value !== "object") return;
78
76
 
79
- if (typeof value.text === 'string') appendTextFragments(value.text, parts);
80
- if (typeof value.response === 'string') appendTextFragments(value.response, parts);
81
- if (typeof value.result === 'string') appendTextFragments(value.result, parts);
77
+ if (typeof value.text === "string") appendTextFragments(value.text, parts);
78
+ if (typeof value.response === "string")
79
+ appendTextFragments(value.response, parts);
80
+ if (typeof value.result === "string")
81
+ appendTextFragments(value.result, parts);
82
82
  if (value.content != null) appendTextFragments(value.content, parts);
83
83
  if (value.message != null) appendTextFragments(value.message, parts);
84
84
  }
@@ -86,18 +86,24 @@ export function appendTextFragments(value, parts) {
86
86
  export function extractText(value) {
87
87
  const parts = [];
88
88
  appendTextFragments(value, parts);
89
- return parts.join('\n').trim();
89
+ return parts.join("\n").trim();
90
90
  }
91
91
 
92
92
  export function terminateChild(child, killGraceMs) {
93
93
  if (!child || child.exitCode !== null || child.killed) return;
94
94
 
95
- try { child.stdin.end(); } catch {}
96
- try { child.kill(); } catch {}
95
+ try {
96
+ child.stdin.end();
97
+ } catch {}
98
+ try {
99
+ child.kill();
100
+ } catch {}
97
101
 
98
102
  const timer = setTimeout(() => {
99
103
  if (child.exitCode === null) {
100
- try { child.kill('SIGKILL'); } catch {}
104
+ try {
105
+ child.kill("SIGKILL");
106
+ } catch {}
101
107
  }
102
108
  }, killGraceMs);
103
109
  timer.unref?.();
package/hud/colors.mjs CHANGED
@@ -12,15 +12,33 @@ export const CLAUDE_ORANGE = "\x1b[38;2;232;112;64m"; // #E87040 (Claude 공식
12
12
  export const CODEX_WHITE = "\x1b[97m"; // bright white (SGR 37은 Windows Terminal에서 연회색 매핑)
13
13
  export const GEMINI_BLUE = "\x1b[38;5;39m";
14
14
 
15
- export function green(t) { return `${GREEN}${t}${RESET}`; }
16
- export function yellow(t) { return `${YELLOW}${t}${RESET}`; }
17
- export function red(t) { return `${RED}${t}${RESET}`; }
18
- export function cyan(t) { return `${CYAN}${t}${RESET}`; }
19
- export function dim(t) { return `${DIM}${t}${RESET}`; }
20
- export function bold(t) { return `${BOLD}${t}${RESET}`; }
21
- export function claudeOrange(t) { return `${CLAUDE_ORANGE}${t}${RESET}`; }
22
- export function codexWhite(t) { return `${CODEX_WHITE}${t}${RESET}`; }
23
- export function geminiBlue(t) { return `${GEMINI_BLUE}${t}${RESET}`; }
15
+ export function green(t) {
16
+ return `${GREEN}${t}${RESET}`;
17
+ }
18
+ export function yellow(t) {
19
+ return `${YELLOW}${t}${RESET}`;
20
+ }
21
+ export function red(t) {
22
+ return `${RED}${t}${RESET}`;
23
+ }
24
+ export function cyan(t) {
25
+ return `${CYAN}${t}${RESET}`;
26
+ }
27
+ export function dim(t) {
28
+ return `${DIM}${t}${RESET}`;
29
+ }
30
+ export function bold(t) {
31
+ return `${BOLD}${t}${RESET}`;
32
+ }
33
+ export function claudeOrange(t) {
34
+ return `${CLAUDE_ORANGE}${t}${RESET}`;
35
+ }
36
+ export function codexWhite(t) {
37
+ return `${CODEX_WHITE}${t}${RESET}`;
38
+ }
39
+ export function geminiBlue(t) {
40
+ return `${GEMINI_BLUE}${t}${RESET}`;
41
+ }
24
42
 
25
43
  export function colorByPercent(value, text) {
26
44
  if (value >= 85) return red(text);
package/hud/constants.mjs CHANGED
@@ -6,26 +6,88 @@ import { join } from "node:path";
6
6
 
7
7
  export const VERSION = "2.0";
8
8
 
9
- export const QOS_PATH = join(homedir(), ".omc", "state", "cli_qos_profile.json");
10
- export const ACCOUNTS_CONFIG_PATH = join(homedir(), ".omc", "router", "accounts.json");
11
- export const ACCOUNTS_STATE_PATH = join(homedir(), ".omc", "state", "cli_accounts_state.json");
9
+ export const QOS_PATH = join(
10
+ homedir(),
11
+ ".claude",
12
+ "cache",
13
+ "tfx-hub",
14
+ "cli_qos_profile.json",
15
+ );
16
+ export const ACCOUNTS_CONFIG_PATH = join(
17
+ homedir(),
18
+ ".claude",
19
+ "cache",
20
+ "tfx-hub",
21
+ "accounts.json",
22
+ );
23
+ export const ACCOUNTS_STATE_PATH = join(
24
+ homedir(),
25
+ ".claude",
26
+ "cache",
27
+ "tfx-hub",
28
+ "cli_accounts_state.json",
29
+ );
12
30
 
13
31
  // tfx-multi 상태 (v2.2 HUD 통합)
14
- export const TEAM_STATE_PATH = join(homedir(), ".claude", "cache", "tfx-hub", "team-state.json");
15
- export const CONTEXT_MONITOR_CACHE_PATH = join(homedir(), ".claude", "cache", "tfx-hub", "context-monitor.json");
16
- export const CONTEXT_MONITOR_LEGACY_PATH = join(homedir(), ".omc", "state", "context-monitor.json");
17
- export const CONTEXT_MONITOR_LOG_DIR = join(homedir(), ".omc", "logs");
32
+ export const TEAM_STATE_PATH = join(
33
+ homedir(),
34
+ ".claude",
35
+ "cache",
36
+ "tfx-hub",
37
+ "team-state.json",
38
+ );
39
+ export const CONTEXT_MONITOR_CACHE_PATH = join(
40
+ homedir(),
41
+ ".claude",
42
+ "cache",
43
+ "tfx-hub",
44
+ "context-monitor.json",
45
+ );
46
+ export const CONTEXT_MONITOR_LEGACY_PATH = join(
47
+ homedir(),
48
+ ".omc",
49
+ "state",
50
+ "context-monitor.json",
51
+ );
52
+ export const CONTEXT_MONITOR_LOG_DIR = join(
53
+ homedir(),
54
+ ".claude",
55
+ "cache",
56
+ "tfx-hub",
57
+ "logs",
58
+ );
18
59
 
19
60
  // 원격 프로브 캐시 (tfx-remote-spawn)
20
- export const REMOTE_ENV_CACHE_DIR = join(homedir(), ".claude", "cache", "tfx-hub", "remote-env");
61
+ export const REMOTE_ENV_CACHE_DIR = join(
62
+ homedir(),
63
+ ".claude",
64
+ "cache",
65
+ "tfx-hub",
66
+ "remote-env",
67
+ );
21
68
  export const REMOTE_ENV_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24시간
22
69
 
23
70
  // Claude OAuth Usage API (api.anthropic.com/api/oauth/usage)
24
- export const CLAUDE_CREDENTIALS_PATH = join(homedir(), ".claude", ".credentials.json");
25
- export const CLAUDE_USAGE_CACHE_PATH = join(homedir(), ".claude", "cache", "claude-usage-cache.json");
26
- export const OMC_PLUGIN_USAGE_CACHE_PATH = join(homedir(), ".claude", "plugins", "oh-my-claudecode", ".usage-cache.json");
71
+ export const CLAUDE_CREDENTIALS_PATH = join(
72
+ homedir(),
73
+ ".claude",
74
+ ".credentials.json",
75
+ );
76
+ export const CLAUDE_USAGE_CACHE_PATH = join(
77
+ homedir(),
78
+ ".claude",
79
+ "cache",
80
+ "claude-usage-cache.json",
81
+ );
82
+ export const OMC_PLUGIN_USAGE_CACHE_PATH = join(
83
+ homedir(),
84
+ ".claude",
85
+ "cache",
86
+ "tfx-hub",
87
+ "plugin-usage-cache.json",
88
+ );
27
89
  export const CLAUDE_USAGE_STALE_MS_SOLO = 5 * 60 * 1000; // OMC 없을 때: 5분 캐시
28
- export const CLAUDE_USAGE_STALE_MS_WITH_OMC = 15 * 60 * 1000; // OMC 있을 때: 15분 (OMC가 30초마다 갱신)
90
+ export const CLAUDE_USAGE_STALE_MS_WITH_OMC = 15 * 60 * 1000; // OMC 있을 때: 15분
29
91
  export const CLAUDE_USAGE_429_BACKOFF_MS = 10 * 60 * 1000; // 429 에러 시 10분 backoff
30
92
  export const GEMINI_429_BASE_DELAY_MS = 2000;
31
93
  export const GEMINI_429_MAX_RETRIES = 3;
@@ -38,27 +100,115 @@ export const ONE_DAY_MS = 24 * 60 * 60 * 1000;
38
100
  export const DEFAULT_OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
39
101
 
40
102
  export const CODEX_AUTH_PATH = join(homedir(), ".codex", "auth.json");
41
- export const CODEX_QUOTA_CACHE_PATH = join(homedir(), ".claude", "cache", "codex-rate-limits-cache.json");
42
- export const CODEX_QUOTA_STALE_MS = 15 * 1000; // 15초
103
+ export const CODEX_QUOTA_CACHE_PATH = join(
104
+ homedir(),
105
+ ".claude",
106
+ "cache",
107
+ "codex-rate-limits-cache.json",
108
+ );
109
+ export const CODEX_QUOTA_STALE_MS = 30 * 1000; // 30초
43
110
  export const CODEX_MIN_BUCKETS = 2;
44
111
 
112
+ // Spawn lock (중복 refresh 방지)
113
+ export const CLAUDE_REFRESH_LOCK_PATH = join(
114
+ homedir(),
115
+ ".claude",
116
+ "cache",
117
+ ".claude-refresh-lock",
118
+ );
119
+ export const CODEX_REFRESH_LOCK_PATH = join(
120
+ homedir(),
121
+ ".claude",
122
+ "cache",
123
+ ".codex-refresh-lock",
124
+ );
125
+ export const GEMINI_QUOTA_REFRESH_LOCK_PATH = join(
126
+ homedir(),
127
+ ".claude",
128
+ "cache",
129
+ ".gemini-quota-refresh-lock",
130
+ );
131
+ export const GEMINI_SESSION_REFRESH_LOCK_PATH = join(
132
+ homedir(),
133
+ ".claude",
134
+ "cache",
135
+ ".gemini-session-refresh-lock",
136
+ );
137
+ export const SPAWN_LOCK_TTL_MS = 30 * 1000; // 30초 spawn dedup
138
+
45
139
  // Gemini 쿼터 API 관련
46
140
  export const GEMINI_OAUTH_PATH = join(homedir(), ".gemini", "oauth_creds.json");
47
- export const GEMINI_QUOTA_CACHE_PATH = join(homedir(), ".claude", "cache", "gemini-quota-cache.json");
48
- export const GEMINI_PROJECT_CACHE_PATH = join(homedir(), ".claude", "cache", "gemini-project-id.json");
49
- export const GEMINI_SESSION_CACHE_PATH = join(homedir(), ".claude", "cache", "gemini-session-cache.json");
50
- export const GEMINI_RPM_TRACKER_PATH = join(homedir(), ".claude", "cache", "gemini-rpm-tracker.json");
51
- export const SV_ACCUMULATOR_PATH = join(homedir(), ".claude", "cache", "sv-accumulator.json");
141
+ export const GEMINI_QUOTA_CACHE_PATH = join(
142
+ homedir(),
143
+ ".claude",
144
+ "cache",
145
+ "gemini-quota-cache.json",
146
+ );
147
+ export const GEMINI_PROJECT_CACHE_PATH = join(
148
+ homedir(),
149
+ ".claude",
150
+ "cache",
151
+ "gemini-project-id.json",
152
+ );
153
+ export const GEMINI_SESSION_CACHE_PATH = join(
154
+ homedir(),
155
+ ".claude",
156
+ "cache",
157
+ "gemini-session-cache.json",
158
+ );
159
+ export const TFX_PREFLIGHT_CACHE_PATH = join(
160
+ homedir(),
161
+ ".claude",
162
+ "cache",
163
+ "tfx-preflight.json",
164
+ );
165
+ export const GEMINI_RPM_TRACKER_PATH = join(
166
+ homedir(),
167
+ ".claude",
168
+ "cache",
169
+ "gemini-rpm-tracker.json",
170
+ );
171
+ export const SV_ACCUMULATOR_PATH = join(
172
+ homedir(),
173
+ ".claude",
174
+ "cache",
175
+ "sv-accumulator.json",
176
+ );
52
177
  // 이전 .omc/ 경로 fallback (기존 환경 호환)
53
- export const LEGACY_GEMINI_QUOTA_CACHE = join(homedir(), ".omc", "state", "gemini_quota_cache.json");
54
- export const LEGACY_GEMINI_PROJECT_CACHE = join(homedir(), ".omc", "state", "gemini_project_id.json");
55
- export const LEGACY_GEMINI_SESSION_CACHE = join(homedir(), ".omc", "state", "gemini_session_tokens_cache.json");
56
- export const LEGACY_GEMINI_RPM_TRACKER = join(homedir(), ".omc", "state", "gemini_rpm_tracker.json");
57
- export const LEGACY_SV_ACCUMULATOR = join(homedir(), ".omc", "state", "sv-accumulator.json");
178
+ export const LEGACY_GEMINI_QUOTA_CACHE = join(
179
+ homedir(),
180
+ ".omc",
181
+ "state",
182
+ "gemini_quota_cache.json",
183
+ );
184
+ export const LEGACY_GEMINI_PROJECT_CACHE = join(
185
+ homedir(),
186
+ ".omc",
187
+ "state",
188
+ "gemini_project_id.json",
189
+ );
190
+ export const LEGACY_GEMINI_SESSION_CACHE = join(
191
+ homedir(),
192
+ ".omc",
193
+ "state",
194
+ "gemini_session_tokens_cache.json",
195
+ );
196
+ export const LEGACY_GEMINI_RPM_TRACKER = join(
197
+ homedir(),
198
+ ".omc",
199
+ "state",
200
+ "gemini_rpm_tracker.json",
201
+ );
202
+ export const LEGACY_SV_ACCUMULATOR = join(
203
+ homedir(),
204
+ ".omc",
205
+ "state",
206
+ "sv-accumulator.json",
207
+ );
58
208
 
59
209
  export const GEMINI_RPM_WINDOW_MS = 60 * 1000; // 60초 슬라이딩 윈도우
60
210
  export const GEMINI_QUOTA_STALE_MS = 5 * 60 * 1000; // 5분
61
- export const GEMINI_SESSION_STALE_MS = 15 * 1000; // 15초
211
+ export const GEMINI_SESSION_STALE_MS = 30 * 1000; // 30초
62
212
  export const GEMINI_API_TIMEOUT_MS = 3000; // 3초
63
213
 
64
214
  export const ACCOUNT_LABEL_WIDTH = 10;
@@ -84,5 +234,12 @@ export const ROWS_BUDGET_MEDIUM = 28;
84
234
  export const ROWS_BUDGET_SMALL = 22;
85
235
 
86
236
  // Gemini Pro 풀 공유 그룹: 같은 remainingFraction을 공유하는 모델 ID들
87
- export const GEMINI_PRO_POOL = new Set(["gemini-2.5-pro", "gemini-3-pro-preview", "gemini-3.1-pro-preview"]);
88
- export const GEMINI_FLASH_POOL = new Set(["gemini-2.5-flash", "gemini-3-flash-preview"]);
237
+ export const GEMINI_PRO_POOL = new Set([
238
+ "gemini-2.5-pro",
239
+ "gemini-3-pro-preview",
240
+ "gemini-3.1-pro-preview",
241
+ ]);
242
+ export const GEMINI_FLASH_POOL = new Set([
243
+ "gemini-2.5-flash",
244
+ "gemini-3-flash-preview",
245
+ ]);