@triflux/core 10.1.0 → 10.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) 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 +191 -31
  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 +262 -34
  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 +1051 -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 +22 -21
  33. package/hub/delegator/index.mjs +14 -14
  34. package/hub/delegator/schema/delegator-tools.schema.json +1 -1
  35. package/hub/delegator/service.mjs +93 -64
  36. package/hub/delegator/tool-definitions.mjs +35 -35
  37. package/hub/dynamic-routing-engine.mjs +511 -0
  38. package/hub/fullcycle.mjs +34 -18
  39. package/hub/gemini-adapter.mjs +82 -108
  40. package/hub/hitl.mjs +202 -143
  41. package/hub/intent.mjs +167 -38
  42. package/hub/lib/bash-path.mjs +73 -0
  43. package/hub/lib/cache-guard.mjs +43 -17
  44. package/hub/lib/env-detect.mjs +145 -0
  45. package/hub/lib/hosts-compat.mjs +319 -0
  46. package/hub/lib/mcp-response-cache.mjs +239 -0
  47. package/hub/lib/memory-store.mjs +285 -111
  48. package/hub/lib/path-utils.mjs +165 -0
  49. package/hub/lib/phase-manager.mjs +493 -0
  50. package/hub/lib/process-utils.mjs +797 -104
  51. package/hub/lib/prompt-tmp.mjs +33 -0
  52. package/hub/lib/risk-tier.mjs +53 -0
  53. package/hub/lib/spawn-trace.mjs +561 -0
  54. package/hub/lib/ssh-command.mjs +44 -24
  55. package/hub/lib/ssh-retry.mjs +5 -1
  56. package/hub/lib/staged-file-detect.mjs +43 -0
  57. package/hub/lib/state-snapshot.mjs +293 -0
  58. package/hub/lib/tfx-route-args.mjs +309 -0
  59. package/hub/lib/timeline-adapter.mjs +190 -0
  60. package/hub/lib/trace-recorder.mjs +153 -0
  61. package/hub/lib/uuidv7.mjs +4 -3
  62. package/hub/memory-doctor.mjs +266 -106
  63. package/hub/middleware/quota-middleware.mjs +70 -0
  64. package/hub/middleware/request-logger.mjs +61 -34
  65. package/hub/paths.mjs +9 -9
  66. package/hub/pipeline/gates/confidence.mjs +34 -15
  67. package/hub/pipeline/gates/consensus.mjs +27 -15
  68. package/hub/pipeline/gates/index.mjs +7 -3
  69. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  70. package/hub/pipeline/index.mjs +77 -42
  71. package/hub/pipeline/state.mjs +10 -10
  72. package/hub/pipeline/transitions.mjs +40 -23
  73. package/hub/platform.mjs +65 -48
  74. package/hub/quality/deslop.mjs +70 -49
  75. package/hub/reflexion.mjs +87 -13
  76. package/hub/research.mjs +33 -25
  77. package/hub/router.mjs +941 -791
  78. package/hub/routing/complexity.mjs +132 -29
  79. package/hub/routing/index.mjs +17 -12
  80. package/hub/routing/q-learning.mjs +78 -30
  81. package/hub/routing-snapshot.mjs +261 -0
  82. package/hub/session-fingerprint.mjs +127 -61
  83. package/hub/state.mjs +86 -44
  84. package/hub/team/claude-daemon-control.mjs +1130 -0
  85. package/hub/team/retry-state-machine.mjs +336 -0
  86. package/hub/token-mode.mjs +72 -71
  87. package/hub/workers/worker-utils.mjs +23 -17
  88. package/hud/colors.mjs +27 -9
  89. package/hud/constants.mjs +190 -27
  90. package/hud/context-monitor.mjs +133 -44
  91. package/hud/hud-qos-status.mjs +157 -50
  92. package/hud/mission-board.mjs +56 -0
  93. package/hud/providers/claude.mjs +398 -114
  94. package/hud/providers/codex.mjs +96 -21
  95. package/hud/providers/gemini.mjs +210 -52
  96. package/hud/renderers.mjs +440 -127
  97. package/hud/terminal.mjs +101 -31
  98. package/hud/utils.mjs +81 -39
  99. package/mesh/index.mjs +69 -0
  100. package/mesh/mesh-budget.mjs +137 -0
  101. package/mesh/mesh-heartbeat.mjs +100 -0
  102. package/mesh/mesh-protocol.mjs +96 -0
  103. package/mesh/mesh-queue.mjs +163 -0
  104. package/mesh/mesh-registry.mjs +78 -0
  105. package/mesh/mesh-router.mjs +77 -0
  106. package/package.json +5 -1
  107. package/scripts/lib/agent-json.mjs +27 -0
  108. package/scripts/lib/async.mjs +174 -0
  109. package/scripts/lib/claudemd-scanner.mjs +6 -1
  110. package/scripts/lib/cli-agy.mjs +62 -0
  111. package/scripts/lib/cli-claude.mjs +78 -0
  112. package/scripts/lib/cli-codex.mjs +199 -0
  113. package/scripts/lib/cli-gemini.mjs +37 -0
  114. package/scripts/lib/codex-recovery.sh +50 -0
  115. package/scripts/lib/context.mjs +3 -3
  116. package/scripts/lib/cross-review-utils.mjs +13 -5
  117. package/scripts/lib/doctor-env-checks.mjs +121 -0
  118. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  119. package/scripts/lib/env-probe.mjs +58 -30
  120. package/scripts/lib/env.mjs +14 -0
  121. package/scripts/lib/gemini-profiles.mjs +44 -10
  122. package/scripts/lib/handoff.mjs +33 -17
  123. package/scripts/lib/hook-utils.mjs +8 -6
  124. package/scripts/lib/hub.mjs +48 -0
  125. package/scripts/lib/keyword-rules.mjs +49 -20
  126. package/scripts/lib/logger.mjs +24 -24
  127. package/scripts/lib/mcp-filter.mjs +391 -247
  128. package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
  129. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  130. package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
  131. package/scripts/lib/mcp-guard-engine.mjs +1281 -115
  132. package/scripts/lib/mcp-health.mjs +773 -0
  133. package/scripts/lib/mcp-manifest.mjs +24 -14
  134. package/scripts/lib/mcp-server-catalog.mjs +355 -118
  135. package/scripts/lib/pid.mjs +63 -0
  136. package/scripts/lib/process-utils.mjs +26 -0
  137. package/scripts/lib/psmux-info.mjs +117 -12
  138. package/scripts/lib/quota.mjs +47 -0
  139. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  140. package/scripts/lib/skill-state.mjs +220 -0
  141. package/scripts/lib/skill-template.mjs +32 -7
  142. package/scripts/lib/team.mjs +75 -0
  143. package/scripts/lib/timeout.mjs +66 -0
  144. package/scripts/lib/tmp.mjs +26 -0
  145. package/scripts/lib/toml.mjs +113 -0
