triflux 10.52.0 → 10.54.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 (137) hide show
  1. package/README.ko.md +11 -16
  2. package/README.md +15 -20
  3. package/adapters/codex/skills/tfx-live/SKILL.md +16 -4
  4. package/bin/tfx-live.mjs +190 -20
  5. package/bin/triflux.mjs +159 -138
  6. package/config/mcp-registry.json +6 -7
  7. package/hooks/codex-session-hook.mjs +1 -17
  8. package/hub/bridge.mjs +4 -25
  9. package/hub/cli-adapter-base.mjs +3 -524
  10. package/hub/lib/hosts-compat.mjs +18 -64
  11. package/hub/lib/private-tmp.mjs +35 -0
  12. package/hub/lib/process-utils.mjs +19 -220
  13. package/hub/lib/prompt-tmp.mjs +6 -24
  14. package/hub/lib/spawn-trace.mjs +112 -5
  15. package/hub/lib/timeout-defaults.mjs +34 -6
  16. package/hub/lib/worker-lifecycle.mjs +3 -0
  17. package/hub/platform.mjs +3 -1
  18. package/hub/team/backend.mjs +10 -3
  19. package/hub/team/claude-transcript.mjs +19 -0
  20. package/hub/team/cli/commands/start/index.mjs +0 -2
  21. package/hub/team/cli/commands/start/start-headless.mjs +2 -0
  22. package/hub/team/codex-queue.mjs +109 -1
  23. package/hub/team/handoff.mjs +1 -1
  24. package/hub/team/headless.mjs +75 -63
  25. package/hub/team/intervention.mjs +6 -4
  26. package/hub/team/nativeProxy.mjs +2 -48
  27. package/hub/team/pane.mjs +31 -18
  28. package/hub/team/psmux.mjs +36 -20
  29. package/hub/team/remote-session.mjs +66 -56
  30. package/hub/team/results-index.mjs +92 -0
  31. package/hub/team/retry-state-machine.mjs +1 -1
  32. package/hub/team/session.mjs +80 -59
  33. package/hub/team/terminal-opener.mjs +5 -19
  34. package/hub/team/tui-viewer.mjs +2 -2
  35. package/hub/team/tui.mjs +0 -6
  36. package/hub/team/uds-orchestrator.mjs +18 -19
  37. package/hub/workers/factory.mjs +1 -10
  38. package/hub/workers/interface.mjs +0 -7
  39. package/hud/constants.mjs +7 -25
  40. package/hud/hud-qos-status.mjs +9 -28
  41. package/hud/providers/claude.mjs +35 -15
  42. package/hud/providers/codex-probe.mjs +2 -31
  43. package/hud/providers/codex.mjs +185 -100
  44. package/hud/renderers.mjs +22 -31
  45. package/hud/utils.mjs +0 -31
  46. package/package.json +4 -11
  47. package/scripts/check-codex-config-stable.mjs +2 -11
  48. package/scripts/config-audit.mjs +0 -2
  49. package/scripts/doctor-diagnose.mjs +31 -77
  50. package/scripts/ensure-codex-hooks.mjs +70 -24
  51. package/scripts/lib/backup-rotation.mjs +60 -0
  52. package/scripts/lib/codex-profile-config.mjs +20 -7
  53. package/scripts/lib/codex-recovery.sh +2 -3
  54. package/scripts/lib/install-blob-history.json +3196 -0
  55. package/scripts/lib/install-retire.mjs +203 -0
  56. package/scripts/lib/legacy-hook-cleanup.mjs +148 -13
  57. package/scripts/lib/legacy-mcp-cleanup.mjs +275 -103
  58. package/scripts/lib/machine-profile.mjs +2 -2
  59. package/scripts/lib/mcp-filter.mjs +1 -42
  60. package/scripts/lib/mcp-guard-engine.mjs +36 -11
  61. package/scripts/lib/mcp-manifest.mjs +0 -40
  62. package/scripts/lib/process-utils.mjs +19 -0
  63. package/scripts/lib/stable-node.mjs +35 -0
  64. package/scripts/lib/test-env.mjs +20 -0
  65. package/scripts/lib/tmr-install.mjs +183 -0
  66. package/scripts/lint-private-info.mjs +137 -0
  67. package/scripts/mcp-check.mjs +24 -40
  68. package/scripts/pack.mjs +29 -44
  69. package/scripts/release/check-packages-mirror.mjs +77 -125
  70. package/scripts/release/lib.mjs +1 -1
  71. package/scripts/release/prepare.mjs +2 -0
  72. package/scripts/release/resolve-auto-release.mjs +11 -0
  73. package/scripts/release/version-manifest.json +7 -1
  74. package/scripts/remote-spawn.mjs +226 -116
  75. package/scripts/session-stale-cleanup.mjs +124 -75
  76. package/scripts/setup.mjs +291 -346
  77. package/scripts/test-lock.mjs +62 -8
  78. package/scripts/tfx-route-post.mjs +30 -4
  79. package/scripts/tfx-route.sh +74 -27
  80. package/scripts/tmp-cleanup.mjs +16 -3
  81. package/skills/tfx-auto/SKILL.md +12 -5
  82. package/skills/tfx-lead/SKILL.md +24 -7
  83. package/skills/tfx-live/SKILL.md +16 -4
  84. package/skills/tfx-remote/SKILL.md +2 -2
  85. package/skills/tfx-research/SKILL.md +10 -7
  86. package/skills/tfx-review/SKILL.md +14 -3
  87. package/skills/tfx-setup/SKILL.md +2 -0
  88. package/skills/tfx-ship/SKILL.md +1 -1
  89. package/CLAUDE.md +0 -210
  90. package/cto/brief.mjs +0 -73
  91. package/cto/collect.mjs +0 -490
  92. package/cto/current.schema.json +0 -148
  93. package/cto/events.mjs +0 -208
  94. package/cto/hygiene.mjs +0 -320
  95. package/cto/index.mjs +0 -46
  96. package/cto/lake-root.mjs +0 -127
  97. package/cto/status.mjs +0 -124
  98. package/docs/assets/demo-multi.gif +0 -0
  99. package/hooks/agy-session-hook.mjs +0 -155
  100. package/hooks/claude-cwd-projection-refresh.mjs +0 -11
  101. package/hooks/hook-orchestrator.mjs +0 -11
  102. package/hooks/lib/resolve-root.mjs +0 -62
  103. package/hub/account-broker.mjs +0 -1221
  104. package/hub/codex-adapter.mjs +0 -302
  105. package/hub/codex-preflight.mjs +0 -219
  106. package/hub/lib/cto-env.mjs +0 -70
  107. package/hub/team/notify.mjs +0 -385
  108. package/hub/team/process-cleanup.mjs +0 -386
  109. package/hub/team/routing.mjs +0 -249
  110. package/hub/team/staleState.mjs +0 -403
  111. package/hub/team/worker-signal.mjs +0 -263
  112. package/hub/team/worker-signal.types.d.ts +0 -52
  113. package/hub/workers/codex-mcp.mjs +0 -853
  114. package/references/codex-plugin-cc-analysis.md +0 -701
  115. package/references/codex-plugin-cc-code-patterns.md +0 -468
  116. package/scripts/__tests__/ensure-codex-hooks.test.mjs +0 -183
  117. package/scripts/__tests__/lint-skills.test.mjs +0 -169
  118. package/scripts/__tests__/mcp-guard-engine-http-headers.test.mjs +0 -223
  119. package/scripts/__tests__/mcp-guard-engine-policy-sync.test.mjs +0 -185
  120. package/scripts/__tests__/mcp-guard-engine-proactive-sync.test.mjs +0 -206
  121. package/scripts/__tests__/mcp-guard-engine-stdio-sync.test.mjs +0 -186
  122. package/scripts/__tests__/mcp-guard-engine-sync-http-headers.test.mjs +0 -270
  123. package/scripts/__tests__/mcp-guard-engine.test.mjs +0 -376
  124. package/scripts/__tests__/release-governance.test.mjs +0 -732
  125. package/scripts/__tests__/release-publish-ancestor.test.mjs +0 -67
  126. package/scripts/__tests__/release-verify-wait.test.mjs +0 -169
  127. package/scripts/__tests__/remote-spawn-transfer.test.mjs +0 -128
  128. package/scripts/__tests__/remote-spawn.test.mjs +0 -98
  129. package/scripts/__tests__/resolve-auto-release.test.mjs +0 -216
  130. package/scripts/__tests__/setup-cleanup-stale-skills.test.mjs +0 -86
  131. package/scripts/__tests__/spawn-trace.test.mjs +0 -269
  132. package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +0 -59
  133. package/scripts/claudemd-sync.mjs +0 -157
  134. package/scripts/ensure-agy-hooks.mjs +0 -163
  135. package/scripts/lib/remote-spawn-transfer.mjs +0 -226
  136. package/scripts/lib/session-presence.mjs +0 -151
  137. package/scripts/mcp-cleanup.ps1 +0 -26
