triflux 10.3.4 → 10.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/LICENSE +21 -21
  2. package/bin/tfx-doctor-tui.mjs +1 -1
  3. package/bin/tfx-doctor.mjs +6 -1
  4. package/bin/tfx-profile.mjs +1 -1
  5. package/bin/tfx-setup-tui.mjs +1 -1
  6. package/bin/tfx-setup.mjs +6 -1
  7. package/bin/triflux.mjs +2396 -1140
  8. package/hooks/agent-route-guard.mjs +12 -8
  9. package/hooks/cross-review-tracker.mjs +21 -8
  10. package/hooks/error-context.mjs +19 -7
  11. package/hooks/hook-adaptive-collector.mjs +18 -16
  12. package/hooks/hook-manager.mjs +93 -32
  13. package/hooks/hook-orchestrator.mjs +108 -24
  14. package/hooks/hook-registry.json +11 -0
  15. package/hooks/keyword-rules.json +6 -10
  16. package/hooks/lib/resolve-root.mjs +1 -1
  17. package/hooks/mcp-config-watcher.mjs +6 -2
  18. package/hooks/pipeline-stop.mjs +3 -6
  19. package/hooks/safety-guard.mjs +99 -28
  20. package/hooks/session-start-fast.mjs +143 -0
  21. package/hooks/subagent-verifier.mjs +5 -4
  22. package/hub/account-broker.mjs +256 -60
  23. package/hub/adaptive-diagnostic.mjs +75 -48
  24. package/hub/adaptive-inject.mjs +95 -57
  25. package/hub/adaptive-memory.mjs +156 -42
  26. package/hub/adaptive.mjs +60 -31
  27. package/hub/assign-callbacks.mjs +67 -30
  28. package/hub/bridge.mjs +0 -1
  29. package/hub/cli-adapter-base.mjs +200 -48
  30. package/hub/codex-adapter.mjs +76 -96
  31. package/hub/codex-compat.mjs +3 -3
  32. package/hub/codex-preflight.mjs +63 -37
  33. package/hub/delegator/contracts.mjs +19 -23
  34. package/hub/delegator/index.mjs +3 -3
  35. package/hub/delegator/service.mjs +88 -64
  36. package/hub/delegator/tool-definitions.mjs +5 -5
  37. package/hub/fullcycle.mjs +33 -17
  38. package/hub/gemini-adapter.mjs +69 -94
  39. package/hub/hitl.mjs +89 -30
  40. package/hub/intent.mjs +161 -38
  41. package/hub/lib/cache-guard.mjs +43 -17
  42. package/hub/lib/mcp-response-cache.mjs +66 -32
  43. package/hub/lib/memory-store.mjs +285 -111
  44. package/hub/lib/path-utils.mjs +35 -37
  45. package/hub/lib/process-utils.mjs +106 -37
  46. package/hub/lib/spawn-trace.mjs +527 -0
  47. package/hub/lib/ssh-command.mjs +34 -4
  48. package/hub/lib/ssh-retry.mjs +5 -1
  49. package/hub/lib/uuidv7.mjs +4 -3
  50. package/hub/memory-doctor.mjs +266 -106
  51. package/hub/middleware/request-logger.mjs +61 -34
  52. package/hub/paths.mjs +9 -9
  53. package/hub/pipeline/gates/confidence.mjs +34 -15
  54. package/hub/pipeline/gates/consensus.mjs +27 -15
  55. package/hub/pipeline/gates/index.mjs +7 -3
  56. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  57. package/hub/pipeline/index.mjs +77 -42
  58. package/hub/pipeline/state.mjs +10 -10
  59. package/hub/pipeline/transitions.mjs +40 -23
  60. package/hub/platform.mjs +57 -48
  61. package/hub/promote-penalties.mjs +25 -7
  62. package/hub/quality/deslop.mjs +70 -49
  63. package/hub/research.mjs +32 -25
  64. package/hub/router.mjs +240 -107
  65. package/hub/routing/complexity.mjs +132 -29
  66. package/hub/routing/index.mjs +17 -12
  67. package/hub/routing/q-learning.mjs +76 -28
  68. package/hub/server.mjs +4 -4
  69. package/hub/session-fingerprint.mjs +126 -60
  70. package/hub/state.mjs +84 -43
  71. package/hub/store-adapter.mjs +59 -26
  72. package/hub/store.mjs +356 -153
  73. package/hub/team/agent-map.json +22 -7
  74. package/hub/team/ansi.mjs +186 -122
  75. package/hub/team/backend.mjs +28 -10
  76. package/hub/team/cli/commands/attach.mjs +29 -9
  77. package/hub/team/cli/commands/control.mjs +29 -8
  78. package/hub/team/cli/commands/debug.mjs +32 -11
  79. package/hub/team/cli/commands/focus.mjs +38 -11
  80. package/hub/team/cli/commands/interrupt.mjs +18 -6
  81. package/hub/team/cli/commands/kill.mjs +16 -5
  82. package/hub/team/cli/commands/list.mjs +11 -4
  83. package/hub/team/cli/commands/send.mjs +19 -6
  84. package/hub/team/cli/commands/start/index.mjs +154 -31
  85. package/hub/team/cli/commands/start/parse-args.mjs +38 -11
  86. package/hub/team/cli/commands/start/start-headless.mjs +112 -36
  87. package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
  88. package/hub/team/cli/commands/start/start-mux.mjs +70 -21
  89. package/hub/team/cli/commands/start/start-wt.mjs +29 -12
  90. package/hub/team/cli/commands/status.mjs +43 -14
  91. package/hub/team/cli/commands/stop.mjs +11 -4
  92. package/hub/team/cli/commands/task.mjs +8 -3
  93. package/hub/team/cli/commands/tasks.mjs +1 -1
  94. package/hub/team/cli/index.mjs +2 -2
  95. package/hub/team/cli/manifest.mjs +38 -8
  96. package/hub/team/cli/render.mjs +30 -8
  97. package/hub/team/cli/services/attach-fallback.mjs +31 -11
  98. package/hub/team/cli/services/hub-client.mjs +42 -14
  99. package/hub/team/cli/services/member-selector.mjs +11 -4
  100. package/hub/team/cli/services/native-control.mjs +48 -21
  101. package/hub/team/cli/services/runtime-mode.mjs +2 -1
  102. package/hub/team/cli/services/state-store.mjs +25 -8
  103. package/hub/team/cli/services/task-model.mjs +16 -6
  104. package/hub/team/conductor-mesh-bridge.mjs +24 -23
  105. package/hub/team/conductor.mjs +8 -4
  106. package/hub/team/dashboard-anchor.mjs +4 -5
  107. package/hub/team/dashboard-layout.mjs +3 -1
  108. package/hub/team/dashboard-open.mjs +41 -21
  109. package/hub/team/dashboard.mjs +76 -28
  110. package/hub/team/event-log.mjs +18 -10
  111. package/hub/team/handoff.mjs +31 -15
  112. package/hub/team/headless.mjs +2 -1
  113. package/hub/team/health-probe.mjs +69 -54
  114. package/hub/team/launcher-template.mjs +16 -13
  115. package/hub/team/native-supervisor.mjs +65 -21
  116. package/hub/team/native.mjs +74 -35
  117. package/hub/team/nativeProxy.mjs +184 -113
  118. package/hub/team/notify.mjs +119 -76
  119. package/hub/team/orchestrator.mjs +9 -4
  120. package/hub/team/pane.mjs +12 -7
  121. package/hub/team/process-cleanup.mjs +25 -16
  122. package/hub/team/psmux.mjs +491 -201
  123. package/hub/team/remote-probe.mjs +68 -52
  124. package/hub/team/remote-session.mjs +117 -59
  125. package/hub/team/remote-watcher.mjs +61 -33
  126. package/hub/team/routing.mjs +51 -25
  127. package/hub/team/runtime-strategy.mjs +3 -1
  128. package/hub/team/session.mjs +98 -34
  129. package/hub/team/staleState.mjs +72 -30
  130. package/hub/team/swarm-locks.mjs +15 -13
  131. package/hub/team/swarm-planner.mjs +32 -21
  132. package/hub/team/swarm-reconciler.mjs +48 -23
  133. package/hub/team/tui-lite.mjs +266 -68
  134. package/hub/team/tui-remote-adapter.mjs +14 -10
  135. package/hub/team/tui-viewer.mjs +99 -43
  136. package/hub/team/tui.mjs +708 -271
  137. package/hub/team/worktree-lifecycle.mjs +152 -58
  138. package/hub/team/wt-manager.mjs +24 -14
  139. package/hub/token-mode.mjs +71 -71
  140. package/hub/tray.mjs +66 -23
  141. package/hub/workers/claude-worker.mjs +162 -118
  142. package/hub/workers/codex-mcp.mjs +192 -141
  143. package/hub/workers/delegator-mcp.mjs +507 -333
  144. package/hub/workers/factory.mjs +8 -8
  145. package/hub/workers/gemini-worker.mjs +115 -84
  146. package/hub/workers/interface.mjs +6 -1
  147. package/hub/workers/worker-utils.mjs +21 -14
  148. package/hud/colors.mjs +27 -9
  149. package/hud/constants.mjs +162 -26
  150. package/hud/context-monitor.mjs +82 -41
  151. package/hud/hud-qos-status.mjs +129 -49
  152. package/hud/mission-board.mjs +6 -3
  153. package/hud/providers/claude.mjs +226 -115
  154. package/hud/providers/codex.mjs +62 -22
  155. package/hud/providers/gemini.mjs +168 -56
  156. package/hud/renderers.mjs +384 -119
  157. package/hud/terminal.mjs +101 -31
  158. package/hud/utils.mjs +78 -38
  159. package/mesh/index.mjs +11 -5
  160. package/mesh/mesh-budget.mjs +18 -9
  161. package/mesh/mesh-heartbeat.mjs +1 -1
  162. package/mesh/mesh-queue.mjs +3 -5
  163. package/mesh/mesh-router.mjs +5 -4
  164. package/package.json +2 -1
  165. package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
  166. package/scripts/__tests__/keyword-detector.test.mjs +77 -28
  167. package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
  168. package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
  169. package/scripts/__tests__/remote-spawn.test.mjs +10 -4
  170. package/scripts/__tests__/session-start-fast.test.mjs +36 -0
  171. package/scripts/__tests__/skill-template.test.mjs +98 -50
  172. package/scripts/__tests__/smoke.test.mjs +1 -1
  173. package/scripts/__tests__/spawn-trace.test.mjs +102 -0
  174. package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
  175. package/scripts/cache-doctor.mjs +11 -4
  176. package/scripts/cache-warmup.mjs +96 -37
  177. package/scripts/claudemd-sync.mjs +27 -17
  178. package/scripts/codex-gateway-preflight.mjs +52 -37
  179. package/scripts/codex-mcp-gateway-sync.mjs +59 -39
  180. package/scripts/completions/tfx.bash +47 -47
  181. package/scripts/completions/tfx.fish +44 -44
  182. package/scripts/completions/tfx.zsh +83 -83
  183. package/scripts/config-audit.mjs +232 -0
  184. package/scripts/convert-to-tmpl.mjs +54 -0
  185. package/scripts/cross-review-gate.mjs +35 -12
  186. package/scripts/cross-review-tracker.mjs +21 -8
  187. package/scripts/demo.mjs +35 -17
  188. package/scripts/doctor-diagnose.mjs +284 -0
  189. package/scripts/gen-skill-docs.mjs +7 -2
  190. package/scripts/gen-skill-manifest.mjs +2 -1
  191. package/scripts/headless-guard.mjs +86 -48
  192. package/scripts/hub-ensure.mjs +45 -26
  193. package/scripts/keyword-detector.mjs +41 -20
  194. package/scripts/keyword-rules-expander.mjs +47 -30
  195. package/scripts/lib/claudemd-scanner.mjs +6 -1
  196. package/scripts/lib/context.mjs +3 -3
  197. package/scripts/lib/cross-review-utils.mjs +6 -3
  198. package/scripts/lib/env-probe.mjs +47 -28
  199. package/scripts/lib/gemini-profiles.mjs +44 -10
  200. package/scripts/lib/handoff.mjs +33 -17
  201. package/scripts/lib/hook-utils.mjs +8 -6
  202. package/scripts/lib/keyword-rules.mjs +43 -19
  203. package/scripts/lib/logger.mjs +24 -24
  204. package/scripts/lib/mcp-filter.mjs +377 -239
  205. package/scripts/lib/mcp-guard-engine.mjs +194 -79
  206. package/scripts/lib/mcp-manifest.mjs +23 -13
  207. package/scripts/lib/mcp-server-catalog.mjs +300 -63
  208. package/scripts/lib/psmux-info.mjs +11 -6
  209. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  210. package/scripts/lib/skill-template.mjs +30 -7
  211. package/scripts/mcp-check.mjs +58 -39
  212. package/scripts/mcp-gateway-config.mjs +83 -39
  213. package/scripts/mcp-gateway-ensure.mjs +43 -35
  214. package/scripts/mcp-gateway-integration-test.mjs +70 -58
  215. package/scripts/mcp-gateway-start.mjs +126 -60
  216. package/scripts/mcp-gateway-verify.mjs +24 -22
  217. package/scripts/mcp-safety-guard.mjs +44 -11
  218. package/scripts/notion-read.mjs +199 -84
  219. package/scripts/pack.mjs +94 -89
  220. package/scripts/preflight-cache.mjs +27 -10
  221. package/scripts/preinstall.mjs +42 -13
  222. package/scripts/remote-spawn.mjs +309 -94
  223. package/scripts/run.cjs +8 -5
  224. package/scripts/session-spawn-helper.mjs +130 -39
  225. package/scripts/session-stale-cleanup.mjs +123 -0
  226. package/scripts/setup.mjs +941 -492
  227. package/scripts/test-lock.mjs +20 -7
  228. package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
  229. package/scripts/tfx-batch-stats.mjs +32 -11
  230. package/scripts/tfx-gate-activate.mjs +11 -4
  231. package/scripts/tfx-route-post.mjs +87 -20
  232. package/scripts/tfx-route-worker.mjs +57 -51
  233. package/scripts/tfx-route.sh +41 -124
  234. package/scripts/tmp-cleanup.mjs +21 -7
  235. package/scripts/token-snapshot.mjs +204 -85
  236. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  237. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  238. package/skills/.omc/state/last-tool-error.json +7 -0
  239. package/skills/.omc/state/subagent-tracking.json +7 -0
  240. package/skills/_templates/base.md +1 -6
  241. package/skills/merge-worktree/SKILL.md.tmpl +144 -0
  242. package/skills/shared/telemetry-segment.md +6 -0
  243. package/skills/star-prompt/SKILL.md.tmpl +222 -0
  244. package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
  245. package/skills/tfx-analysis/skill.json +1 -6
  246. package/skills/tfx-auto/SKILL.md +1 -0
  247. package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
  248. package/skills/tfx-auto-codex/skill.json +1 -3
  249. package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
  250. package/skills/tfx-autopilot/skill.json +1 -5
  251. package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
  252. package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
  253. package/skills/tfx-autoroute/skill.json +1 -7
  254. package/skills/tfx-codex/SKILL.md +1 -0
  255. package/skills/tfx-codex/skill.json +1 -3
  256. package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
  257. package/skills/tfx-codex-swarm/evals/evals.json +1 -1
  258. package/skills/tfx-codex-swarm/skill.json +1 -4
  259. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
  260. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
  261. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
  262. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
  263. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
  264. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
  265. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
  266. package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
  267. package/skills/tfx-debate/SKILL.md.tmpl +192 -0
  268. package/skills/tfx-debate/skill.json +1 -7
  269. package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
  270. package/skills/tfx-deep-analysis/skill.json +1 -5
  271. package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
  272. package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
  273. package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
  274. package/skills/tfx-deep-qa/skill.json +1 -6
  275. package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
  276. package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
  277. package/skills/tfx-doctor/SKILL.md +21 -0
  278. package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
  279. package/skills/tfx-doctor/skill.json +1 -3
  280. package/skills/tfx-find/SKILL.md +1 -0
  281. package/skills/tfx-forge/SKILL.md.tmpl +187 -0
  282. package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
  283. package/skills/tfx-fullcycle/skill.json +1 -6
  284. package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
  285. package/skills/tfx-gemini/skill.json +1 -3
  286. package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
  287. package/skills/tfx-hooks/skill.json +1 -3
  288. package/skills/tfx-hub/SKILL.md.tmpl +212 -0
  289. package/skills/tfx-hub/skill.json +1 -3
  290. package/skills/tfx-index/SKILL.md +1 -0
  291. package/skills/tfx-index/skill.json +1 -6
  292. package/skills/tfx-interview/SKILL.md.tmpl +285 -0
  293. package/skills/tfx-multi/SKILL.md.tmpl +183 -0
  294. package/skills/tfx-multi/skill.json +1 -3
  295. package/skills/tfx-panel/SKILL.md.tmpl +189 -0
  296. package/skills/tfx-panel/skill.json +1 -7
  297. package/skills/tfx-persist/SKILL.md.tmpl +270 -0
  298. package/skills/tfx-persist/skill.json +1 -7
  299. package/skills/tfx-plan/SKILL.md +1 -0
  300. package/skills/tfx-plan/skill.json +1 -6
  301. package/skills/tfx-profile/SKILL.md.tmpl +239 -0
  302. package/skills/tfx-profile/skill.json +1 -3
  303. package/skills/tfx-prune/SKILL.md.tmpl +200 -0
  304. package/skills/tfx-prune/skill.json +1 -7
  305. package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
  306. package/skills/tfx-psmux-rules/skill.json +1 -4
  307. package/skills/tfx-qa/SKILL.md +1 -0
  308. package/skills/tfx-qa/skill.json +1 -6
  309. package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
  310. package/skills/tfx-ralph/skill.json +1 -4
  311. package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
  312. package/skills/tfx-remote-setup/skill.json +1 -3
  313. package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
  314. package/skills/tfx-remote-spawn/references/hosts.json +16 -0
  315. package/skills/tfx-remote-spawn/skill.json +1 -4
  316. package/skills/tfx-research/SKILL.md +1 -0
  317. package/skills/tfx-review/SKILL.md +1 -0
  318. package/skills/tfx-review/skill.json +1 -6
  319. package/skills/tfx-setup/SKILL.md.tmpl +504 -0
  320. package/skills/tfx-setup/skill.json +1 -3
  321. package/skills/tfx-swarm/SKILL.md +22 -0
  322. package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
  323. package/tui/codex-profile.mjs +88 -33
  324. package/tui/core.mjs +45 -15
  325. package/tui/doctor.mjs +75 -28
  326. package/tui/gemini-profile.mjs +74 -29
  327. package/tui/monitor-data.mjs +8 -4
  328. package/tui/monitor.mjs +71 -27
  329. package/tui/setup.mjs +133 -42