@@ -5,46 +5,80 @@
5
5
  // 각 모듈에서 색상, 터미널, 프로바이더, 렌더러를 가져와 조합한다.
6
6
  // ============================================================================
7
7
 
8
- import { DIM, RESET, bold, dim, green, claudeOrange, codexWhite, geminiBlue, colorByPercent } from "./colors.mjs";
9
8
  import {
10
- QOS_PATH, ACCOUNTS_CONFIG_PATH, ACCOUNTS_STATE_PATH,
11
- CLAUDE_REFRESH_FLAG, CODEX_REFRESH_FLAG,
12
- GEMINI_REFRESH_FLAG, GEMINI_SESSION_REFRESH_FLAG,
13
- GEMINI_PRO_POOL, GEMINI_FLASH_POOL,
9
+ bold,
10
+ claudeOrange,
11
+ codexWhite,
12
+ DIM,
13
+ dim,
14
+ geminiBlue,
15
+ green,
16
+ RESET,
17
+ } from "./colors.mjs";
18
+ import {
19
+ ACCOUNTS_CONFIG_PATH,
20
+ ACCOUNTS_STATE_PATH,
21
+ CLAUDE_REFRESH_FLAG,
22
+ CODEX_REFRESH_FLAG,
23
+ GEMINI_FLASH_POOL,
24
+ GEMINI_PRO_POOL,
25
+ GEMINI_REFRESH_FLAG,
26
+ GEMINI_SESSION_REFRESH_FLAG,
27
+ QOS_PATH,
28
+ TFX_PREFLIGHT_CACHE_PATH,
29
+ TFX_PREFLIGHT_CACHE_STALE_MS,
14
30
  } from "./constants.mjs";