package/README.ko.md CHANGED
@@ -18,10 +18,6 @@
18
18
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-374151?style=flat-square" alt="License: MIT"></a>
19
19
  </p>
20
20
 
21
- <p align="center">
22
- <img alt="triflux demo" src="docs/assets/demo-multi.gif" width="680">
23
- </p>
24
-
25
21
  triflux는 코딩 작업을 Claude, Codex, Antigravity 사이에서 나눠 맡기는 Claude Code 플러그인이자
26
22
  npm CLI다. `/tfx-auto`에 할 일을 한 번 적으면 triflux가 CLI 레인(기본은 Codex)을 고르고, 임의
27
23
  셸 명령 대신 관리된 경로로 실행한다. 필요하면 로컬 병렬 워커, Claude↔Codex 라이브 세션, 원격 호스트로 작업을 나눈다.
@@ -73,8 +69,9 @@ claude plugin install triflux@triflux
73
69
  | --- | --- |
74
70
  | `/tfx-auto` | 구현, 수정, 리뷰, 병렬 작업의 진입점. 동작은 아래 플래그로 정한다. |
75
71
  | `/tfx-live` | Claude↔Codex 라이브 세션. `start`/`ask`/`wait`/`stop`, `peer` 중계, `list-sessions`. |
72
+ | `/tfx-lead` | 여러 Claude, Codex 세션을 지휘하는 리드 역할. 역할별 모델, 지시서, 교차 리뷰, 머지·릴리스 조율. |
76
73
  | `/tfx-remote` | SSH 원격 Claude Code 세션 시작, 조회, 재부착, 메시지 전송, 준비 상태 확인, 모니터링, 종료. |
77
- | `/tfx-setup` | 대화형 설정. 파일 동기화, HUD, Codex 프로필, MCP, 훅 우선순위. |
74
+ | `/tfx-setup` | 설정. 파일 동기화, HUD, Codex와 Antigravity 프로필, MCP. mods는 `tfx setup --mods`로 설치. |
78
75
  | `/tfx-doctor` | 진단과 복구. |
79
76
  | `/tfx-ship` | triflux 릴리즈 절차(메인테이너용). |
80
77
  | `/tfx-wt` | Windows Terminal 탭·패인 조작. `tfx setup`은 Windows에만 설치한다. |
@@ -134,7 +131,6 @@ Codex는 이름 붙은 프로필로 실행한다. 모델 ID는 `~/.codex/<프로
134
131
  | `tfx setup` / `tfx doctor` | 파일·HUD·MCP·프로필 동기화 / 진단과 복구(`--fix`, `--json`) |
135
132
  | `tfx multi` | tmux 기반 로컬 멀티 CLI 팀 |
136
133
  | `tfx mcp` | 관리형 MCP 레지스트리: `list`, `sync`, `add`, `remove` |
137
- | `tfx cto` | 저장소 단위 권위 콘솔: `collect`, `status`, `hygiene`(dry-run) |
138
134
  | `bash ~/.claude/scripts/tfx-route.sh code-reviewer "<지시>"` | 정책에 따라 `codex exec review`로 리뷰 전달 |
139
135
  | `tfx list`, `tfx update`, `tfx version` | 설치된 스킬, 업데이트, 버전 |
140
136
  | `tfx-live` | 라이브 세션 브리지(`/tfx-live` 스킬과 같은 명령) |
@@ -168,11 +164,6 @@ Claude Code Bash 도구의 600초 제한에 걸리지 않는다. 이후 `--job-s
168
164
  다른 경로로 넘어가지 않고 실패한다. 자세한 내용은
169
165
  [`.claude/rules/tfx-machine-profile.md`](.claude/rules/tfx-machine-profile.md).
170
166
 
171
- **CTO lake.** `tfx cto collect`로 `.triflux/lake/`의 저장소 스냅샷을 갱신하고,
172
- `tfx cto status`로 생성 시각과 경과 시간을 확인한다. `tfx cto hygiene --dry-run`는 dry-run 결과를
173
- 보고한다. 트레이와 쓰이지 않는 CTO 운영 명령은 제거하였다
174
- ([ADR-0024](docs/adr/0024-cto-explicit-queries-only.md)). 자동 수집은 기본으로 꺼져 있고 `TFX_CTO_AUTO_COLLECT=1` 로 켠다([ADR-0018](docs/_archive/adr/0018-cto-auto-behaviors-opt-in.md)).
175
-
176
167
  **원격 호스트.** `/tfx-remote`는 `~/.config/triflux/hosts.json`