package/hub/fullcycle.mjs CHANGED
@@ -1,14 +1,21 @@
1
1
  // hub/fullcycle.mjs — tfx-fullcycle runtime artifact/state helpers
2
2
 
3
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
4
- import { join, resolve } from 'node:path';
5
- import { ensureTfxDirs, TFX_FULLCYCLE_DIR, TFX_PLANS_DIR } from './paths.mjs';
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ readdirSync,
7
+ readFileSync,
8
+ statSync,
9
+ writeFileSync,
10
+ } from "node:fs";
11
+ import { join, resolve } from "node:path";
12
+ import { ensureTfxDirs, TFX_FULLCYCLE_DIR, TFX_PLANS_DIR } from "./paths.mjs";
6
13
 
7
14
  function safeResolve(baseDir, relativePath) {
8
15
  const base = resolve(baseDir);
9
16
  const target = resolve(join(baseDir, relativePath));
10
17
  if (!target.startsWith(base)) {
11
- throw new Error('Invalid fullcycle path: path traversal detected');
18
+ throw new Error("Invalid fullcycle path: path traversal detected");
12
19
  }
13
20
  return target;
14
21
  }
@@ -17,9 +24,9 @@ function safeResolve(baseDir, relativePath) {
17
24
  export function createFullcycleRunId(now = new Date()) {
18
25
  return now
19
26
  .toISOString()
20
- .replace(/[:.]/g, '-')
21
- .replace('T', '_')
22
- .replace('Z', 'Z');
27
+ .replace(/[:.]/g, "-")
28
+ .replace("T", "_")
29
+ .replace("Z", "Z");
23
30
  }
24
31
 
25
32
  export function getFullcycleRunDir(runId, baseDir = process.cwd()) {
@@ -33,32 +40,41 @@ export function ensureFullcycleRunDir(runId, baseDir = process.cwd()) {
33
40
  return dir;
34
41
  }
35
42
 
36
- export function saveFullcycleArtifact(runId, filename, content, baseDir = process.cwd()) {
37
- if (!filename || typeof filename !== 'string') {
38
- throw new Error('Artifact filename is required');
43
+ export function saveFullcycleArtifact(
44
+ runId,
45
+ filename,
46
+ content,
47
+ baseDir = process.cwd(),
48
+ ) {
49
+ if (!filename || typeof filename !== "string") {
50
+ throw new Error("Artifact filename is required");
39
51
  }
40
52
 
41
53
  const dir = ensureFullcycleRunDir(runId, baseDir);
42
54
  const path = safeResolve(dir, filename);
43
- writeFileSync(path, content, 'utf8');
55
+ writeFileSync(path, content, "utf8");
44
56
  return path;
45
57
  }
46
58
 
47
- export function readFullcycleArtifact(runId, filename, baseDir = process.cwd()) {
59
+ export function readFullcycleArtifact(
60
+ runId,
61
+ filename,
62
+ baseDir = process.cwd(),
63
+ ) {
48
64
  const dir = getFullcycleRunDir(runId, baseDir);
49
65
  const path = safeResolve(dir, filename);
50
66
  if (!existsSync(path)) return null;
51
- return readFileSync(path, 'utf8');
67
+ return readFileSync(path, "utf8");
52
68
  }
53
69
 
54
70
  export function writeFullcycleState(runId, state, baseDir = process.cwd()) {
55
- const payload = typeof state === 'object' && state !== null ? state : {};
71
+ const payload = typeof state === "object" && state !== null ? state : {};
56
72
  const serialized = JSON.stringify(payload, null, 2);
57
- return saveFullcycleArtifact(runId, 'state.json', serialized, baseDir);
73
+ return saveFullcycleArtifact(runId, "state.json", serialized, baseDir);
58
74
  }
59
75
 
60
76
  export function readFullcycleState(runId, baseDir = process.cwd()) {
61
- const content = readFullcycleArtifact(runId, 'state.json', baseDir);
77
+ const content = readFullcycleArtifact(runId, "state.json", baseDir);
62
78
  if (!content) return null;
63
79
  try {
64
80
  return JSON.parse(content);
@@ -87,7 +103,7 @@ export function shouldStopQaLoop(failureHistory = [], maxRepeats = 3) {
87
103
  if (!Array.isArray(failureHistory) || maxRepeats <= 1) return false;
88
104
 
89
105
  const normalized = failureHistory
90
- .map((entry) => String(entry ?? '').trim())
106
+ .map((entry) => String(entry ?? "").trim())
91
107
  .filter(Boolean);
92
108
 
93
109
  if (normalized.length < maxRepeats) return false;
@@ -1,41 +1,42 @@
1
1
  // hub/gemini-adapter.mjs — Gemini CLI 방어 계층
2
2
  // codex-adapter.mjs와 동일 패턴, cli-adapter-base 공통 인터페이스 사용
3
3
 
4
- import { mkdirSync } from 'node:fs';
5
- import { join } from 'node:path';
6
- import { tmpdir } from 'node:os';
7
-
8
- import { withRetry } from './workers/worker-utils.mjs';
9
- import { whichCommandAsync } from './platform.mjs';
4
+ import { mkdirSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
10
7
  import {
11
- createCircuitBreaker,
12
- createResult,
13
- appendWarnings,
8
+ executeWithCircuitBroker,
14
9
  normalizePathForShell,
15
- shellQuote,
16
10
  runProcess,
17
- } from './cli-adapter-base.mjs';
18
-
19
- const breaker = createCircuitBreaker();
11
+ shellQuote,
12
+ } from "./cli-adapter-base.mjs";
13
+ import { whichCommandAsync } from "./platform.mjs";
20
14
 
21
15
  // ── Gemini-specific stall inference ─────────────────────────────
22
16
 
23
17
  function inferStallMode(stdout, stderr) {
24
18
  const text = `${stdout}\n${stderr}`.toLowerCase();
25
- if (/(rate.?limit|quota|resource.?exhaust|429)/u.test(text)) return 'rate_limited';
26
- if (/(unauthorized|forbidden|auth|login|token|credential|api.?key)/u.test(text)) return 'auth_stall';
27
- if (/\bmcp\b|playwright|tavily|brave|sequential|server/u.test(text)) return 'mcp_stall';
28
- return 'timeout';
19
+ if (/(rate.?limit|quota|resource.?exhaust|429)/u.test(text))
20
+ return "rate_limited";
21
+ if (
22
+ /(unauthorized|forbidden|auth|login|token|credential|api.?key)/u.test(text)
23
+ )
24
+ return "auth_stall";
25
+ if (/\bmcp\b|playwright|tavily|brave|sequential|server/u.test(text))
26
+ return "mcp_stall";
27
+ return "timeout";
29
28
  }
30
29
 
31
30
  // ── Preflight ───────────────────────────────────────────────────
32
31
 
33
32
  async function runPreflight(opts = {}) {
34
- const geminiPath = await whichCommandAsync('gemini');
33
+ const geminiPath = await whichCommandAsync("gemini");
35
34
  if (!geminiPath) {
36
35
  return {
37
36
  geminiPath: null,
38
- warnings: ['Gemini CLI not found. Install Gemini and ensure `gemini` is available on PATH.'],
37
+ warnings: [
38
+ "Gemini CLI not found. Install Gemini and ensure `gemini` is available on PATH.",
39
+ ],
39
40
  excludeMcpServers: [],
40
41
  ok: false,
41
42
  };
@@ -45,7 +46,7 @@ async function runPreflight(opts = {}) {
45
46
  const excludeMcpServers = [];
46
47
 
47
48
  for (const name of Array.isArray(opts.mcpServers) ? opts.mcpServers : []) {
48
- const server = String(name ?? '').trim();
49
+ const server = String(name ?? "").trim();
49
50
  if (!server) continue;
50
51
  // Gemini MCP health는 best-effort: 실행 시점에 --allowed-mcp-server-names로 필터링
51
52
  // 사전 probe는 수행하지 않음 (gemini가 자체적으로 graceful degrade)
@@ -57,26 +58,33 @@ async function runPreflight(opts = {}) {
57
58
  // ── Command building ────────────────────────────────────────────
58
59
 
59
60
  function buildGeminiCommand(prompt, resultFile, opts = {}) {
60
- const parts = ['gemini'];
61
+ const parts = ["gemini"];
61
62
 
62
- if (opts.model) parts.push('--model', shellQuote(opts.model));
63
- parts.push('--yolo');
63
+ if (opts.model) parts.push("--model", shellQuote(opts.model));
64
+ parts.push("--yolo");
64
65
 
65
- const allowed = Array.isArray(opts.allowedMcpServers) ? opts.allowedMcpServers : [];
66
- const excluded = Array.isArray(opts.excludeMcpServers) ? opts.excludeMcpServers : [];
66
+ const allowed = Array.isArray(opts.allowedMcpServers)
67
+ ? opts.allowedMcpServers
68
+ : [];
69
+ const excluded = Array.isArray(opts.excludeMcpServers)
70
+ ? opts.excludeMcpServers
71
+ : [];
67
72
  const filtered = allowed.filter((name) => !excluded.includes(name));
68
73
  if (filtered.length) {
69
- parts.push('--allowed-mcp-server-names', ...filtered.map((n) => shellQuote(n)));
74
+ parts.push(
75
+ "--allowed-mcp-server-names",
76
+ ...filtered.map((n) => shellQuote(n)),
77
+ );
70
78
  }
71
79
 
72
- parts.push('--prompt', shellQuote(prompt));
73
- parts.push('--output-format', 'text');
80
+ parts.push("--prompt", shellQuote(prompt));
81
+ parts.push("--output-format", "text");
74
82
 
75
83
  if (resultFile) {
76
- return `${parts.join(' ')} > ${shellQuote(normalizePathForShell(resultFile))} 2>${shellQuote(normalizePathForShell(resultFile + '.err'))}`;
84
+ return `${parts.join(" ")} > ${shellQuote(normalizePathForShell(resultFile))} 2>${shellQuote(normalizePathForShell(resultFile + ".err"))}`;
77
85
  }
78
86
 
79
- return parts.join(' ');
87
+ return parts.join(" ");
80
88
  }
81
89
 
82
90
  function buildAttempts(opts, preflight) {
@@ -84,20 +92,19 @@ function buildAttempts(opts, preflight) {
84
92
  const base = {
85
93
  timeout,
86
94
  model: opts.model,
87
- allowedMcpServers: Array.isArray(opts.mcpServers) ? [...opts.mcpServers] : [],
95
+ allowedMcpServers: Array.isArray(opts.mcpServers)
96
+ ? [...opts.mcpServers]
97
+ : [],
88
98
  excludeMcpServers: [...(preflight.excludeMcpServers || [])],
89
99
  };
90
100
  if (opts.retryOnFail === false) return [base];
91
- return [
92
- base,
93
- { ...base, timeout: timeout * 2, allowedMcpServers: [] },
94
- ];
101
+ return [base, { ...base, timeout: timeout * 2, allowedMcpServers: [] }];
95
102
  }
96
103
 
97
104
  // ── Public: buildExecArgs ───────────────────────────────────────
98
105
 
99
106
  export function buildExecArgs(opts = {}) {
100
- const prompt = typeof opts.prompt === 'string' ? opts.prompt : '';
107
+ const prompt = typeof opts.prompt === "string" ? opts.prompt : "";
101
108
  return buildGeminiCommand(prompt, opts.resultFile || null, {
102
109
  model: opts.model,
103
110
  allowedMcpServers: opts.mcpServers,
@@ -107,74 +114,42 @@ export function buildExecArgs(opts = {}) {
107
114
  // ── Execution ───────────────────────────────────────────────────
108
115
 
109
116
  async function runGemini(prompt, workdir, preflight, attempt) {
110
- const dir = join(tmpdir(), 'triflux-gemini-exec');
117
+ const dir = join(tmpdir(), "triflux-gemini-exec");
111
118
  mkdirSync(dir, { recursive: true });
112
- const resultFile = join(dir, `gemini-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`);
119
+ const resultFile = join(
120
+ dir,
121
+ `gemini-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`,
122
+ );
113
123
  const command = buildGeminiCommand(prompt, resultFile, {
114
124
  model: attempt.model,
115
125
  allowedMcpServers: attempt.allowedMcpServers,
116
126
  excludeMcpServers: attempt.excludeMcpServers,
117
127
  });
118
- return runProcess(command, workdir, attempt.timeout, { resultFile, inferStallMode });
128
+ return runProcess(command, workdir, attempt.timeout, {
129
+ resultFile,
130
+ inferStallMode,
131
+ });
119
132
  }
120
133
 
121
134
  // ── Public API ──────────────────────────────────────────────────
122
135
 
123
- export function getCircuitState(now) {
124
- return breaker.getState(now);
136
+ export async function getCircuitState() {
137
+ const brokerMod = await import("./account-broker.mjs");
138
+ if (!brokerMod.broker) return { state: "closed", failures: [] };
139
+ const snap = brokerMod.broker
140
+ .snapshot()
141
+ .filter((a) => a.provider === "gemini");
142
+ return snap.length
143
+ ? { state: snap[0].circuitState, accounts: snap }
144
+ : { state: "closed", failures: [] };
125
145
  }
126
146
 
127
- export async function execute(opts = {}) {
128
- const entry = breaker.canExecute();
129
- if (!entry.allowed) {
130
- return createResult(false, { fellBack: true, failureMode: 'circuit_open' });
131
- }
132
-
133
- const preflight = await runPreflight({ mcpServers: opts.mcpServers });
134
- if (!preflight.ok) {
135
- breaker.clearTrial();
136
- breaker.recordFailure(entry.halfOpen);
137
- return createResult(false, {
138
- stderr: appendWarnings('', preflight.warnings),
139
- fellBack: opts.fallbackToClaude !== false,
140
- failureMode: 'crash',
141
- });
142
- }
143
-
144
- const attempts = buildAttempts(opts, preflight);
145
- let attemptIndex = 0;
146
- let lastResult = createResult(false);
147
-
148
- try {
149
- lastResult = await withRetry(async () => {
150
- const result = await runGemini(opts.prompt || '', opts.workdir || process.cwd(), preflight, attempts[attemptIndex]);
151
- const current = { ...result, stderr: appendWarnings(result.stderr, preflight.warnings), retried: attemptIndex > 0 };
152
- const canRetry = !current.ok && attemptIndex < attempts.length - 1;
153
- attemptIndex += 1;
154
- if (!canRetry) return current;
155
- const error = new Error('retry');
156
- error.retryable = true;
157
- error.result = current;
158
- throw error;
159
- }, {
160
- maxAttempts: attempts.length,
161
- baseDelayMs: 250,
162
- maxDelayMs: 750,
163
- shouldRetry: (error) => error?.retryable === true,
164
- });
165
- } catch (error) {
166
- lastResult = error?.result || createResult(false, { stderr: String(error?.message || error) });
167
- }
168
-
169
- if (lastResult.ok) {
170
- breaker.reset();
171
- return lastResult;
172
- }
173
-
174
- breaker.recordFailure(entry.halfOpen);
175
- return {
176
- ...lastResult,
177
- retried: attempts.length > 1,
178
- fellBack: opts.fallbackToClaude !== false,
179
- };
147
+ export function execute(opts = {}) {
148
+ return executeWithCircuitBroker({
149
+ provider: "gemini",
150
+ runFn: runGemini,
151
+ preflightFn: (o) => runPreflight({ mcpServers: o.mcpServers }),
152
+ buildAttemptsFn: buildAttempts,
153
+ opts,
154
+ });
180
155
  }
package/hub/hitl.mjs CHANGED
@@ -7,20 +7,34 @@
7
7
  * @param {object} router — createRouter() 반환 객체
8
8
  */
9
9
  export function createHitlManager(store, router = null) {
10
- function forwardHumanResponse({ requesterAgent, requestId, action, content, submittedBy, correlationId, traceId, priority }) {
10
+ function forwardHumanResponse({
11
+ requesterAgent,
12
+ requestId,
13
+ action,
14
+ content,
15
+ submittedBy,
16
+ correlationId,
17
+ traceId,
18
+ priority,
19
+ }) {
11
20
  if (!router?.handlePublish) {
12
- throw new Error('router.handlePublish is required for HITL forwarding');
21
+ throw new Error("router.handlePublish is required for HITL forwarding");
13
22
  }
14
23
  return router.handlePublish({
15
- from: 'hub:hitl',
24
+ from: "hub:hitl",
16
25
  to: requesterAgent,
17
- topic: 'human.response',
26
+ topic: "human.response",
18
27
  priority,
19
28
  ttl_ms: 300000,
20
- payload: { request_id: requestId, action, content, submitted_by: submittedBy },
29
+ payload: {
30
+ request_id: requestId,
31
+ action,
32
+ content,
33
+ submitted_by: submittedBy,
34
+ },
21
35
  correlation_id: correlationId,
22
36
  trace_id: traceId,
23
- message_type: 'human_response',
37
+ message_type: "human_response",
24
38
  });
25
39
  }
26
40
 
@@ -30,24 +44,41 @@ export function createHitlManager(store, router = null) {
30
44
  * 터미널에 알림 출력 후 pending 상태로 저장
31
45
  */
32
46
  requestHumanInput({
33
- requester_agent, kind, prompt, requested_schema = {},
34
- deadline_ms, default_action, channel_preference = 'terminal',
35
- correlation_id, trace_id,
47
+ requester_agent,
48
+ kind,
49
+ prompt,
50
+ requested_schema = {},
51
+ deadline_ms,
52
+ default_action,
53
+ channel_preference = "terminal",
54
+ correlation_id,
55
+ trace_id,
36
56
  }) {
37
57
  const result = store.insertHumanRequest({
38
- requester_agent, kind, prompt, requested_schema,
39
- deadline_ms, default_action,
40
- correlation_id, trace_id,
58
+ requester_agent,
59
+ kind,
60
+ prompt,
61
+ requested_schema,
62
+ deadline_ms,
63
+ default_action,
64
+ correlation_id,
65
+ trace_id,
41
66
  });
42
67
 
43
68
  // 터미널 알림 (stderr — stdout은 MCP 용)
44
- const kindLabel = { captcha: 'CAPTCHA', approval: '승인', credential: '자격증명', choice: '선택', text: '텍스트' };
69
+ const kindLabel = {
70
+ captcha: "CAPTCHA",
71
+ approval: "승인",
72
+ credential: "자격증명",
73
+ choice: "선택",
74
+ text: "텍스트",
75
+ };
45
76
  process.stderr.write(
46
77
  `\n[tfx-hub] 사용자 입력 요청 (${kindLabel[kind] || kind})\n` +
47
- ` 요청자: ${requester_agent}\n` +
48
- ` 내용: ${prompt}\n` +
49
- ` ID: ${result.request_id}\n` +
50
- ` 제한: ${Math.round(deadline_ms / 1000)}초\n\n`,
78
+ ` 요청자: ${requester_agent}\n` +
79
+ ` 내용: ${prompt}\n` +
80
+ ` ID: ${result.request_id}\n` +
81
+ ` 제한: ${Math.round(deadline_ms / 1000)}초\n\n`,
51
82
  );
52
83
 
53
84
  return { ok: true, data: result };
@@ -57,21 +88,45 @@ export function createHitlManager(store, router = null) {
57
88
  * 사용자 입력 응답 제출
58
89
  * 유효성 검증 → 상태 업데이트 → 요청자에게 응답 메시지 전달
59
90
  */
60
- submitHumanInput({ request_id, action, content = null, submitted_by = 'human' }) {
91
+ submitHumanInput({
92
+ request_id,
93
+ action,
94
+ content = null,
95
+ submitted_by = "human",
96
+ }) {
61
97
  // 요청 조회
62
98
  const hr = store.getHumanRequest(request_id);
63
99
  if (!hr) {
64
- return { ok: false, error: { code: 'NOT_FOUND', message: `요청 없음: ${request_id}` } };
100
+ return {
101
+ ok: false,
102
+ error: { code: "NOT_FOUND", message: `요청 없음: ${request_id}` },
103
+ };
65
104
  }
66
- if (hr.state !== 'pending') {
67
- return { ok: false, error: { code: 'ALREADY_HANDLED', message: `이미 처리됨: ${hr.state}` } };
105
+ if (hr.state !== "pending") {
106
+ return {
107
+ ok: false,
108
+ error: {
109
+ code: "ALREADY_HANDLED",
110
+ message: `이미 처리됨: ${hr.state}`,
111
+ },
112
+ };
68
113
  }
69
114
 
70
115
  // 상태 매핑
71
- const stateMap = { accept: 'accepted', decline: 'declined', cancel: 'cancelled' };
116
+ const stateMap = {
117
+ accept: "accepted",
118
+ decline: "declined",
119
+ cancel: "cancelled",
120
+ };
72
121
  const newState = stateMap[action];
73
122
  if (!newState) {
74
- return { ok: false, error: { code: 'INVALID_ACTION', message: `잘못된 action: ${action}` } };
123
+ return {
124
+ ok: false,
125
+ error: {
126
+ code: "INVALID_ACTION",
127
+ message: `잘못된 action: ${action}`,
128
+ },
129
+ };
75
130
  }
76
131
 
77
132
  // DB 업데이트
@@ -79,7 +134,7 @@ export function createHitlManager(store, router = null) {
79
134
 
80
135
  // 요청자에게 응답 메시지 전달
81
136
  let forwardedMessageId = null;
82
- if (action === 'accept' || action === 'decline') {
137
+ if (action === "accept" || action === "decline") {
83
138
  const published = forwardHumanResponse({
84
139
  requesterAgent: hr.requester_agent,
85
140
  requestId: request_id,
@@ -95,7 +150,11 @@ export function createHitlManager(store, router = null) {
95
150
 
96
151
  return {
97
152
  ok: true,
98
- data: { request_id, new_state: newState, forwarded_message_id: forwardedMessageId },
153
+ data: {
154
+ request_id,
155
+ new_state: newState,
156
+ forwarded_message_id: forwardedMessageId,
157
+ },
99
158
  };
100
159
  },
101
160
 
@@ -106,19 +165,19 @@ export function createHitlManager(store, router = null) {
106
165
  checkTimeouts() {
107
166
  const pending = store.getPendingHumanRequests();
108
167
  const now = Date.now();
109
- const expired = pending.filter(hr => hr.deadline_ms <= now);
168
+ const expired = pending.filter((hr) => hr.deadline_ms <= now);
110
169
  if (!expired.length) return 0;
111
170
 
112
171
  const expireRequests = () => {
113
172
  for (const hr of expired) {
114
- store.updateHumanRequest(hr.request_id, 'timed_out', null);
115
- if (hr.default_action === 'timeout_continue') {
173
+ store.updateHumanRequest(hr.request_id, "timed_out", null);
174
+ if (hr.default_action === "timeout_continue") {
116
175
  forwardHumanResponse({
117
176
  requesterAgent: hr.requester_agent,
118
177
  requestId: hr.request_id,
119
- action: 'timeout_continue',
178
+ action: "timeout_continue",
120
179
  content: null,
121
- submittedBy: 'system',
180
+ submittedBy: "system",
122
181
  correlationId: hr.correlation_id,
123
182
  traceId: hr.trace_id,
124
183
  priority: 5,