15
31
  import {
16
- readJson, readStdinJson, getProviderAccountId, getCliArgValue,
17
- } from "./utils.mjs";
18
- import { selectTier } from "./terminal.mjs";
19
-
32
+ buildContextUsageView,
33
+ deriveContextLimit,
34
+ } from "./context-monitor.mjs";
35
+ import { getMissionBoardState } from "./mission-board.mjs";
20
36
  // Claude provider
21
37
  import {
22
- readClaudeUsageSnapshot, scheduleClaudeUsageRefresh, fetchClaudeUsage,
38
+ fetchClaudeUsage,
23
39
  readClaudeContextSnapshot,
40
+ readClaudeUsageSnapshot,
41
+ scheduleClaudeUsageRefresh,
24
42
  } from "./providers/claude.mjs";
25
- import { buildContextUsageView } from "./context-monitor.mjs";
26
-
27
43
  // Codex provider
28
44
  import {
29
- getCodexEmail, readCodexRateLimitSnapshot,
30
- refreshCodexRateLimitsCache, scheduleCodexRateLimitRefresh,
45
+ getCodexEmail,
46
+ readCodexRateLimitSnapshot,
47
+ refreshCodexRateLimitsCache,
48
+ scheduleCodexRateLimitRefresh,
31
49
  } from "./providers/codex.mjs";
32
-
33
50
  // Gemini provider
34
51
  import {
35
- getGeminiEmail, buildGeminiAuthContext,
36
- readGeminiQuotaSnapshot, readGeminiSessionSnapshot,
37
- fetchGeminiQuota, refreshGeminiSessionCache,
38
- scheduleGeminiQuotaRefresh, scheduleGeminiSessionRefresh,
52
+ buildGeminiAuthContext,
53
+ fetchGeminiQuota,
54
+ getAntigravityEmail,
55
+ getGeminiEmail,
56
+ readGeminiQuotaSnapshot,
57
+ readGeminiSessionSnapshot,
58
+ refreshGeminiSessionCache,
59
+ scheduleGeminiQuotaRefresh,
60
+ scheduleGeminiSessionRefresh,
39
61
  } from "./providers/gemini.mjs";
40
-
41
62
  // Renderers
42
63
  import {
43
- getClaudeRows, getProviderRow, getTeamRow,
44
- renderAlignedRows, getMicroLine,
45
- readLatestBenchmarkDiff, formatTokenSummary,
46
- readTokenSavings, readSvAccumulator,
64
+ formatTokenSummary,
65
+ getClaudeRows,
66
+ getMicroLine,
67
+ getProviderRow,
68
+ getTeamRow,
69
+ readLatestBenchmarkDiff,
70
+ readSvAccumulator,
71
+ readTokenSavings,
72
+ renderAlignedRows,
73
+ renderMissionBoard,
47
74
  } from "./renderers.mjs";
75
+ import { selectTier } from "./terminal.mjs";
76
+ import {
77
+ getCliArgValue,
78
+ getProviderAccountId,
79
+ readJson,
80
+ readStdinJson,
81
+ } from "./utils.mjs";
48
82
 
49
83
  // ============================================================================
50
84
  // 메인