177
168
  (Windows는 `%APPDATA%\triflux\hosts.json`)에서 호스트를 읽는다. 세션 시작은
178
169
  `remote-spawn.mjs`의 `--host <host> --prompt "<요청>"` 옵션을 사용한다.
@@ -183,18 +174,22 @@ Claude Code Bash 도구의 600초 제한에 걸리지 않는다. 이후 `--job-s
183
174
  ```mermaid
184
175
  graph TD
185
176
  User([Claude Code 프롬프트 / 셸]) --> Skills["/tfx-auto · /tfx-live · /tfx-remote"]
177
+ User --> Lead["/tfx-lead"]
186
178
  User --> CLI[tfx CLI]
179
+ Lead -->|"지시서, 교차 리뷰, 머지"| Live
187
180
  Skills --> Route[tfx-route.sh]
188
181
  Skills --> Live[tfx-live]
189
182
  CLI --> Team["tfx multi"]
190
183
  Route --> Codex[Codex CLI]
191
184
  Route --> Agy[Antigravity agy]
192
185
  Route --> Claude[Claude Code]
193
- Team --> Route
194
- Live -->|UDS 또는 tmux| Sessions[Claude / Codex TUI 세션]
195
- Route --> UI["HUD · claude agents 패널"]
196
- Team --> UI
197
- CLI --> Lake[(".triflux/lake (tfx cto)")]
186
+ Team -->|headless 워커| Route
187
+ Team --> Index[("결과 색인: tfx-headless/*.results.json")]
188
+ Team --> Rows["claude agents 행"]
189
+ Rows -->|Enter| Room["워커 tmux 방"]
190
+ Live -->|"codex queue, tmux 폴백"| CodexTUI[Codex TUI 세션]
191
+ Live -->|"UDS 또는 tmux"| ClaudeTUI[Claude Code 세션]
192
+ Route --> HUD[HUD]
198
193
  ```
199
194
 
200
195
  패키지 구성과 실행 경로는 [ARCHITECTURE.md](ARCHITECTURE.md), 문서 지도는
package/README.md CHANGED
@@ -18,10 +18,6 @@
18
18
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-374151?style=flat-square" alt="License: MIT"></a>
19
19
  </p>
20
20
 
21
- <p align="center">
22
- <img alt="triflux demo" src="docs/assets/demo-multi.gif" width="680">
23
- </p>
24
-
25
21
  triflux is a Claude Code plugin and npm CLI that routes coding work across Claude, Codex,
26
22
  and Antigravity. You describe the task once with `/tfx-auto`; triflux picks the CLI lane
27
23
  (Codex by default), runs it through managed routes instead of ad-hoc shell commands, and
@@ -75,8 +71,9 @@ Skills you invoke directly:
75
71
  | --- | --- |
76
72
  | `/tfx-auto` | Front door for implementing, fixing, reviewing, and parallel work. Behavior is set by flags (below). |
77
73
  | `/tfx-live` | Live Claude↔Codex sessions: `start`/`ask`/`wait`/`stop`, `peer` relay, `list-sessions`. |
78
- | `/tfx-remote` | SSH 원격 Claude Code 세션 시작, 조회, 재부착, 메시지 전송, 준비 상태 확인, 모니터링, 종료. |
79
- | `/tfx-setup` | Interactive setup: file sync, HUD, Codex profiles, MCP, hook priority. |
74
+ | `/tfx-lead` | Lead role for running several Claude/Codex sessions: roles and models, briefs, cross review, merge and release coordination. |
75
+ | `/tfx-remote` | Remote Claude Code sessions over SSH: start, list, reattach, send, readiness probe, monitor, stop. |
76
+ | `/tfx-setup` | Setup: file sync, HUD, Codex and Antigravity profiles, MCP. `tfx setup --mods` installs mods. |
80
77
  | `/tfx-doctor` | Diagnose and repair. |
81
78
  | `/tfx-ship` | triflux release flow (maintainers). |
82
79
  | `/tfx-wt` | Windows Terminal tabs and panes. `tfx setup` installs it on Windows only. |
@@ -136,7 +133,6 @@ escalation chain: [`.claude/rules/tfx-escalation-chain.md`](.claude/rules/tfx-es
136
133
  | `tfx setup` / `tfx doctor` | Sync files, HUD, MCP, profiles / diagnose and repair (`--fix`, `--json`) |
137
134
  | `tfx multi` | Local multi-CLI team in tmux |
138
135
  | `tfx mcp` | Managed MCP registry: `list`, `sync`, `add`, `remove` |
139
- | `tfx cto` | Repo-local authority console: `collect`, `status`, `hygiene` (dry-run) |
140
136
  | `bash ~/.claude/scripts/tfx-route.sh code-reviewer "<instruction>"` | Send review to Codex (`codex exec review` via policy) |
141
137
  | `tfx list`, `tfx update`, `tfx version` | Installed skills, update, version |
142
138
  | `tfx-live` | Live session bridge (same as the `/tfx-live` skill) |
@@ -170,32 +166,31 @@ tmux pane the worker runs in.
170
166
  removes a CLI from routing; if no allowed CLI is available the route fails instead of silently
171
167
  falling back. Details: [`.claude/rules/tfx-machine-profile.md`](.claude/rules/tfx-machine-profile.md).
172
168
 
173
- **CTO lake.** Run `tfx cto collect` to refresh the repo snapshot in `.triflux/lake/`, then
174
- `tfx cto status` to inspect it with its generation time and age. `tfx cto hygiene --dry-run` reports
175
- dry-run findings. The tray and unused CTO operating commands were removed
176
- ([ADR-0024](docs/adr/0024-cto-explicit-queries-only.md)). Automatic collection stays off unless `TFX_CTO_AUTO_COLLECT=1` is set ([ADR-0018](docs/_archive/adr/0018-cto-auto-behaviors-opt-in.md)).
177
-
178
- **원격 호스트.** `/tfx-remote`는 `~/.config/triflux/hosts.json`에서 호스트를 읽는다
179
- (Windows: `%APPDATA%\triflux\hosts.json`). 세션 시작은 `remote-spawn.mjs`의
180
- `--host <host> --prompt "<요청>"` 옵션을 사용한다. [실행 옵션](skills/tfx-remote/SKILL.md)을 따른다.
169
+ **Remote hosts.** `/tfx-remote` reads hosts from `~/.config/triflux/hosts.json`
170
+ (Windows: `%APPDATA%\triflux\hosts.json`). Start a session with `remote-spawn.mjs`
171
+ `--host <host> --prompt "<request>"`. Options: [skills/tfx-remote/SKILL.md](skills/tfx-remote/SKILL.md).
181
172
 
182
173
  ## Architecture
183
174
 
184
175
  ```mermaid
185
176
  graph TD
186
177
  User([Claude Code prompt / shell]) --> Skills["/tfx-auto · /tfx-live · /tfx-remote"]
178
+ User --> Lead["/tfx-lead"]
187
179
  User --> CLI[tfx CLI]
180
+ Lead -->|"briefs, cross review, merge"| Live
188
181
  Skills --> Route[tfx-route.sh]
189
182
  Skills --> Live[tfx-live]
190
183
  CLI --> Team["tfx multi"]
191
184
  Route --> Codex[Codex CLI]
192
185
  Route --> Agy[Antigravity agy]
193
186
  Route --> Claude[Claude Code]
194
- Team --> Route
195
- Live -->|UDS or tmux| Sessions[Claude / Codex TUI sessions]
196
- Route --> UI["HUD · claude agents panel"]
197
- Team --> UI
198
- CLI --> Lake[(".triflux/lake (tfx cto)")]
187
+ Team -->|headless workers| Route
188
+ Team --> Index[("results index: tfx-headless/*.results.json")]
189
+ Team --> Rows["claude agents rows"]
190
+ Rows -->|Enter| Room["worker tmux room"]
191
+ Live -->|"codex queue, tmux fallback"| CodexTUI[Codex TUI sessions]
192
+ Live -->|"UDS or tmux"| ClaudeTUI[Claude Code sessions]
193
+ Route --> HUD[HUD]
199
194
  ```
200
195
 
201
196
  Package layout and execution paths: [ARCHITECTURE.md](ARCHITECTURE.md). Documentation map:
@@ -3,7 +3,7 @@ name: tfx-live
3
3
  description: >
4
4
  Claude·Codex 세션을 실시간으로 생성·질의·대기·압축·종료하거나,
5
5
  tmux/daemon UDS 연결과 peer 중계를 운영할 때 사용한다.
6
- argument-hint: "<start|ask|wait|compact|stop|rename|interrupt|probe|list-sessions|peer|converse|goal-driven> ..."
6
+ argument-hint: "<start|ask|wait|queue|compact|stop|rename|interrupt|probe|list-sessions|peer|converse|goal-driven> ..."
7
7
  ---
8
8
 
9
9
  # tfx-live
@@ -29,7 +29,7 @@ tfx-live ask --cli codex --session cx1 --prompt "현재 변경사항을 요약
29
29
  tfx-live stop --cli codex --session cx1
30
30
  ```
31
31
 
32
- 새 세션 이름은 `<월>.<일> <주제>`로 짓는다. `start --name`을 생략하면 자동으로 만들고 `nameGenerated: true`를 보고한다. `--resume` 또는 `--resume-last`는 `--name`을 명시한 경우에만 이름을 적용한다. 승계 세션에는 기존 이름 뒤에 ` 2`, 이후 ` 3`을 붙인다. 이름 적용을 요청한 경우 결과의 `nameApplied: false`는 적용 실패를 뜻한다. Codex `start`는 `/rename` 제출을 입력창이 빌 때까지 확인하고, 이름으로 찾은 thread UUID를 `threadId`로 돌려주며 tmux 세션 옵션 `@tfx_codex_thread`에 남긴다. 기존 Codex UDS thread는 `tfx-live rename --cli codex --transport uds --thread ID --name "<이름>"`으로 이름을 바꾼다.
32
+ 새 세션 이름은 `<월>.<일> <주제>`로 짓는다. `start --name`을 생략하면 자동으로 만들고 `nameGenerated: true`를 보고한다. `--resume` 또는 `--resume-last`는 `--name`을 명시한 경우에만 이름을 적용한다. 승계 세션에는 기존 이름 뒤에 ` 2`, 이후 ` 3`을 붙인다. 이름 적용을 요청한 경우 결과의 `nameApplied: false`는 적용 실패를 뜻한다. Codex `start`는 `/rename` 제출을 입력창이 빌 때까지 확인하고, 이름으로 찾은 thread UUID를 `threadId`로 돌려주며 tmux 세션 옵션 `@tfx_codex_thread`에 남긴다. 로컬 Codex `--resume-last`는 Codex가 고른 thread를 사후에 찾는다. 띄운 뒤 공유 app-server daemon에 로드된 같은 cwd의 thread 중 rollout에 그 시각 이후의 resume 기록(`thread_settings_applied`)이 생긴 것이 정확히 하나이고 1.5초 뒤에도 같을 때만 `threadId`로 쓴다. 이 확인은 5초 안에 끝난다. TUI가 daemon을 쓰지 않거나 같은 cwd에서 동시에 resume이 일어나면 `null`이다. 기존 Codex UDS thread는 `tfx-live rename --cli codex --transport uds --thread ID --name "<이름>"`으로 이름을 바꾼다.
33
33
 
34
34
  ## 리드 운영
35
35
 
@@ -51,11 +51,19 @@ tfx-live stop --cli codex --session cx1
51
51
  1. 기본은 `codex queue --thread <UUID>`다. 입력창을 건드리지 않고, 바쁘면 쌓였다가 앞 턴이 끝난 직후 순서대로 들어간다. 유휴 TUI는 약 20초 주기로 큐를 가져간다.
52
52
  2. 첫 줄 머리말은 `[from <보낸 세션 이름>] [tfx-live req=<id>]`다. 이름은 `--from`, `$TFX_LIVE_FROM`, Claude 세션 기록, Codex `session_index` 순으로 찾고 없으면 머리말 없이 표식만 붙인다. Codex TUI는 Claude처럼 접어 보여 주지 않고 사용자 입력으로 그대로 표시한다.
53
53
  3. thread는 `--thread UUID`, `start`가 남긴 `@tfx_codex_thread`, Codex 세션 레지스트리, pane 프로세스의 `resume <UUID>` 인자, pane cwd와 같은 rollout 하나 순으로 찾는다. cwd 대응은 그 cwd의 Codex TUI가 하나일 때만 쓰고 exec 워커와 서브에이전트 thread는 뺀다. 이름으로는 보내지 않는다(`codex queue`의 이름 조회가 실패한다).