@@ -78,6 +112,13 @@ async function main() {
78
112
  const stdinPromise = readStdinJson();
79
113
 
80
114
  const qosProfile = readJson(QOS_PATH, { providers: {} });
115
+ const preflightCache = readJson(TFX_PREFLIGHT_CACHE_PATH, null);
116
+ const preflightTimestamp = Number(preflightCache?.timestamp);
117
+ const preflightFresh =
118
+ Number.isFinite(preflightTimestamp) &&
119
+ Date.now() - preflightTimestamp <= TFX_PREFLIGHT_CACHE_STALE_MS;
120
+ const antigravityReady =
121
+ preflightFresh && preflightCache?.antigravity?.ok === true;
81
122
  const accountsConfig = readJson(ACCOUNTS_CONFIG_PATH, { providers: {} });
82
123
  const accountsState = readJson(ACCOUNTS_STATE_PATH, { providers: {} });
83
124
  const claudeUsageSnapshot = readClaudeUsageSnapshot();
@@ -85,11 +126,18 @@ async function main() {
85
126
  if (claudeUsageSnapshot.shouldRefresh) {
86
127
  scheduleClaudeUsageRefresh();
87
128
  }
88
- const geminiAccountId = getProviderAccountId("gemini", accountsConfig, accountsState);
129
+ const geminiAccountId = getProviderAccountId(
130
+ "gemini",
131
+ accountsConfig,
132
+ accountsState,
133
+ );
89
134
  const codexSnapshot = readCodexRateLimitSnapshot();
90
135
  const geminiSessionSnapshot = readGeminiSessionSnapshot();
91
136
  const geminiAuthContext = buildGeminiAuthContext(geminiAccountId);
92
- const geminiQuotaSnapshot = readGeminiQuotaSnapshot(geminiAccountId, geminiAuthContext);
137
+ const geminiQuotaSnapshot = readGeminiQuotaSnapshot(
138
+ geminiAccountId,
139
+ geminiAuthContext,
140
+ );
93
141
  if (codexSnapshot.shouldRefresh) {
94
142
  scheduleCodexRateLimitRefresh();
95
143
  }
@@ -103,25 +151,31 @@ async function main() {
103
151
  // 실측 데이터 추출
104
152
  const stdin = await stdinPromise;
105
153
  const contextView = buildContextUsageView(stdin, contextSnapshot);
154
+ const claudeUsage = claudeUsageSnapshot.data;
106
155
  const codexEmail = getCodexEmail();
107
156
  const geminiEmail = getGeminiEmail();
157
+ const antigravityEmail = getAntigravityEmail();
108
158
  const codexBuckets = codexSnapshot.buckets;
109
159
  const geminiSession = geminiSessionSnapshot.session;
110
160
  const geminiQuota = geminiQuotaSnapshot.quota;
161
+ const missionBoardState = await getMissionBoardState();
111
162
 
112
163
  // 누적 절약 데이터 읽기
113
164
  const svSavings = readTokenSavings();
114
165
  const svAccumulator = readSvAccumulator();
115
- const totalCostSaved = svSavings?.totalSaved || svAccumulator?.totalCostSaved || 0;
166
+ const _totalCostSaved =
167
+ svSavings?.totalSaved || svAccumulator?.totalCostSaved || 0;
116
168
 
117
169
  // 세션/누적 토큰 → context 대비 절약 배수 (개별 provider sv%)
118
- const ctxCapacity = stdin?.context_window?.context_window_size || 200000;
170
+ const ctxCapacity = deriveContextLimit(stdin);
119
171
  let codexSv = null;
120
172
  if (svAccumulator?.codex?.tokens > 0) {
121
173
  codexSv = svAccumulator.codex.tokens / ctxCapacity;
122
174
  } else if (codexBuckets) {
123
- const main = codexBuckets.codex || codexBuckets[Object.keys(codexBuckets)[0]];
124
- if (main?.tokens?.total_tokens) codexSv = main.tokens.total_tokens / ctxCapacity;
175
+ const main =
176
+ codexBuckets.codex || codexBuckets[Object.keys(codexBuckets)[0]];
177
+ if (main?.tokens?.total_tokens)
178
+ codexSv = main.tokens.total_tokens / ctxCapacity;
125
179
  }
126
180
  let geminiSv = null;
127
181
  if (svAccumulator?.gemini?.tokens > 0) {
@@ -134,12 +188,16 @@ async function main() {
134
188
  // Gemini: 3풀 버킷 추출 (Pro/Flash/Lite — 각 풀 내 모델들은 쿼터 공유)
135
189
  const geminiModel = geminiSession?.model || "gemini-3-flash-preview";
136
190
  const geminiBuckets = geminiQuota?.buckets || [];
137
- const geminiBucket = geminiBuckets.find((b) => b.modelId === geminiModel)
138
- || geminiBuckets.find((b) => b.modelId === "gemini-3-flash-preview")
139
- || null;
140
- const geminiProBucket = geminiBuckets.find((b) => GEMINI_PRO_POOL.has(b.modelId)) || null;
141
- const geminiFlashBucket = geminiBuckets.find((b) => GEMINI_FLASH_POOL.has(b.modelId)) || null;
142
- const geminiLiteBucket = geminiBuckets.find((b) => b.modelId?.includes("flash-lite")) || null;
191
+ const geminiBucket =
192
+ geminiBuckets.find((b) => b.modelId === geminiModel) ||
193
+ geminiBuckets.find((b) => b.modelId === "gemini-3-flash-preview") ||
194
+ null;
195
+ const geminiProBucket =
196
+ geminiBuckets.find((b) => GEMINI_PRO_POOL.has(b.modelId)) || null;
197
+ const geminiFlashBucket =
198
+ geminiBuckets.find((b) => GEMINI_FLASH_POOL.has(b.modelId)) || null;
199
+ const geminiLiteBucket =
200
+ geminiBuckets.find((b) => b.modelId?.includes("flash-lite")) || null;
143
201
 
144
202
  // 합산 절약: Codex+Gemini sv% 합산 (컨텍스트 대비 위임 토큰 비율)
145
203
  const combinedSvPct = Math.round(((codexSv ?? 0) + (geminiSv ?? 0)) * 100);
@@ -149,32 +207,76 @@ async function main() {
149
207
 
150
208
  // nano tier: 1줄 모드 (극소 폭 또는 알림 배너 대응)
151
209
  if (CURRENT_TIER === "nano") {
152
- const microLine = getMicroLine(contextView, claudeUsageSnapshot.data, codexBuckets,
153
- geminiSession, geminiBucket, combinedSvPct);
210
+ const microLine = getMicroLine(
211
+ contextView,
212
+ claudeUsage,
213
+ codexBuckets,
214
+ antigravityReady ? null : geminiSession,
215
+ antigravityReady ? null : geminiBucket,
216
+ antigravityReady ? Math.round((codexSv ?? 0) * 100) : combinedSvPct,
217
+ antigravityReady ? "a" : "g",
218
+ );
154
219
  process.stdout.write(`\x1b[0m${microLine}\n`);
155
220
  return;
156
221
  }
157
222
 
158
- const codexQuotaData = codexBuckets ? { type: "codex", buckets: codexBuckets } : null;
223
+ const codexQuotaData = codexBuckets
224
+ ? { type: "codex", buckets: codexBuckets }
225
+ : null;
159
226
  const geminiQuotaData = {
160
227
  type: "gemini",
161
228
  quotaBucket: geminiBucket,
162
- pools: { pro: geminiProBucket, flash: geminiFlashBucket, lite: geminiLiteBucket },
229
+ pools: {
230
+ pro: geminiProBucket,
231
+ flash: geminiFlashBucket,
232
+ lite: geminiLiteBucket,
233
+ },
163
234
  session: geminiSession,
164
235
  };
165
236
 
166
237
  const rows = [
167
- ...getClaudeRows(CURRENT_TIER, contextView, claudeUsageSnapshot.data, combinedSvPct),
168
- getProviderRow(CURRENT_TIER, "codex", "x", codexWhite, qosProfile, accountsConfig, accountsState,
169
- codexQuotaData, codexEmail, codexSv, null),
170
- getProviderRow(CURRENT_TIER, "gemini", "g", geminiBlue, qosProfile, accountsConfig, accountsState,
171
- geminiQuotaData, geminiEmail, geminiSv, null),
238
+ ...getClaudeRows(CURRENT_TIER, contextView, claudeUsage, combinedSvPct),
239
+ getProviderRow(
240
+ CURRENT_TIER,
241
+ "codex",
242
+ "x",
243
+ codexWhite,
244
+ qosProfile,
245
+ accountsConfig,
246
+ accountsState,
247
+ codexQuotaData,
248
+ codexEmail,
249
+ codexSv,
250
+ null,
251
+ ),
252
+ getProviderRow(
253
+ CURRENT_TIER,
254
+ antigravityReady ? "antigravity" : "gemini",
255
+ antigravityReady ? "a" : "g",
256
+ geminiBlue,
257
+ qosProfile,
258
+ accountsConfig,
259
+ accountsState,
260
+ antigravityReady ? { type: "antigravity" } : geminiQuotaData,
261
+ antigravityReady ? antigravityEmail : geminiEmail,
262
+ antigravityReady ? null : geminiSv,
263
+ null,
264
+ ),
172
265
  ];
173
266
 
174
267
  // tfx-multi 활성 시 팀 상태 행 추가 (v2.2)
175
268
  const teamRow = getTeamRow(CURRENT_TIER);
176
269
  if (teamRow) rows.push(teamRow);
177
270
 
271
+ const missionBoard = renderMissionBoard(missionBoardState);
272
+ if (missionBoard) {
273
+ rows.push({
274
+ prefix: bold(claudeOrange("\u25B2")),
275
+ left: missionBoard,
276
+ right: "",
277
+ });
278
+ }
279
+
178
280
  // 최근 벤치마크 diff → 토큰 요약 행 추가
179
281
  const latestDiff = readLatestBenchmarkDiff();
180
282
  if (latestDiff) {
@@ -186,10 +288,13 @@ async function main() {
186
288
 
187
289
  // 비활성 프로바이더 dim 처리: 데이터 없으면 전체 줄 dim
188
290
  const codexActive = codexBuckets != null;
189
- const geminiActive = (geminiSession?.total || 0) > 0 || geminiBucket != null
190
- || geminiProBucket != null || geminiFlashBucket != null;
291
+ const geminiActive =
292
+ (geminiSession?.total || 0) > 0 ||
293
+ geminiBucket != null ||
294
+ geminiProBucket != null ||
295
+ geminiFlashBucket != null;
191
296
 
192
- let outputLines = renderAlignedRows(rows);
297
+ const outputLines = renderAlignedRows(rows);
193
298
 
194
299
  // 비활성 줄 dim 래핑 (rows 순서: [claude, codex, gemini])
195
300
  if (outputLines.length >= 3) {
@@ -201,10 +306,12 @@ async function main() {
201
306
  const contextPercent = contextView.percent;
202
307
  const leadingBreaks = contextPercent >= 85 ? "\n\n" : "\n";
203
308
  // 줄별 RESET: Claude Code TUI 스타일 간섭 방지 (색상 밝기 버그 수정)
204
- const resetedLines = outputLines.map(line => `\x1b[0m${line}`);
309
+ const resetedLines = outputLines.map((line) => `\x1b[0m${line}`);
205
310
  process.stdout.write(`${leadingBreaks}${resetedLines.join("\n")}\n`);
206
311
  }
207
312
 
208
313
  main().catch(() => {
209
- process.stdout.write(`\x1b[0m${bold(claudeOrange("c"))}: ${dim("5h:")}${green("0%")} ${dim("(n/a)")} ${dim("1w:")}${green("0%")} ${dim("(n/a)")} ${dim("|")} ${dim("ctx:")}${green("0%")}\n`);
314
+ process.stdout.write(
315
+ `\x1b[0m${bold(claudeOrange("c"))}: ${dim("5h:")}${green("0%")} ${dim("(n/a)")} ${dim("1w:")}${green("0%")} ${dim("(n/a)")} ${dim("|")} ${dim("ctx:")}${green("0%")}\n`,
316
+ );
210
317
  });
@@ -0,0 +1,56 @@
1
+ // ============================================================================
2
+ // HUD Mission Board — .omc/state/sessions/ 기반 에이전트 진행률 집계
3
+ // ============================================================================
4
+ import { readdir, readFile } from "node:fs/promises";
5
+ import { homedir } from "node:os";
6
+ import { join } from "node:path";
7
+
8
+ const SESSIONS_DIR = join(homedir(), ".omc", "state", "sessions");
9
+
10
+ /**
11
+ * .omc/state/sessions/ 디렉토리를 읽어 팀 상태를 반환한다.
12
+ * @returns {{ agents: Array<{name: string, status: string, progress: number}>, dagLevel: number, totalProgress: number } | null}
13
+ */
14
+ export async function getMissionBoardState(sessionsDir = SESSIONS_DIR) {
15
+ let entries;
16
+ try {
17
+ entries = await readdir(sessionsDir);
18
+ } catch {
19
+ return null;
20
+ }
21
+
22
+ const jsonFiles = entries.filter((f) => f.endsWith(".json"));
23
+ if (jsonFiles.length === 0) return null;
24
+
25
+ const agents = [];
26
+ for (const file of jsonFiles) {
27
+ let data;
28
+ try {
29
+ const raw = await readFile(join(sessionsDir, file), "utf8");
30
+ data = JSON.parse(raw);
31
+ } catch {
32
+ continue;
33
+ }
34
+
35
+ const name = data.name ?? file.replace(/\.json$/, "");
36
+ const status = data.status ?? "idle";
37
+ const progress = typeof data.progress === "number" ? data.progress : 0;
38
+ agents.push({ name, status, progress });
39
+ }
40
+
41
+ if (agents.length === 0) return null;
42
+
43
+ const totalProgress =
44
+ agents.length > 0
45
+ ? Math.round(
46
+ agents.reduce((sum, a) => sum + a.progress, 0) / agents.length,
47
+ )
48
+ : 0;
49
+
50
+ return {
51
+ agents,
52
+ // TODO: derive dagLevel from real mission dependency metadata instead of hardcoding 0.
53
+ dagLevel: 0,
54
+ totalProgress,
55
+ };
56
+ }