54
- 4. tmux 직접 입력은 queue를 못 쓸 때만 쓴다: 원격 호스트, thread를 못 찾음(`resume --last`로 띄운 세션, 같은 cwd의 TUI 여럿 포함), `codex queue` 오류(0.160 미만 포함). 결과에 `transport: "tmux"`, `transportRequested: "queue"`, `fallbackReason`이 남는다. 시간 초과처럼 queue가 이미 쌓았을 수도 있는 오류는 `status: "unknown"`으로 끝내고 재전송하지 않는다.
54
+ 4. tmux 직접 입력은 queue를 못 쓸 때만 쓴다: 원격 호스트, thread를 못 찾음(`start` 밖에서 `resume --last`로 띄운 세션, `start --resume-last`의 `threadId: null`, 같은 cwd의 TUI 여럿 포함), `codex queue` 오류(0.160 미만 포함). 결과에 `transport: "tmux"`, `transportRequested: "queue"`, `fallbackReason`이 남는다. 시간 초과처럼 queue가 이미 쌓았을 수도 있는 오류는 `status: "unknown"`으로 끝내고 재전송하지 않는다.
55
55
  5. 슬래시 명령(`/rename`, `/compact`, `/new`)과 interrupt(Escape)는 tmux 직접 입력 전용이다. queue로 보낸 슬래시 명령은 실행되지 않고 일반 텍스트로 모델에 들어간다. `/명령`으로 시작하는 프롬프트(절대 경로는 제외)와 `--if-busy fail|interrupt`는 tmux로 보내고 `transportReason`을 남긴다. 슬래시 명령에는 표식을 붙이지 않으므로 `wait`로 추적할 수 없다.
56
56
 
57
57
  결과의 `status`는 `queued`(쌓임), `working`(TUI가 가져가 턴 시작), `completed`, `failed`로 나뉘고 `delivered`, `deliveredAt`, `turnId`가 배달을 구분한다. queue는 바쁜 세션에도 쌓으므로 `--if-busy wait`는 의미가 없다.
58
58
 
59
+ ```bash
60
+ tfx-live queue --cli codex --session cx1
61
+ tfx-live queue --cli codex --session cx1 --request-id <requestId> --delete all
62
+ tfx-live queue --cli codex --thread <UUID> --delete <queuedMessageId>
63
+ ```
64
+
65
+ `queue`는 TUI가 아직 가져가지 않은 항목을 보고 지운다. Codex app-server의 실험 API `thread/queue/list`·`thread/queue/delete`를 stdio로 호출하며 큐 저장소 파일은 건드리지 않는다. 목록 항목은 `id`(ask 결과의 `queuedMessageId`와 같다), `requestId`, `text`다. 삭제 결과의 `missing`은 이미 TUI가 가져갔거나 없는 항목이다. 유휴 TUI는 쌓인 항목을 곧 가져가므로 지울 수 있는 것은 사실상 바쁜 세션과 닫힌 세션의 항목이다. 가져간 뒤의 처리 여부는 `wait`로 확인한다.
66
+
59
67
  ### 컨텍스트 판단
60
68
 
61
69
  분모는 공식 모델 창이고 실행 한도가 더 작으면 그 한도 아래에서 운영한다. 아래 비율은 공식 권고가 아닌 **[추론] 운영값**이다. 모델 문서가 바뀌면 한도도 갱신한다.
@@ -90,6 +98,8 @@ tfx-live stop --session cl1
90
98
  ```bash
91
99
  tfx-live ask --cli claude --transport uds --short <8hex> --prompt "진행 상황을 보고해줘" --no-wait
92
100
  tfx-live wait --cli claude --short <8hex> --request-id <requestId> --timeout 120 --poll-interval 500
101
+ tfx-live ask --cli claude --session cl1 --prompt "진행 상황을 보고해줘" --no-wait
102
+ tfx-live wait --cli claude --session cl1 --request-id <requestId> --timeout 120
93
103
  tfx-live ask --cli codex --session cx1 --prompt "진행 상황을 보고해줘" --no-wait
94
104
  tfx-live wait --cli codex --session cx1 --request-id <requestId> --timeout 120
95
105
  ```
@@ -98,11 +108,13 @@ tfx-live wait --cli codex --session cx1 --request-id <requestId> --timeout 120
98
108
 
99
109
  `wait`는 Claude transcript에서 표식을 user 메시지, `queue-operation`의 `enqueue` content, queued command attachment에서 찾는다. 표식이 소비된 턴이 끝난 뒤 마지막 assistant 텍스트를 응답으로 쓴다. 표식 없는 `wait`는 최신 user turn을 본다. `isSidechain`과 합성 API 오류 메시지는 정상 응답에서 제외한다. 요청을 소비한 턴에서 합성 오류가 나타나면 오류 문구와 `status: "failed"`를 반환한다. daemon의 `idle`만으로 완료를 단정하지 않는다. timeout은 `status: "working"`, `timedOut: true`, `done: false`다.
100
110
 
111
+ tmux로 띄운 대화형 Claude는 `wait --session <tmux 대상>`을 쓴다. pane에 붙은 Claude 세션 기록(`~/.claude/sessions`)에서 sessionId를 찾아 같은 transcript 규칙으로 판정하고, daemon의 idle 대신 세션 기록의 `status: "idle"`을 쓴다. transcript는 처음 찾은 것을 끝까지 쓰므로 대기 중 `/clear`로 바뀐 세션은 따라가지 않는다. 로컬 전용이다.
112
+
101
113
  Codex `wait`는 `--session` 또는 `--thread UUID`의 rollout에서 표식이 든 user 메시지의 `turn_id`를 찾고, 같은 턴의 `task_complete.last_agent_message`를 응답으로 쓴다. `turn_aborted`는 `status: "failed"`다. 표식이 아직 없으면 `status: "queued"`, `timedOut: true`다. `--timeout 0`은 한 번만 확인한다.
102
114
 
103
115
  명시한 `--config-dir`은 정확히 일치하는 daemon만 선택한다. `CLAUDE_CONFIG_DIR`도 해당 env 디렉터리로 제한한다. stale endpoint 복구는 같은 source configDir 안에서만 재시도한다. attach 전 예외는 `inputSent: false`이므로 `auto`의 tmux fallback이 가능하다. attach 뒤 전송 여부가 불명확하면 `status: "unknown"`으로 보고하고 재전송하지 않는다.
104
116
 
105
- 직접 `tmux capture-pane`과 `tmux send-keys`를 쓸 때는 전송·pane 진단에 한정한다. Claude pane에 `C-c`를 보내지 않는다. 자동 세션 탐색에는 `tmr ls --json`을 사용한다.
117
+ 직접 `tmux capture-pane`과 `tmux send-keys`를 쓸 때는 전송·pane 진단에 한정한다. Claude pane에 `C-c`를 보내지 않는다. 자동 세션 탐색에는 `tmr ls --json`으로 pane 별 에이전트, 상태, 클라이언트 수, 마지막 활동을 본다. tmr 이 없으면 아래 `tfx-live list-sessions`와 `tmux ls -F '#{session_name} #{session_attached}'`를 쓴다. tmr 은 macOS(arm64) 에서 `tfx setup` 이 설치를 묻는다.
106
118
 
107
119
  ## 세션 탐색과 연결
108
120
 
package/bin/tfx-live.mjs CHANGED
@@ -8,9 +8,10 @@ import {
8
8
  readdir,
9
9
  readFile,
10
10
  rm,
11
+ stat,
11
12
  writeFile,
12
13
  } from "node:fs/promises";
13
- import { homedir, tmpdir } from "node:os";
14
+ import { homedir } from "node:os";
14
15
  import { join as pathJoin, resolve as pathResolve } from "node:path";
15
16
  import { fileURLToPath } from "node:url";
16
17
  import { promisify } from "node:util";
@@ -19,17 +20,21 @@ import {
19
20
  readCodexSessionRecords,
20
21
  registryDir,
21
22
  } from "../hub/lib/codex-session-registry.mjs";
23
+ import { privateTmpDir } from "../hub/lib/private-tmp.mjs";
22
24
  import { resolveHardCeilingMs } from "../hub/lib/worker-lifecycle.mjs";
23
25
  import { exposeLiveSession } from "../hub/team/agents-row.mjs";
24
26
  import {
27
+ claudeWaitVerdict,
25
28
  findClaudeTranscript,
26
29
  readClaudeTranscript,
27
30
  } from "../hub/team/claude-transcript.mjs";
28
31
  import {
29
32
  codexThreadIdByName,
30
33
  countCodexTuiInCwd,
34
+ deleteCodexQueueItems,
31
35
  findCodexThreadByCwd,
32
36
  isCodexThreadId,
37
+ listCodexQueue,
33
38
  queueCodexMessage,
34
39
  resolveSenderName,
35
40
  waitCodexRequest,
@@ -43,6 +48,7 @@ import {
43
48
  import {
44
49
  CONTEXT_THRESHOLDS,
45
50
  contextGuard,
51
+ findCodexRollout,
46
52
  modelContext,
47
53
  readCodexContext,
48
54
  } from "../hub/team/session-context.mjs";
@@ -94,8 +100,10 @@ function usage(command) {
94
100
  " tfx-live ask --transport uds|auto (--short SHORT | --session-id ID) --prompt TEXT [--config-dir DIR] [--bridge ABS] [--session NAME (auto fallback)] [--timeout 60]",
95
101
  " ask options: --no-wait --no-relay-tag --warn-context-pct N --max-context-pct N (0 disables; Claude 60/90, Codex 15/22).",
96
102
  " tfx-live compact --cli claude --session NAME [--instructions TEXT] [--if-busy fail|wait] [--timeout 60]",
97
- " tfx-live wait --cli claude (--short SHORT | --session-id ID) [--request-id ID] [--config-dir DIR] [--bridge ABS] [--timeout 60] [--poll-interval 1500]",
103
+ " tfx-live wait --cli claude (--short SHORT | --session-id ID | --session NAME[:WINDOW.PANE]) [--request-id ID] [--config-dir DIR] [--bridge ABS] [--timeout 60] [--poll-interval 1500]",
98
104
  " tfx-live wait --cli codex (--session NAME[:WINDOW.PANE] | --thread UUID) --request-id ID [--timeout 60] [--poll-interval 1500]",
105
+ " tfx-live queue --cli codex (--session NAME[:WINDOW.PANE] | --thread UUID) [--request-id ID] [--delete QUEUED_ID|all]",
106
+ " queue lists or deletes items the TUI has not picked up yet (official app-server thread/queue API); missing means already delivered or gone.",
99
107
  " tfx-live rename --cli codex --transport uds --thread ID --name NAME [--codex-socket PATH|default]",
100
108
  " transport: auto is the default for Claude when --short/--session-id is present; otherwise tmux. bridge path: --bridge > $TFX_BRIDGE > $TFX_REPO_ROOT/hub/bridge.mjs > bundled Triflux hub/bridge.mjs.",
101
109
  " tfx-live interrupt --session NAME [--cli codex|claude] [--transport tmux|uds|auto] [--short SHORT | --session-id ID] [--config-dir DIR] [--bridge ABS] [--timeout 5]",
@@ -1925,8 +1933,9 @@ async function callBridgeVerb(bridgePath, verb, payload, timeoutMs) {
1925
1933
  let payloadFile = null;
1926
1934
  let args;
1927
1935
  if (json.length > PAYLOAD_FILE_THRESHOLD) {
1936
+ // 페이로드에 프롬프트가 들어 있어 사용자별 0700 디렉터리에 쓴다.
1928
1937
  payloadFile = pathJoin(
1929
- tmpdir(),
1938
+ privateTmpDir("tfx-live"),
1930
1939
  `codex-live-payload-${process.pid}-${Date.now()}.json`,
1931
1940
  );
1932
1941
  await writeFile(payloadFile, json, "utf8");
@@ -2061,6 +2070,76 @@ async function resolveAskTransport(adapter, opts, deps = {}) {
2061
2070
  };
2062
2071
  }
2063
2072
 
2073
+ // 공유 app-server daemon 에 로드된 thread 중 cwd 가 같은 것. daemon 이 없거나 늦으면 빈 목록.
2074
+ async function loadedCodexThreadIds(cwd, timeoutMs) {
2075
+ try {
2076
+ const { listCodexAppServerThreads } = await import(
2077
+ "../hub/team/uds-orchestrator.mjs"
2078
+ );
2079
+ const threads = await listCodexAppServerThreads({
2080
+ socketPath: resolveCodexDaemonSocket("default"),
2081
+ cwd,
2082
+ timeoutMs,
2083
+ });
2084
+ return threads.map((thread) => thread.threadId);
2085
+ } catch {
2086
+ return [];
2087
+ }
2088
+ }
2089
+
2090
+ const ROLLOUT_TAIL_BYTES = 256 * 1024;
2091
+
2092
+ // thread_settings_applied 는 resume 과 설정 변경 때 쓰인다. 끝부분이 sinceMs 이후를 다 덮지
2093
+ // 못하면 "unknown" 이다.
2094
+ async function resumedSince(rollout, sinceMs) {
2095
+ const info = await stat(rollout).catch(() => null);
2096
+ if (!info || info.mtimeMs < sinceMs) return "no";
2097
+ const handle = await open(rollout, "r");
2098
+ let text;
2099
+ try {
2100
+ const length = Math.min(info.size, ROLLOUT_TAIL_BYTES);
2101
+ const { buffer } = await handle.read(
2102
+ Buffer.alloc(length),
2103
+ 0,
2104
+ length,
2105
+ info.size - length,
2106
+ );
2107
+ text = buffer.toString("utf8");
2108
+ } finally {
2109
+ await handle.close();
2110
+ }
2111
+ let covered = info.size <= ROLLOUT_TAIL_BYTES;
2112
+ for (const line of text.split("\n")) {
2113
+ let entry;
2114
+ try {
2115
+ entry = JSON.parse(line);
2116
+ } catch {
2117
+ continue;
2118
+ }
2119
+ const at = Date.parse(entry.timestamp);
2120
+ if (at < sinceMs) covered = true;
2121
+ else if (entry.payload?.type === "thread_settings_applied")
2122
+ return "resumed";
2123
+ }
2124
+ return covered ? "no" : "unknown";
2125
+ }
2126
+
2127
+ // resume --last 는 pane 인자에 UUID 가 남지 않아 Codex 가 실제로 연 thread 를 사후에 찾는다.
2128
+ // 띄운 뒤 기록이 생긴 thread 가 정확히 하나이고 나머지 후보가 모두 확인됐을 때만 인정한다.
2129
+ async function resumedCodexThread(
2130
+ { cwd, launchedAtMs, timeoutMs },
2131
+ { loadedIds = loadedCodexThreadIds, findRollout = findCodexRollout } = {},
2132
+ ) {
2133
+ const matches = [];
2134
+ for (const id of await loadedIds(cwd, timeoutMs)) {
2135
+ const rollout = isCodexThreadId(id) && (await findRollout(id));
2136
+ const state = rollout ? await resumedSince(rollout, launchedAtMs) : "no";
2137
+ if (state === "unknown") return null;
2138
+ if (state === "resumed") matches.push(id);
2139
+ }
2140
+ return matches.length === 1 ? matches[0] : null;
2141
+ }
2142
+
2064
2143
  async function doStart(adapter, opts) {
2065
2144
  const {
2066
2145
  session,
@@ -2093,6 +2172,8 @@ async function doStart(adapter, opts) {
2093
2172
  if (resumed && name && adapter.cli === "claude")
2094
2173
  launchKeys[0] += ` ${shellQuote(["-n", name])}`;
2095
2174
  const resumeTarget = resume ?? (resumeLast ? "last" : null);
2175
+ const trackResumeLast = resumeLast && adapter.cli === "codex" && !remote;
2176
+ const launchedAtMs = Date.now();
2096
2177
 
2097
2178
  // 분리 세션 기본 80x24 에서는 긴 입력이 화면 높이를 넘으므로 넓게 만든다. 붙으면 클라이언트 크기를 따른다.
2098
2179
  await runTmux(remote, [
@@ -2129,6 +2210,23 @@ async function doStart(adapter, opts) {
2129
2210
  let nameApplied = Boolean(name) && adapter.cli === "claude" && ready;
2130
2211
  let threadId =
2131
2212
  adapter.cli === "codex" && isCodexThreadId(resume) ? resume : null;
2213
+ // 우리 TUI 의 resume 기록이 늦게 반영되면 남의 기록만 보일 수 있어, 1.5초 뒤에도 같은 후보가
2214
+ // 유일할 때만 확정한다. 5초 안에 확인하지 못하면 null 로 둔다.
2215
+ const threadDeadline = Date.now() + 5000;
2216
+ let candidate = null;
2217
+ while (trackResumeLast && ready && !threadId) {
2218
+ const timeoutMs = threadDeadline - Date.now();
2219
+ if (timeoutMs <= 0) break;
2220
+ const found = await resumedCodexThread({
2221
+ cwd: cwd ?? process.cwd(),
2222
+ launchedAtMs,
2223
+ timeoutMs,
2224
+ });
2225
+ if (found && found === candidate) threadId = found;
2226
+ candidate = found;
2227
+ if (!threadId)
2228
+ await sleep(Math.min(found ? 1500 : 500, threadDeadline - Date.now()));
2229
+ }
2132
2230
  if (name && adapter.cli === "codex" && ready) {
2133
2231
  try {
2134
2232
  const command = `/rename ${name}`;
@@ -3272,19 +3370,24 @@ async function ask(flags) {
3272
3370
  printJson(await doAsk(adapter, askOpts(flags, adapter)));
3273
3371
  }
3274
3372
 
3373
+ async function codexThreadFromFlags(flags) {
3374
+ if (flags.thread) {
3375
+ if (!isCodexThreadId(flags.thread))
3376
+ throw new Error("--thread must be a Codex session UUID");
3377
+ return { threadId: flags.thread, threadSource: "flag" };
3378
+ }
3379
+ const thread = await resolveCodexTmuxThread(
3380
+ splitTmuxTarget(requireFlag(flags, "session")).target,
3381
+ );
3382
+ if (!thread.threadId)
3383
+ throw new Error(`Codex thread unavailable: ${thread.reason}`);
3384
+ return thread;
3385
+ }
3386
+
3275
3387
  async function waitCodex(flags) {
3276
3388
  if (flags.remote) throw new Error("Codex wait supports local sessions only");
3277
3389
  const requestId = requireFlag(flags, "request-id");
3278
- let thread = { threadId: flags.thread, threadSource: "flag" };
3279
- if (flags.thread && !isCodexThreadId(flags.thread))
3280
- throw new Error("--thread must be a Codex session UUID");
3281
- if (!flags.thread) {
3282
- thread = await resolveCodexTmuxThread(
3283
- splitTmuxTarget(requireFlag(flags, "session")).target,
3284
- );
3285
- if (!thread.threadId)
3286
- throw new Error(`Codex thread unavailable: ${thread.reason}`);
3287
- }
3390
+ const thread = await codexThreadFromFlags(flags);
3288
3391
  printJson({
3289
3392
  ...(await waitCodexRequest({
3290
3393
  threadId: thread.threadId,
@@ -3296,12 +3399,74 @@ async function waitCodex(flags) {
3296
3399
  });
3297
3400
  }
3298
3401
 
3402
+ // tmux 대화형 Claude 는 daemon 이 없어 pane 의 세션 레코드 status 와 transcript 로 판정한다.
3403
+ async function waitClaudeTmux(flags) {
3404
+ if (flags.remote)
3405
+ throw new Error("Claude tmux wait supports local sessions only");
3406
+ const session = splitTmuxTarget(flags.session).target;
3407
+ const requestId = flags["request-id"] ?? null;
3408
+ const timeoutMs = secondsFlag(flags, "timeout", DEFAULT_ANSWER_TIMEOUT_MS);
3409
+ const pollIntervalMs = msFlag(
3410
+ flags,
3411
+ "poll-interval",
3412
+ DEFAULT_POLL_INTERVAL_MS,
3413
+ );
3414
+ const deadline = Date.now() + timeoutMs;
3415
+ let transcriptPath = null;
3416
+ for (;;) {
3417
+ const target = await claudeTmuxTarget(session);
3418
+ if (!target)
3419
+ throw new Error(`no live Claude session in tmux target ${session}`);
3420
+ // 처음 찾은 transcript 를 고정해 /clear 로 바뀐 세션을 따라가지 않는다.
3421
+ transcriptPath ??= await claudeTmuxTranscript(
3422
+ session,
3423
+ flags["config-dir"],
3424
+ target,
3425
+ );
3426
+ const transcript = await readClaudeTranscript(transcriptPath, {
3427
+ requestId,
3428
+ });
3429
+ const result = {
3430
+ ok: true,
3431
+ status: transcript?.userSeen ? "working" : "submitted",
3432
+ done: false,
3433
+ timedOut: false,
3434
+ response: transcript?.response || "",
3435
+ ...(transcript?.context ?? modelContext("claude", null)),
3436
+ requestId,
3437
+ target,
3438
+ };
3439
+ const verdict = claudeWaitVerdict(transcript, target.status === "idle");
3440
+ if (verdict) return { ...result, ...verdict };
3441
+ if (Date.now() >= deadline) return { ...result, timedOut: true };
3442
+ await sleep(Math.min(pollIntervalMs, deadline - Date.now()));
3443
+ }
3444
+ }
3445
+
3446
+ // 배달 전 항목은 공식 app-server 큐 API 로만 보고 지운다. 큐 저장소 파일은 건드리지 않는다.
3447
+ async function queue(flags) {
3448
+ if ((flags.cli ?? "codex") !== "codex" || flags.remote)
3449
+ throw new Error("queue supports local --cli codex only");
3450
+ const { threadId, threadSource } = await codexThreadFromFlags(flags);
3451
+ const requestId = flags["request-id"];
3452
+ const items = (await listCodexQueue(threadId)).filter(
3453
+ (item) => !requestId || item.requestId === requestId,
3454
+ );
3455
+ const result = { ok: true, cli: "codex", threadId, threadSource };
3456
+ if (!flags.delete) return printJson({ ...result, items });
3457
+ const ids =
3458
+ flags.delete === "all" ? items.map((item) => item.id) : [flags.delete];
3459
+ printJson({ ...result, ...(await deleteCodexQueueItems(threadId, ids)) });
3460
+ }
3461
+
3299
3462
  async function wait(flags) {
3300
3463
  if (flags.cli === "codex") return waitCodex(flags);
3301
3464
  if ((flags.cli ?? "claude") !== "claude")
3302
3465
  throw new Error("wait supports --cli claude or codex");
3466
+ if (flags.session && !flags.short && !flags["session-id"])
3467
+ return printJson(await waitClaudeTmux(flags));
3303
3468
  if (!flags.short && !flags["session-id"])
3304
- throw new Error("wait requires --short or --session-id");
3469
+ throw new Error("wait requires --short, --session-id or --session");
3305
3470
  if (flags.remote || (flags.transport && flags.transport !== "uds"))
3306
3471
  throw new Error("wait supports local Claude UDS only");
3307
3472
  const timeoutMs = secondsFlag(flags, "timeout", DEFAULT_ANSWER_TIMEOUT_MS);
@@ -3354,7 +3519,7 @@ async function stop(flags) {
3354
3519
  printJson(await doStop(adapter, stopOpts(flags)));
3355
3520
  }
3356
3521
 
3357
- async function claudeTmuxTranscript(session, configDir) {
3522
+ async function claudeTmuxTarget(session) {
3358
3523
  const { stdout: paneId } = await runTmux(null, [
3359
3524
  "display-message",
3360
3525
  "-p",
@@ -3363,9 +3528,13 @@ async function claudeTmuxTranscript(session, configDir) {
3363
3528
  "#{pane_id}",
3364
3529
  ]);
3365
3530
  const discovery = await discoverClaudeTmuxSessions();
3366
- const target = discovery.sessions.find(
3367
- (entry) => entry.paneId === paneId.trim(),
3531
+ return (
3532
+ discovery.sessions.find((entry) => entry.paneId === paneId.trim()) ?? null
3368
3533
  );
3534
+ }
3535
+
3536
+ async function claudeTmuxTranscript(session, configDir, target) {
3537
+ target ??= await claudeTmuxTarget(session);
3369
3538
  return findClaudeTranscript({
3370
3539
  configDir:
3371
3540
  configDir ||
@@ -3931,13 +4100,11 @@ function peerArtifactPaths(sessionA, sessionB) {
3931
4100
  const preferredDir =
3932
4101
  process.env.TFX_LIVE_ARTIFACT_DIR ??
3933
4102
  pathJoin(homedir(), ".claude", "cache", "triflux", "tfx-live", "peer-runs");
3934
- const fallbackDir = pathJoin(tmpdir(), "triflux-live");
3935
4103
  let artifactDir = preferredDir;
3936
4104
  try {
3937
4105
  mkdirSync(artifactDir, { recursive: true });
3938
4106
  } catch {
3939
- artifactDir = fallbackDir;
3940
- mkdirSync(artifactDir, { recursive: true });
4107
+ artifactDir = privateTmpDir("tfx-live");
3941
4108
  }
3942
4109
  const safe = (value) =>
3943
4110
  String(value)
@@ -4389,6 +4556,8 @@ async function main() {
4389
4556
  await ask(flags);
4390
4557
  } else if (command === "wait") {
4391
4558
  await wait(flags);
4559
+ } else if (command === "queue") {
4560
+ await queue(flags);
4392
4561
  } else if (command === "compact") {
4393
4562
  await compact(flags);
4394
4563
  } else if (command === "rename") {
@@ -4437,6 +4606,7 @@ export {
4437
4606
  peerSideBaseOpts,
4438
4607
  resolveAskTransport,
4439
4608
  resolveCodexDaemonSocket,
4609
+ resumedCodexThread,
4440
4610
  splitTmuxTarget,
4441
4611
  stopOpts,
4442
4612
  tmuxBufferName,