triflux 10.50.2 → 10.52.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.
- package/CLAUDE.md +57 -41
- package/README.ko.md +41 -45
- package/README.md +41 -45
- package/adapters/codex/skills/tfx-live/SKILL.md +25 -11
- package/bin/tfx-live.mjs +341 -104
- package/bin/triflux.mjs +147 -2624
- package/config/mcp-registry.json +3 -12
- package/cto/brief.mjs +0 -20
- package/cto/collect.mjs +1 -200
- package/cto/current.schema.json +2 -23
- package/cto/hygiene.mjs +2 -61
- package/cto/lake-root.mjs +0 -56
- package/cto/status.mjs +5 -187
- package/hooks/agy-session-hook.mjs +11 -56
- package/hooks/codex-session-hook.mjs +11 -101
- package/hub/bridge.mjs +65 -1250
- package/hub/cli-adapter-base.mjs +12 -90
- package/hub/codex-adapter.mjs +0 -1
- package/hub/codex-preflight.mjs +5 -28
- package/hub/lib/process-utils.mjs +1 -156
- package/hub/lib/spawn-trace.mjs +3 -2
- package/hub/lib/timeout-defaults.mjs +0 -1
- package/hub/team/agent-map.json +0 -1
- package/hub/team/agents-row-attach.py +120 -0
- package/hub/team/agents-row.mjs +200 -0
- package/hub/team/ansi.mjs +1 -35
- package/hub/team/backend.mjs +9 -145
- package/hub/team/claude-daemon-control.mjs +1 -2
- package/hub/team/claude-session-projection.mjs +5 -73
- package/hub/team/cli/commands/attach.mjs +1 -17
- package/hub/team/cli/commands/control.mjs +12 -38
- package/hub/team/cli/commands/debug.mjs +2 -39
- package/hub/team/cli/commands/focus.mjs +5 -31
- package/hub/team/cli/commands/interrupt.mjs +6 -25
- package/hub/team/cli/commands/kill.mjs +1 -30
- package/hub/team/cli/commands/list.mjs +0 -16
- package/hub/team/cli/commands/send.mjs +6 -26
- package/hub/team/cli/commands/start/index.mjs +42 -133
- package/hub/team/cli/commands/start/parse-args.mjs +15 -40
- package/hub/team/cli/commands/start/start-headless.mjs +138 -105
- package/hub/team/cli/commands/start/start-mux.mjs +2 -7
- package/hub/team/cli/commands/status.mjs +2 -57
- package/hub/team/cli/commands/stop.mjs +14 -19
- package/hub/team/cli/help.mjs +4 -5
- package/hub/team/cli/manifest.mjs +5 -1
- package/hub/team/cli/render.mjs +0 -12
- package/hub/team/cli/services/runtime-mode.mjs +27 -98
- package/hub/team/cli/services/state-store.mjs +5 -20
- package/hub/team/codex-queue.mjs +385 -0
- package/hub/team/handoff.mjs +3 -34
- package/hub/team/headless.mjs +108 -1296
- package/hub/team/orchestrator.mjs +4 -30
- package/hub/team/pane.mjs +6 -28
- package/hub/team/psmux.mjs +3 -34
- package/hub/team/remote-session.mjs +1 -2
- package/hub/team/session-context.mjs +1 -1
- package/hub/team/session.mjs +7 -91
- package/hub/team/tui-core.mjs +1 -1
- package/hub/team/tui-viewer.mjs +11 -58
- package/hub/team/tui.mjs +8 -306
- package/hub/team/uds-orchestrator.mjs +2 -580
- package/hub/team/wt-manager.mjs +2 -2
- package/hub/workers/factory.mjs +6 -84
- package/hub/workers/interface.mjs +1 -7
- package/hub/workers/lib/jsonrpc-core.mjs +11 -0
- package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -8
- package/hud/cli-policy.mjs +0 -22
- package/hud/constants.mjs +13 -26
- package/hud/hud-qos-status.mjs +42 -54
- package/hud/providers/gemini.mjs +125 -20
- package/hud/renderers.mjs +101 -122
- package/hud/utils.mjs +10 -6
- package/package.json +7 -12
- package/scripts/__tests__/mcp-guard-engine-http-headers.test.mjs +6 -6
- package/scripts/__tests__/mcp-guard-engine-policy-sync.test.mjs +1 -4
- package/scripts/__tests__/mcp-guard-engine-proactive-sync.test.mjs +16 -21
- package/scripts/__tests__/mcp-guard-engine-stdio-sync.test.mjs +1 -1
- package/scripts/__tests__/mcp-guard-engine-sync-http-headers.test.mjs +2 -2
- package/scripts/__tests__/mcp-guard-engine.test.mjs +56 -63
- package/scripts/__tests__/spawn-trace.test.mjs +16 -0
- package/scripts/check-codex-config-stable.mjs +4 -37
- package/scripts/claude-login-detect.mjs +0 -1
- package/scripts/claudemd-sync.mjs +1 -28
- package/scripts/lib/agent-route-policy.mjs +143 -62
- package/scripts/lib/cli-claude.mjs +0 -14
- package/scripts/lib/env-probe.mjs +2 -131
- package/scripts/lib/gemini-profiles.mjs +2 -69
- package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
- package/scripts/lib/machine-profile.mjs +3 -17
- package/scripts/lib/mcp-filter.mjs +109 -53
- package/scripts/lib/mcp-guard-engine.mjs +40 -98
- package/scripts/lib/mcp-health.mjs +2 -2
- package/scripts/lib/mcp-server-catalog.mjs +2 -2
- package/scripts/lib/psmux-info.mjs +11 -59
- package/scripts/lib/session-presence.mjs +2 -100
- package/scripts/lib/skill-template.mjs +0 -245
- package/scripts/pack.mjs +8 -83
- package/scripts/preflight-cache.mjs +5 -18
- package/scripts/release/check-packages-mirror.mjs +2 -8
- package/scripts/release/version-manifest.json +9 -1
- package/scripts/setup.mjs +100 -417
- package/scripts/test-lock.mjs +0 -1
- package/scripts/tfx-route-post.mjs +5 -69
- package/scripts/tfx-route-worker.mjs +4 -79
- package/scripts/tfx-route.sh +169 -1495
- package/scripts/wt-cli.mjs +1 -2
- package/skills/tfx-auto/SKILL.md +123 -741
- package/skills/tfx-auto/references/consensus-schema.md +161 -0
- package/skills/tfx-auto/references/tmux-observation.md +97 -0
- package/skills/tfx-doctor/SKILL.md +5 -6
- package/skills/tfx-lead/SKILL.md +195 -0
- package/skills/tfx-live/SKILL.md +25 -11
- package/skills/tfx-remote/SKILL.md +24 -87
- package/skills/tfx-research/SKILL.md +13 -20
- package/skills/tfx-review/SKILL.md +33 -35
- package/skills/tfx-setup/SKILL.md +1 -1
- package/skills/tfx-ship/SKILL.md +6 -27
- package/skills/tfx-wt/SKILL.md +34 -68
- package/bin/tfx-doctor-tui.mjs +0 -7
- package/bin/tfx-setup-tui.mjs +0 -7
- package/config/routing-policy.json +0 -124
- package/docs/assets/consensus-flow.svg +0 -125
- package/docs/assets/deep-vs-light.svg +0 -193
- package/docs/assets/demo-dark.gif +0 -0
- package/docs/assets/demo-light.gif +0 -0
- package/docs/assets/demo-psmux.gif +0 -0
- package/hub/adaptive-diagnostic.mjs +0 -350
- package/hub/adaptive-inject.mjs +0 -224
- package/hub/adaptive-memory.mjs +0 -277
- package/hub/adaptive.mjs +0 -172
- package/hub/assign-callbacks.mjs +0 -170
- package/hub/delegator/contracts.mjs +0 -38
- package/hub/delegator/index.mjs +0 -14
- package/hub/delegator/schema/delegator-tools.schema.json +0 -250
- package/hub/delegator/service.mjs +0 -336
- package/hub/delegator/tool-definitions.mjs +0 -35
- package/hub/dynamic-routing-engine.mjs +0 -511
- package/hub/hitl.mjs +0 -202
- package/hub/hub-lifecycle.mjs +0 -248
- package/hub/lib/known-errors.json +0 -72
- package/hub/lib/memory-store.mjs +0 -1001
- package/hub/lib/phase-manager.mjs +0 -493
- package/hub/lib/risk-tier.mjs +0 -53
- package/hub/lib/ssh-command.mjs +0 -231
- package/hub/lib/staged-file-detect.mjs +0 -43
- package/hub/lib/state-snapshot.mjs +0 -293
- package/hub/lib/tfx-route-args.mjs +0 -353
- package/hub/lib/trace-recorder.mjs +0 -153
- package/hub/lib/uuidv7.mjs +0 -45
- package/hub/memory-doctor.mjs +0 -640
- package/hub/middleware/quota-middleware.mjs +0 -70
- package/hub/middleware/request-logger.mjs +0 -191
- package/hub/paths.mjs +0 -24
- package/hub/pipe.mjs +0 -825
- package/hub/pipeline/index.mjs +0 -163
- package/hub/pipeline/state.mjs +0 -191
- package/hub/pipeline/transitions.mjs +0 -131
- package/hub/promote-penalties.mjs +0 -138
- package/hub/public/dashboard.html +0 -416
- package/hub/reflexion.mjs +0 -290
- package/hub/role-contract.mjs +0 -314
- package/hub/router.mjs +0 -2026
- package/hub/routing-snapshot.mjs +0 -261
- package/hub/schema.sql +0 -209
- package/hub/server.mjs +0 -3048
- package/hub/session-fingerprint.mjs +0 -418
- package/hub/state.mjs +0 -300
- package/hub/store-adapter.mjs +0 -182
- package/hub/store.mjs +0 -1736
- package/hub/team/build-worker-prompt.mjs +0 -92
- package/hub/team/check-mcp-hub.mjs +0 -49
- package/hub/team/claude-native-bridge.mjs +0 -987
- package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
- package/hub/team/cli/commands/start/start-wt.mjs +0 -86
- package/hub/team/cli/services/hub-client.mjs +0 -264
- package/hub/team/cli/services/native-control.mjs +0 -145
- package/hub/team/codex-review.mjs +0 -520
- package/hub/team/conductor-mesh-bridge.mjs +0 -122
- package/hub/team/conductor-registry.mjs +0 -121
- package/hub/team/conductor.mjs +0 -1529
- package/hub/team/consensus-meta.mjs +0 -176
- package/hub/team/cto-auto-collect.mjs +0 -115
- package/hub/team/cto-pull.mjs +0 -31
- package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
- package/hub/team/dashboard.mjs +0 -322
- package/hub/team/event-log.mjs +0 -84
- package/hub/team/execution-mode.mjs +0 -324
- package/hub/team/extract-completion-payload.mjs +0 -110
- package/hub/team/headless-bridge-session.mjs +0 -17
- package/hub/team/health-probe.mjs +0 -435
- package/hub/team/interactive-native-launcher.mjs +0 -119
- package/hub/team/interactive-native-worker.mjs +0 -80
- package/hub/team/interactive-tui-transport.mjs +0 -479
- package/hub/team/launcher-template.mjs +0 -144
- package/hub/team/lead-control.mjs +0 -191
- package/hub/team/mcp-selector.mjs +0 -145
- package/hub/team/native-supervisor.mjs +0 -460
- package/hub/team/native.mjs +0 -727
- package/hub/team/recovery-store.mjs +0 -149
- package/hub/team/remote-probe.mjs +0 -292
- package/hub/team/remote-watcher.mjs +0 -506
- package/hub/team/runtime-strategy.mjs +0 -200
- package/hub/team/schemas/worker-completion.json +0 -39
- package/hub/team/sentinel-capture.mjs +0 -127
- package/hub/team/session-sync.mjs +0 -316
- package/hub/team/swarm-cli.mjs +0 -390
- package/hub/team/swarm-hypervisor.mjs +0 -2206
- package/hub/team/swarm-intent.mjs +0 -216
- package/hub/team/swarm-locks.mjs +0 -335
- package/hub/team/swarm-planner.mjs +0 -371
- package/hub/team/swarm-preflight.mjs +0 -366
- package/hub/team/swarm-reconciler.mjs +0 -242
- package/hub/team/synapse-cli.mjs +0 -458
- package/hub/team/synapse-http.mjs +0 -311
- package/hub/team/synapse-registry.mjs +0 -618
- package/hub/team/tui-remote-adapter.mjs +0 -377
- package/hub/team/tui-synapse.mjs +0 -223
- package/hub/team/worker-completion-validator.mjs +0 -79
- package/hub/team/worker-sandbox.mjs +0 -125
- package/hub/team/worktree-lifecycle.mjs +0 -639
- package/hub/team/wt-templates.json +0 -43
- package/hub/team-bridge.mjs +0 -27
- package/hub/tools.mjs +0 -900
- package/hub/workers/antigravity-route-worker.mjs +0 -179
- package/hub/workers/codex-app-server-worker.mjs +0 -1067
- package/hub/workers/delegator-mcp.mjs +0 -1098
- package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
- package/mesh/index.mjs +0 -69
- package/mesh/mesh-budget.mjs +0 -137
- package/mesh/mesh-heartbeat.mjs +0 -100
- package/mesh/mesh-protocol.mjs +0 -96
- package/mesh/mesh-queue.mjs +0 -163
- package/mesh/mesh-registry.mjs +0 -78
- package/mesh/mesh-router.mjs +0 -77
- package/scripts/__tests__/skill-surface.test.mjs +0 -93
- package/scripts/cache-doctor.mjs +0 -156
- package/scripts/cache-warmup.mjs +0 -630
- package/scripts/completions/tfx.bash +0 -74
- package/scripts/completions/tfx.fish +0 -57
- package/scripts/completions/tfx.zsh +0 -98
- package/scripts/demo.mjs +0 -197
- package/scripts/doctor-dynamic-routing.mjs +0 -87
- package/scripts/hub-ensure.mjs +0 -842
- package/scripts/hub-watchdog.mjs +0 -90
- package/scripts/lib/claudemd-scanner.mjs +0 -223
- package/scripts/lib/context.mjs +0 -67
- package/scripts/lib/dynamic-route-cli.mjs +0 -107
- package/scripts/lib/handoff.mjs +0 -187
- package/scripts/lib/logger.mjs +0 -122
- package/scripts/lib/stealth-fetch.mjs +0 -176
- package/scripts/notion-read.mjs +0 -686
- package/scripts/snapshot-codex-state.mjs +0 -37
- package/scripts/snapshot-gemini-state.mjs +0 -37
- package/scripts/sync-codex-auth.mjs +0 -71
- package/scripts/sync-hub-mcp-settings.mjs +0 -697
- package/skills/_templates/base.md +0 -4
- package/skills/_templates/deep.md +0 -6
- package/skills/shared/telemetry-segment.md +0 -6
- package/tui/core.mjs +0 -266
- package/tui/doctor.mjs +0 -375
- package/tui/monitor-data.mjs +0 -152
- package/tui/monitor.mjs +0 -318
- package/tui/setup.mjs +0 -506
package/bin/tfx-live.mjs
CHANGED
|
@@ -20,10 +20,20 @@ import {
|
|
|
20
20
|
registryDir,
|
|
21
21
|
} from "../hub/lib/codex-session-registry.mjs";
|
|
22
22
|
import { resolveHardCeilingMs } from "../hub/lib/worker-lifecycle.mjs";
|
|
23
|
+
import { exposeLiveSession } from "../hub/team/agents-row.mjs";
|
|
23
24
|
import {
|
|
24
25
|
findClaudeTranscript,
|
|
25
26
|
readClaudeTranscript,
|
|
26
27
|
} from "../hub/team/claude-transcript.mjs";
|
|
28
|
+
import {
|
|
29
|
+
codexThreadIdByName,
|
|
30
|
+
countCodexTuiInCwd,
|
|
31
|
+
findCodexThreadByCwd,
|
|
32
|
+
isCodexThreadId,
|
|
33
|
+
queueCodexMessage,
|
|
34
|
+
resolveSenderName,
|
|
35
|
+
waitCodexRequest,
|
|
36
|
+
} from "../hub/team/codex-queue.mjs";
|
|
27
37
|
import {
|
|
28
38
|
escapePwshSingleQuoted as escapeRemotePwshSingleQuoted,
|
|
29
39
|
probeRemoteEnv as probeRemoteHostEnv,
|
|
@@ -58,7 +68,7 @@ const BRIDGE_TIMEOUT_BUFFER_MS = 15_000;
|
|
|
58
68
|
// Above this size an argv-passed --payload risks E2BIG on some platforms;
|
|
59
69
|
// spill the JSON to a temp file and pass --payload-file instead.
|
|
60
70
|
const PAYLOAD_FILE_THRESHOLD = 96 * 1024;
|
|
61
|
-
const VALID_TRANSPORTS = ["tmux", "uds", "auto"];
|
|
71
|
+
const VALID_TRANSPORTS = ["tmux", "uds", "auto", "queue"];
|
|
62
72
|
const BOOLEAN_FLAGS = new Set([
|
|
63
73
|
"json",
|
|
64
74
|
"attach-a",
|
|
@@ -78,11 +88,14 @@ function usage(command) {
|
|
|
78
88
|
"Usage:",
|
|
79
89
|
" tfx-live start --session NAME [--name NAME] [--cli codex|claude] [--model ID] [--effort TIER] [--cwd DIR] [--remote HOST] [--resume ID] [--resume-last 1] [--ready-timeout 30] [--poll-interval 1500]",
|
|
80
90
|
" tfx-live ask --session NAME[:WINDOW.PANE] --prompt TEXT [--cli codex|claude] [--if-busy wait|fail|interrupt] [--busy-timeout 60] [--timeout 60] [--remote HOST] [--settle 1500] [--poll-interval 1500]",
|
|
91
|
+
" tfx-live ask --cli codex [--transport queue|tmux] (--session NAME[:WINDOW.PANE] | --thread UUID) --prompt TEXT [--from NAME] [--timeout 60] [--no-wait]",
|
|
92
|
+
" Codex ask defaults to `codex queue` (queued, delivered when the TUI picks it up); tmux paste is the reported fallback and the path for slash commands.",
|
|
81
93
|
" tfx-live ask --cli codex --transport uds --thread ID|auto --prompt TEXT [--codex-socket PATH|default] [--cwd DIR] [--if-busy wait|fail|steer] [--busy-timeout 60] [--timeout 60] [--max-turn SECONDS]",
|
|
82
94
|
" tfx-live ask --transport uds|auto (--short SHORT | --session-id ID) --prompt TEXT [--config-dir DIR] [--bridge ABS] [--session NAME (auto fallback)] [--timeout 60]",
|
|
83
95
|
" ask options: --no-wait --no-relay-tag --warn-context-pct N --max-context-pct N (0 disables; Claude 60/90, Codex 15/22).",
|
|
84
96
|
" tfx-live compact --cli claude --session NAME [--instructions TEXT] [--if-busy fail|wait] [--timeout 60]",
|
|
85
97
|
" tfx-live wait --cli claude (--short SHORT | --session-id ID) [--request-id ID] [--config-dir DIR] [--bridge ABS] [--timeout 60] [--poll-interval 1500]",
|
|
98
|
+
" tfx-live wait --cli codex (--session NAME[:WINDOW.PANE] | --thread UUID) --request-id ID [--timeout 60] [--poll-interval 1500]",
|
|
86
99
|
" tfx-live rename --cli codex --transport uds --thread ID --name NAME [--codex-socket PATH|default]",
|
|
87
100
|
" 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.",
|
|
88
101
|
" tfx-live interrupt --session NAME [--cli codex|claude] [--transport tmux|uds|auto] [--short SHORT | --session-id ID] [--config-dir DIR] [--bridge ABS] [--timeout 5]",
|
|
@@ -94,9 +107,7 @@ function usage(command) {
|
|
|
94
107
|
" tfx-live converse --session NAME --prompts-file PATH [--cli codex|claude] [--remote HOST] [--cwd DIR] [--timeout 60] [--settle 1500]",
|
|
95
108
|
" tfx-live goal-driven --session NAME --goal TEXT [--cli codex|claude] [--remote HOST] [--cwd DIR] [--timeout 60] [--settle 1500] [--max-rounds 8] [--done-token DONE]",
|
|
96
109
|
" tfx-live peer [--cli-a codex] [--cli-b claude] [--model-a ID] [--model-b ID] [--effort-a TIER] [--effort-b TIER] [--session-a NAME[:WINDOW.PANE]] [--session-b NAME[:WINDOW.PANE]] [--attach-a] [--attach-b] [--transport-a tmux|uds|auto] [--transport-b tmux|uds|auto] [--short-a SHORT] [--short-b SHORT] [--session-id-a ID] [--session-id-b ID] [--thread-a ID|auto] [--thread-b ID|auto] [--codex-socket-a PATH|default] [--codex-socket-b PATH|default] [--bridge ABS] [--remote HOST] [--cwd DIR] [--if-busy wait|fail] [--if-busy-a POLICY] [--if-busy-b POLICY] [--busy-timeout 60] [--max-turn SECONDS] [--rounds 4] [--mode counting|freeform] [--seed TEXT] [--timeout 60]",
|
|
97
|
-
" tfx-live orchestrate --task TEXT [--mode peer|codex-led|claude-led] [--codex-transport exec|app-server-uds] [--codex-socket PATH|default] [--cwd DIR] [--timeout 120]",
|
|
98
110
|
" Codex TUI may use a shared app-server daemon. UDS ask resumes the thread to receive answer events; sending to an active thread with --if-busy steer merges input into that turn.",
|
|
99
|
-
" Runs the Claude(UDS)+Codex orchestration engine. --codex-transport app-server-uds drives a real `codex app-server` over WebSocket-over-UDS (experimental); default exec keeps the codex stdio one-shot path.",
|
|
100
111
|
];
|
|
101
112
|
if (!command) return lines.join("\n");
|
|
102
113
|
const selected = lines.filter(
|
|
@@ -1129,7 +1140,12 @@ function promptComposerNeedle(prompt) {
|
|
|
1129
1140
|
.split(/\r?\n/)
|
|
1130
1141
|
.map((line) => line.trim())
|
|
1131
1142
|
.find(Boolean);
|
|
1132
|
-
|
|
1143
|
+
// 발신자 머리말이 길어도 요청별로 가려지도록 표식을 바늘로 쓴다.
|
|
1144
|
+
const tag = firstContentLine?.match(/\[tfx-live req=[^\]]+\]/)?.[0];
|
|
1145
|
+
return (tag ?? firstContentLine ?? "").slice(
|
|
1146
|
+
0,
|
|
1147
|
+
PROMPT_COMPOSER_NEEDLE_LENGTH,
|
|
1148
|
+
);
|
|
1133
1149
|
}
|
|
1134
1150
|
|
|
1135
1151
|
function composerLineMatchesPrompt(line, prompt) {
|
|
@@ -1547,6 +1563,28 @@ async function dismissCodexUpdatePrompt(remote, session) {
|
|
|
1547
1563
|
return { dismissed: false, raw };
|
|
1548
1564
|
}
|
|
1549
1565
|
|
|
1566
|
+
function isCodexDaemonSettingsPrompt(text) {
|
|
1567
|
+
return /Background server has incompatible feature settings/.test(
|
|
1568
|
+
String(text),
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// 공유 daemon 을 다시 띄우면 다른 클라이언트 설정이 바뀌므로 이번 실행만 daemon 없이 간다.
|
|
1573
|
+
async function dismissCodexDaemonSettingsPrompt(remote, session) {
|
|
1574
|
+
let raw = await captureVisible(remote, session);
|
|
1575
|
+
if (!isCodexDaemonSettingsPrompt(raw)) return { dismissed: false, raw };
|
|
1576
|
+
for (let iteration = 0; iteration < 4; iteration += 1) {
|
|
1577
|
+
if (/Run without daemon/.test(selectedLine(raw))) {
|
|
1578
|
+
await runTmux(remote, ["send-keys", "-t", session, "Enter"]);
|
|
1579
|
+
return { dismissed: true, raw };
|
|
1580
|
+
}
|
|
1581
|
+
await runTmux(remote, ["send-keys", "-t", session, "Up"]);
|
|
1582
|
+
await sleep(200);
|
|
1583
|
+
raw = await captureVisible(remote, session);
|
|
1584
|
+
}
|
|
1585
|
+
return { dismissed: false, raw };
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1550
1588
|
async function dismissClaudeTrustPrompt(remote, session) {
|
|
1551
1589
|
let raw = await captureVisible(remote, session);
|
|
1552
1590
|
if (!isClaudeTrustPrompt(raw)) {
|
|
@@ -1664,6 +1702,11 @@ const ADAPTERS = {
|
|
|
1664
1702
|
isPresent: isUpdatePrompt,
|
|
1665
1703
|
dismiss: dismissCodexUpdatePrompt,
|
|
1666
1704
|
},
|
|
1705
|
+
{
|
|
1706
|
+
name: "daemon-settings",
|
|
1707
|
+
isPresent: isCodexDaemonSettingsPrompt,
|
|
1708
|
+
dismiss: dismissCodexDaemonSettingsPrompt,
|
|
1709
|
+
},
|
|
1667
1710
|
],
|
|
1668
1711
|
},
|
|
1669
1712
|
claude: {
|
|
@@ -2051,11 +2094,16 @@ async function doStart(adapter, opts) {
|
|
|
2051
2094
|
launchKeys[0] += ` ${shellQuote(["-n", name])}`;
|
|
2052
2095
|
const resumeTarget = resume ?? (resumeLast ? "last" : null);
|
|
2053
2096
|
|
|
2097
|
+
// 분리 세션 기본 80x24 에서는 긴 입력이 화면 높이를 넘으므로 넓게 만든다. 붙으면 클라이언트 크기를 따른다.
|
|
2054
2098
|
await runTmux(remote, [
|
|
2055
2099
|
"new-session",
|
|
2056
2100
|
"-d",
|
|
2057
2101
|
"-s",
|
|
2058
2102
|
session,
|
|
2103
|
+
"-x",
|
|
2104
|
+
"240",
|
|
2105
|
+
"-y",
|
|
2106
|
+
"60",
|
|
2059
2107
|
...(cwd ? ["-c", cwd] : []),
|
|
2060
2108
|
]);
|
|
2061
2109
|
// Default tmux history-limit (2000) is far too small for long tool-heavy
|
|
@@ -2079,33 +2127,38 @@ async function doStart(adapter, opts) {
|
|
|
2079
2127
|
pollIntervalMs,
|
|
2080
2128
|
);
|
|
2081
2129
|
let nameApplied = Boolean(name) && adapter.cli === "claude" && ready;
|
|
2130
|
+
let threadId =
|
|
2131
|
+
adapter.cli === "codex" && isCodexThreadId(resume) ? resume : null;
|
|
2082
2132
|
if (name && adapter.cli === "codex" && ready) {
|
|
2083
2133
|
try {
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
discovery.sessions?.some(
|
|
2098
|
-
(entry) =>
|
|
2099
|
-
entry.session === session &&
|
|
2100
|
-
entry.panes.some((pane) => pane.name === name),
|
|
2101
|
-
) === true;
|
|
2102
|
-
if (nameApplied || remote) break;
|
|
2134
|
+
const command = `/rename ${name}`;
|
|
2135
|
+
await runTmux(remote, ["send-keys", "-t", session, "-l", "--", command]);
|
|
2136
|
+
await submitComposerLine(remote, session, command);
|
|
2137
|
+
const deadline = Date.now() + Math.min(readyTimeoutMs, 5000);
|
|
2138
|
+
while (!remote && Date.now() < deadline) {
|
|
2139
|
+
const named = await codexThreadIdByName(name, {
|
|
2140
|
+
sinceMs: now.getTime() - 1000,
|
|
2141
|
+
});
|
|
2142
|
+
if (named) {
|
|
2143
|
+
threadId = named;
|
|
2144
|
+
nameApplied = true;
|
|
2145
|
+
break;
|
|
2146
|
+
}
|
|
2103
2147
|
await sleep(Math.min(pollIntervalMs, 200));
|
|
2104
|
-
}
|
|
2148
|
+
}
|
|
2105
2149
|
} catch {
|
|
2106
2150
|
nameApplied = false;
|
|
2107
2151
|
}
|
|
2108
2152
|
}
|
|
2153
|
+
// ask 의 queue 전송이 레지스트리 없이도 thread 를 찾도록 세션에 남긴다.
|
|
2154
|
+
if (threadId && !remote)
|
|
2155
|
+
await runTmux(null, [
|
|
2156
|
+
"set-option",
|
|
2157
|
+
"-t",
|
|
2158
|
+
session,
|
|
2159
|
+
"@tfx_codex_thread",
|
|
2160
|
+
threadId,
|
|
2161
|
+
]).catch(() => {});
|
|
2109
2162
|
return addLoginGuard(adapter, raw, {
|
|
2110
2163
|
cli: adapter.cli,
|
|
2111
2164
|
session,
|
|
@@ -2116,16 +2169,37 @@ async function doStart(adapter, opts) {
|
|
|
2116
2169
|
name,
|
|
2117
2170
|
nameGenerated: !resumed && opts.name === undefined,
|
|
2118
2171
|
nameApplied,
|
|
2172
|
+
...(adapter.cli === "codex" ? { threadId } : {}),
|
|
2119
2173
|
...(name && adapter.cli === "codex" && !nameApplied
|
|
2120
2174
|
? {
|
|
2121
2175
|
nameWarning:
|
|
2122
|
-
"name update could not be confirmed from
|
|
2176
|
+
"name update could not be confirmed from Codex session_index",
|
|
2123
2177
|
}
|
|
2124
2178
|
: {}),
|
|
2179
|
+
...(await exposeLiveSession({
|
|
2180
|
+
cli: adapter.cli,
|
|
2181
|
+
session,
|
|
2182
|
+
name,
|
|
2183
|
+
cwd,
|
|
2184
|
+
remote,
|
|
2185
|
+
ready,
|
|
2186
|
+
})),
|
|
2125
2187
|
raw,
|
|
2126
2188
|
});
|
|
2127
2189
|
}
|
|
2128
2190
|
|
|
2191
|
+
// 빠른 입력 직후의 Enter 는 Codex 입력창이 버릴 수 있어 입력창이 빌 때까지 다시 누른다.
|
|
2192
|
+
async function submitComposerLine(remote, session, line) {
|
|
2193
|
+
await sleep(300);
|
|
2194
|
+
for (let attempt = 1; attempt <= PROMPT_SUBMIT_MAX_ATTEMPTS; attempt += 1) {
|
|
2195
|
+
await runTmux(remote, ["send-keys", "-t", session, "Enter"]);
|
|
2196
|
+
await sleep(300);
|
|
2197
|
+
if (!composerShowsPrompt(await captureVisible(remote, session), line))
|
|
2198
|
+
return true;
|
|
2199
|
+
}
|
|
2200
|
+
return false;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2129
2203
|
async function waitForTmuxIdle(adapter, opts) {
|
|
2130
2204
|
const { session, remote, pollIntervalMs, busyTimeoutMs, ifBusy } = opts;
|
|
2131
2205
|
if (!["wait", "fail", "interrupt"].includes(ifBusy)) {
|
|
@@ -2337,14 +2411,26 @@ async function doAskViaTmux(adapter, opts) {
|
|
|
2337
2411
|
async function doAsk(adapter, opts) {
|
|
2338
2412
|
const requestId = opts.requestId ?? randomBytes(6).toString("hex");
|
|
2339
2413
|
const tag = `[tfx-live req=${requestId}]`;
|
|
2414
|
+
// Codex 는 접힌 발신자 표시가 없어 첫 줄 머리말로 보낸 세션을 드러낸다.
|
|
2415
|
+
// 슬래시 명령은 첫 글자가 / 여야 실행되므로 표식을 붙이지 않는다.
|
|
2416
|
+
const bare =
|
|
2417
|
+
opts.noRelayTag || (adapter.cli === "codex" && isSlashCommand(opts.prompt));
|
|
2418
|
+
const from =
|
|
2419
|
+
adapter.cli === "codex" && !bare
|
|
2420
|
+
? (opts.from ?? (await resolveSenderName()))
|
|
2421
|
+
: null;
|
|
2422
|
+
const header = from ? `[from ${from}] ${tag}` : tag;
|
|
2340
2423
|
const prompt =
|
|
2341
|
-
|
|
2424
|
+
bare || opts.prompt.split("\n", 1)[0].includes(tag)
|
|
2342
2425
|
? opts.prompt
|
|
2343
|
-
: `${
|
|
2426
|
+
: `${header}\n${opts.prompt}`;
|
|
2344
2427
|
try {
|
|
2345
2428
|
return {
|
|
2346
2429
|
...(await dispatchAsk(adapter, { ...opts, prompt, requestId })),
|
|
2347
2430
|
requestId,
|
|
2431
|
+
...(opts.transportReason
|
|
2432
|
+
? { transportReason: opts.transportReason }
|
|
2433
|
+
: {}),
|
|
2348
2434
|
};
|
|
2349
2435
|
} catch (error) {
|
|
2350
2436
|
error.requestId = requestId;
|
|
@@ -2357,6 +2443,7 @@ async function dispatchAsk(adapter, opts) {
|
|
|
2357
2443
|
if (transport === "tmux") {
|
|
2358
2444
|
return doAskViaTmux(adapter, opts);
|
|
2359
2445
|
}
|
|
2446
|
+
if (transport === "queue") return doAskViaQueue(adapter, opts);
|
|
2360
2447
|
|
|
2361
2448
|
if (adapter.cli === "codex" && transport === "uds") {
|
|
2362
2449
|
if (opts.remote) return doAskViaRemoteLive(adapter, opts);
|
|
@@ -2407,6 +2494,153 @@ async function dispatchAsk(adapter, opts) {
|
|
|
2407
2494
|
return doAskAuto(adapter, opts);
|
|
2408
2495
|
}
|
|
2409
2496
|
|
|
2497
|
+
async function codexPaneArgs(panePid) {
|
|
2498
|
+
if (!/^\d+$/.test(String(panePid ?? ""))) return null;
|
|
2499
|
+
try {
|
|
2500
|
+
const { stdout } = await execFileAsync(
|
|
2501
|
+
"ps",
|
|
2502
|
+
["-ax", "-o", "pid=,ppid=,args="],
|
|
2503
|
+
{
|
|
2504
|
+
timeout: 5000,
|
|
2505
|
+
maxBuffer: MAX_BUFFER,
|
|
2506
|
+
},
|
|
2507
|
+
);
|
|
2508
|
+
const rows = String(stdout)
|
|
2509
|
+
.split("\n")
|
|
2510
|
+
.map((line) => line.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/))
|
|
2511
|
+
.filter(Boolean);
|
|
2512
|
+
const tree = new Set([String(panePid)]);
|
|
2513
|
+
for (let depth = 0; depth < 8; depth += 1)
|
|
2514
|
+
for (const [, pid, ppid] of rows) if (tree.has(ppid)) tree.add(pid);
|
|
2515
|
+
return (
|
|
2516
|
+
rows.find(
|
|
2517
|
+
([, pid, , args]) => tree.has(pid) && isCodexProcessCommand(args),
|
|
2518
|
+
)?.[3] ?? null
|
|
2519
|
+
);
|
|
2520
|
+
} catch {
|
|
2521
|
+
return null;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
// tfx-live start 가 남긴 tmux 옵션, 세션 레지스트리, 프로세스 인자, rollout cwd 순으로 찾는다.
|
|
2526
|
+
async function resolveCodexTmuxThread(session, deps = {}) {
|
|
2527
|
+
const tmux = deps.runTmux ?? runTmux;
|
|
2528
|
+
let fields;
|
|
2529
|
+
try {
|
|
2530
|
+
const { stdout } = await tmux(null, [
|
|
2531
|
+
"display-message",
|
|
2532
|
+
"-p",
|
|
2533
|
+
"-t",
|
|
2534
|
+
session,
|
|
2535
|
+
"#{@tfx_codex_thread}\t#{pane_id}\t#{pane_current_path}\t#{session_created}\t#{pane_pid}",
|
|
2536
|
+
]);
|
|
2537
|
+
fields = String(stdout).trim().split("\t");
|
|
2538
|
+
} catch (error) {
|
|
2539
|
+
return { threadId: null, reason: `tmux-unavailable: ${error.message}` };
|
|
2540
|
+
}
|
|
2541
|
+
const [stored, paneId, cwd, created, panePid] = fields;
|
|
2542
|
+
if (isCodexThreadId(stored))
|
|
2543
|
+
return { threadId: stored, threadSource: "tmux-option" };
|
|
2544
|
+
const record = (deps.readCodexSessionRecords ?? readCodexSessionRecords)({
|
|
2545
|
+
dir: registryDir(),
|
|
2546
|
+
}).find((entry) => entry.tmuxPane === paneId);
|
|
2547
|
+
if (isCodexThreadId(record?.sessionId))
|
|
2548
|
+
return { threadId: record.sessionId, threadSource: "registry" };
|
|
2549
|
+
// resume 한 thread 는 rollout 이 세션보다 오래돼 cwd 대응에서 빠지므로 프로세스 인자로 찾는다.
|
|
2550
|
+
const args = await (deps.codexPaneArgs ?? codexPaneArgs)(panePid);
|
|
2551
|
+
if (/\bresume\b/.test(args ?? "")) {
|
|
2552
|
+
const resumed = args.match(/\bresume\s+([0-9a-f-]{36})\b/i)?.[1];
|
|
2553
|
+
return isCodexThreadId(resumed)
|
|
2554
|
+
? { threadId: resumed, threadSource: "process-args" }
|
|
2555
|
+
: { threadId: null, reason: "resumed-thread-unknown" };
|
|
2556
|
+
}
|
|
2557
|
+
if (!cwd) return { threadId: null, reason: "cwd-unknown" };
|
|
2558
|
+
// 같은 cwd 에 Codex TUI 가 둘 이상이면 rollout 만으로 누구 것인지 알 수 없다.
|
|
2559
|
+
const live = await (deps.countCodexTuiInCwd ?? countCodexTuiInCwd)(cwd);
|
|
2560
|
+
if (live !== 1)
|
|
2561
|
+
return {
|
|
2562
|
+
threadId: null,
|
|
2563
|
+
reason: live === null ? "cwd-unverified" : "thread-ambiguous",
|
|
2564
|
+
};
|
|
2565
|
+
const createdMs = Number.parseInt(created, 10) * 1000;
|
|
2566
|
+
const byCwd = await findCodexThreadByCwd(cwd, {
|
|
2567
|
+
sinceMs: Number.isFinite(createdMs) ? createdMs : 0,
|
|
2568
|
+
});
|
|
2569
|
+
return byCwd.threadId
|
|
2570
|
+
? { threadId: byCwd.threadId, threadSource: "rollout-cwd" }
|
|
2571
|
+
: byCwd;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
async function doAskViaQueue(adapter, opts, deps = {}) {
|
|
2575
|
+
// queue 를 못 쓰면 tmux 붙여넣기로 보내고 그 이유를 결과에 남긴다.
|
|
2576
|
+
const fallback = async (reason) => ({
|
|
2577
|
+
...(await doAskViaTmux(adapter, { ...opts, transport: "tmux" })),
|
|
2578
|
+
transportRequested: "queue",
|
|
2579
|
+
fallbackReason: reason,
|
|
2580
|
+
});
|
|
2581
|
+
if (opts.remote) return fallback("remote-host");
|
|
2582
|
+
const thread = opts.threadId
|
|
2583
|
+
? { threadId: opts.threadId, threadSource: "flag" }
|
|
2584
|
+
: await resolveCodexTmuxThread(opts.session, deps);
|
|
2585
|
+
if (!thread.threadId) {
|
|
2586
|
+
if (!opts.session)
|
|
2587
|
+
throw new Error(`Codex thread unavailable: ${thread.reason}`);
|
|
2588
|
+
return fallback(thread.reason);
|
|
2589
|
+
}
|
|
2590
|
+
const context = opts.skipContextGuard
|
|
2591
|
+
? {}
|
|
2592
|
+
: await readCodexContext(null, thread.threadId);
|
|
2593
|
+
const guard = opts.skipContextGuard
|
|
2594
|
+
? {}
|
|
2595
|
+
: contextGuard("codex", context, opts);
|
|
2596
|
+
const base = {
|
|
2597
|
+
cli: "codex",
|
|
2598
|
+
transport: "queue",
|
|
2599
|
+
session: opts.session ?? null,
|
|
2600
|
+
threadId: thread.threadId,
|
|
2601
|
+
threadSource: thread.threadSource,
|
|
2602
|
+
...context,
|
|
2603
|
+
...guard,
|
|
2604
|
+
};
|
|
2605
|
+
if (guard.ok === false) return { ...base, status: "failed", done: false };
|
|
2606
|
+
let queued;
|
|
2607
|
+
try {
|
|
2608
|
+
queued = await (deps.queueCodexMessage ?? queueCodexMessage)({
|
|
2609
|
+
threadId: thread.threadId,
|
|
2610
|
+
message: opts.prompt,
|
|
2611
|
+
});
|
|
2612
|
+
} catch (error) {
|
|
2613
|
+
if (error.maybeQueued)
|
|
2614
|
+
return {
|
|
2615
|
+
...base,
|
|
2616
|
+
ok: false,
|
|
2617
|
+
status: "unknown",
|
|
2618
|
+
inputSent: null,
|
|
2619
|
+
done: false,
|
|
2620
|
+
error: error.message,
|
|
2621
|
+
};
|
|
2622
|
+
if (!opts.session) throw error;
|
|
2623
|
+
return fallback(`queue-error: ${error.message}`);
|
|
2624
|
+
}
|
|
2625
|
+
const sent = {
|
|
2626
|
+
...base,
|
|
2627
|
+
ok: true,
|
|
2628
|
+
inputSent: true,
|
|
2629
|
+
queuedMessageId: queued.queuedMessageId,
|
|
2630
|
+
submittedAt: new Date().toISOString(),
|
|
2631
|
+
};
|
|
2632
|
+
// 표식이 없으면 rollout 에서 이 요청의 턴을 가려낼 수 없다.
|
|
2633
|
+
if (opts.noWait || opts.noRelayTag)
|
|
2634
|
+
return { ...sent, status: "queued", delivered: false, done: false };
|
|
2635
|
+
const result = await waitCodexRequest({
|
|
2636
|
+
threadId: thread.threadId,
|
|
2637
|
+
requestId: opts.requestId,
|
|
2638
|
+
timeoutMs: opts.timeoutMs,
|
|
2639
|
+
pollIntervalMs: opts.pollIntervalMs,
|
|
2640
|
+
});
|
|
2641
|
+
return { ...sent, ...result, transport: "queue" };
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2410
2644
|
async function doAskViaRemoteLive(adapter, opts, deps = {}) {
|
|
2411
2645
|
const result = await callRemoteLive(
|
|
2412
2646
|
"ask",
|
|
@@ -2882,14 +3116,52 @@ function transportFlag(flags, adapter, short, sessionId) {
|
|
|
2882
3116
|
return transport;
|
|
2883
3117
|
}
|
|
2884
3118
|
|
|
3119
|
+
// 절대 경로로 시작하는 프롬프트는 슬래시 명령이 아니다.
|
|
3120
|
+
function isSlashCommand(prompt) {
|
|
3121
|
+
return /^\/[a-z][\w-]*(?:\s|$)/i.test(String(prompt ?? ""));
|
|
3122
|
+
}
|
|
3123
|
+
|
|
2885
3124
|
function askOpts(flags, adapter) {
|
|
2886
3125
|
const short = flags.short;
|
|
2887
3126
|
const sessionId = flags["session-id"];
|
|
2888
|
-
|
|
3127
|
+
// Codex 메시지는 queue 가 기본이다. 슬래시 명령과 바쁠 때 거부·중단은 tmux 만 할 수 있다.
|
|
3128
|
+
const slash = isSlashCommand(flags.prompt);
|
|
3129
|
+
const busyPolicy = ["fail", "interrupt"].includes(flags["if-busy"]);
|
|
3130
|
+
const transportReason =
|
|
3131
|
+
adapter.cli === "codex" && !flags.transport && (slash || busyPolicy)
|
|
3132
|
+
? slash
|
|
3133
|
+
? "slash-command"
|
|
3134
|
+
: `if-busy-${flags["if-busy"]}`
|
|
3135
|
+
: null;
|
|
3136
|
+
const transport = transportFlag(
|
|
3137
|
+
adapter.cli === "codex" && !flags.transport
|
|
3138
|
+
? { ...flags, transport: transportReason ? "tmux" : "queue" }
|
|
3139
|
+
: flags,
|
|
3140
|
+
adapter,
|
|
3141
|
+
short,
|
|
3142
|
+
sessionId,
|
|
3143
|
+
);
|
|
2889
3144
|
const codexUds = adapter.cli === "codex" && transport === "uds";
|
|
3145
|
+
const codexQueue = transport === "queue";
|
|
2890
3146
|
if (adapter.cli === "codex" && transport === "auto")
|
|
2891
3147
|
throw new Error("--transport auto is only supported with --cli claude");
|
|
2892
|
-
if (
|
|
3148
|
+
if (codexQueue && adapter.cli !== "codex")
|
|
3149
|
+
throw new Error("--transport queue is only supported with --cli codex");
|
|
3150
|
+
if (codexQueue && (slash || busyPolicy))
|
|
3151
|
+
throw new Error(
|
|
3152
|
+
"queue cannot run slash commands or --if-busy fail|interrupt; use --transport tmux",
|
|
3153
|
+
);
|
|
3154
|
+
if (codexQueue && flags.thread && !isCodexThreadId(flags.thread))
|
|
3155
|
+
throw new Error("--thread for queue must be a Codex session UUID");
|
|
3156
|
+
if (codexQueue && !flags.thread && !flags.session)
|
|
3157
|
+
throw new Error("Codex queue ask requires --session or --thread");
|
|
3158
|
+
if (
|
|
3159
|
+
transport !== "tmux" &&
|
|
3160
|
+
!codexUds &&
|
|
3161
|
+
!codexQueue &&
|
|
3162
|
+
!short &&
|
|
3163
|
+
!sessionId
|
|
3164
|
+
) {
|
|
2893
3165
|
throw new Error(
|
|
2894
3166
|
`--transport ${transport} requires --short or --session-id`,
|
|
2895
3167
|
);
|
|
@@ -2898,14 +3170,20 @@ function askOpts(flags, adapter) {
|
|
|
2898
3170
|
// tmux needs a tmux session name; uds needs a daemon ref (short/sessionId);
|
|
2899
3171
|
// auto can take both (daemon ref to probe, tmux session for fallback).
|
|
2900
3172
|
session:
|
|
2901
|
-
transport === "tmux"
|
|
3173
|
+
transport === "tmux" || (codexQueue && flags.session)
|
|
2902
3174
|
? splitTmuxTarget(requireFlag(flags, "session")).target
|
|
2903
3175
|
: flags.session,
|
|
2904
3176
|
short,
|
|
2905
3177
|
sessionId,
|
|
2906
3178
|
configDir: flags["config-dir"],
|
|
2907
3179
|
transport,
|
|
2908
|
-
|
|
3180
|
+
transportReason,
|
|
3181
|
+
from: flags.from,
|
|
3182
|
+
threadId: codexUds
|
|
3183
|
+
? requireFlag(flags, "thread")
|
|
3184
|
+
: codexQueue
|
|
3185
|
+
? (flags.thread ?? null)
|
|
3186
|
+
: null,
|
|
2909
3187
|
codexSocket: codexUds ? (flags["codex-socket"] ?? "default") : null,
|
|
2910
3188
|
socketPath:
|
|
2911
3189
|
codexUds && !flags.remote
|
|
@@ -2963,6 +3241,8 @@ function interruptOpts(flags, adapter) {
|
|
|
2963
3241
|
const short = flags.short;
|
|
2964
3242
|
const sessionId = flags["session-id"];
|
|
2965
3243
|
const transport = transportFlag(flags, adapter, short, sessionId);
|
|
3244
|
+
if (transport === "queue")
|
|
3245
|
+
throw new Error("interrupt uses tmux; queue cannot send keys");
|
|
2966
3246
|
if (adapter.cli === "codex" && transport === "uds")
|
|
2967
3247
|
throw new Error(
|
|
2968
3248
|
"active turnId unavailable; cannot interrupt Codex UDS thread",
|
|
@@ -2992,11 +3272,34 @@ async function ask(flags) {
|
|
|
2992
3272
|
printJson(await doAsk(adapter, askOpts(flags, adapter)));
|
|
2993
3273
|
}
|
|
2994
3274
|
|
|
3275
|
+
async function waitCodex(flags) {
|
|
3276
|
+
if (flags.remote) throw new Error("Codex wait supports local sessions only");
|
|
3277
|
+
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
|
+
}
|
|
3288
|
+
printJson({
|
|
3289
|
+
...(await waitCodexRequest({
|
|
3290
|
+
threadId: thread.threadId,
|
|
3291
|
+
requestId,
|
|
3292
|
+
timeoutMs: secondsFlag(flags, "timeout", DEFAULT_ANSWER_TIMEOUT_MS),
|
|
3293
|
+
pollIntervalMs: msFlag(flags, "poll-interval", DEFAULT_POLL_INTERVAL_MS),
|
|
3294
|
+
})),
|
|
3295
|
+
threadSource: thread.threadSource,
|
|
3296
|
+
});
|
|
3297
|
+
}
|
|
3298
|
+
|
|
2995
3299
|
async function wait(flags) {
|
|
3300
|
+
if (flags.cli === "codex") return waitCodex(flags);
|
|
2996
3301
|
if ((flags.cli ?? "claude") !== "claude")
|
|
2997
|
-
throw new Error(
|
|
2998
|
-
"wait supports Claude UDS only; Codex UDS wait is unavailable",
|
|
2999
|
-
);
|
|
3302
|
+
throw new Error("wait supports --cli claude or codex");
|
|
3000
3303
|
if (!flags.short && !flags["session-id"])
|
|
3001
3304
|
throw new Error("wait requires --short or --session-id");
|
|
3002
3305
|
if (flags.remote || (flags.transport && flags.transport !== "uds"))
|
|
@@ -3084,18 +3387,8 @@ async function tmuxContext(adapter, opts) {
|
|
|
3084
3387
|
);
|
|
3085
3388
|
return (await readClaudeTranscript(transcript))?.context ?? unknown;
|
|
3086
3389
|
}
|
|
3087
|
-
const {
|
|
3088
|
-
|
|
3089
|
-
"-p",
|
|
3090
|
-
"-t",
|
|
3091
|
-
opts.session,
|
|
3092
|
-
"#{session_name}:#{window_index}.#{pane_index}",
|
|
3093
|
-
]);
|
|
3094
|
-
const discovery = await discoverCodexTmuxSessions();
|
|
3095
|
-
const pane = discovery.sessions
|
|
3096
|
-
.flatMap((entry) => entry.panes)
|
|
3097
|
-
.find((entry) => entry.target === target.trim());
|
|
3098
|
-
return await readCodexContext(null, pane?.threadId);
|
|
3390
|
+
const { threadId } = await resolveCodexTmuxThread(opts.session);
|
|
3391
|
+
return await readCodexContext(null, threadId);
|
|
3099
3392
|
} catch {
|
|
3100
3393
|
return unknown;
|
|
3101
3394
|
}
|
|
@@ -3702,10 +3995,8 @@ function peerSideTransport(flags, side, adapter) {
|
|
|
3702
3995
|
? (globalTransport ?? defaultTransportFor(adapter, short, sessionId))
|
|
3703
3996
|
: undefined) ??
|
|
3704
3997
|
"tmux";
|
|
3705
|
-
if (!VALID_TRANSPORTS.includes(transport)) {
|
|
3706
|
-
throw new Error(
|
|
3707
|
-
`--transport-${side} must be one of: ${VALID_TRANSPORTS.join(", ")}`,
|
|
3708
|
-
);
|
|
3998
|
+
if (!VALID_TRANSPORTS.includes(transport) || transport === "queue") {
|
|
3999
|
+
throw new Error(`--transport-${side} must be one of: tmux, uds, auto`);
|
|
3709
4000
|
}
|
|
3710
4001
|
if (
|
|
3711
4002
|
transport !== "tmux" &&
|
|
@@ -4080,58 +4371,6 @@ async function peer(flags) {
|
|
|
4080
4371
|
printJson(output);
|
|
4081
4372
|
}
|
|
4082
4373
|
|
|
4083
|
-
const ORCHESTRATION_MODES = ["peer", "codex-led", "claude-led"];
|
|
4084
|
-
const CODEX_ORCH_TRANSPORTS = ["exec", "app-server-uds"];
|
|
4085
|
-
|
|
4086
|
-
async function orchestrate(flags) {
|
|
4087
|
-
const mode = flags.mode ?? "peer";
|
|
4088
|
-
if (!ORCHESTRATION_MODES.includes(mode)) {
|
|
4089
|
-
throw new Error(`--mode must be one of: ${ORCHESTRATION_MODES.join(", ")}`);
|
|
4090
|
-
}
|
|
4091
|
-
const task = requireFlag(flags, "task");
|
|
4092
|
-
if (flags["codex-socket"] && flags["codex-transport"] === "exec")
|
|
4093
|
-
throw new Error("--codex-socket conflicts with --codex-transport exec");
|
|
4094
|
-
const codexTransport =
|
|
4095
|
-
flags["codex-transport"] ??
|
|
4096
|
-
(flags["codex-socket"] ? "app-server-uds" : "exec");
|
|
4097
|
-
if (!CODEX_ORCH_TRANSPORTS.includes(codexTransport)) {
|
|
4098
|
-
throw new Error(
|
|
4099
|
-
`--codex-transport must be one of: ${CODEX_ORCH_TRANSPORTS.join(", ")}`,
|
|
4100
|
-
);
|
|
4101
|
-
}
|
|
4102
|
-
const cwd = flags.cwd ?? process.cwd();
|
|
4103
|
-
const timeoutMs = secondsFlag(flags, "timeout", 120_000);
|
|
4104
|
-
const socketPath = flags["codex-socket"]
|
|
4105
|
-
? resolveCodexDaemonSocket(flags["codex-socket"])
|
|
4106
|
-
: undefined;
|
|
4107
|
-
|
|
4108
|
-
// Lazy import keeps the orchestration engine (and its hub/team deps) off the
|
|
4109
|
-
// hot path for every other thin-CLI verb; only `orchestrate` pays the cost.
|
|
4110
|
-
const orchestratorUrl = new URL(
|
|
4111
|
-
"../hub/team/uds-orchestrator.mjs",
|
|
4112
|
-
import.meta.url,
|
|
4113
|
-
).href;
|
|
4114
|
-
const {
|
|
4115
|
-
runUdsOrchestration,
|
|
4116
|
-
createClaudeUdsEndpoint,
|
|
4117
|
-
createCodexExecEndpoint,
|
|
4118
|
-
createCodexAppServerUdsEndpoint,
|
|
4119
|
-
} = await import(orchestratorUrl);
|
|
4120
|
-
|
|
4121
|
-
const claude = createClaudeUdsEndpoint({ cwd, timeoutMs });
|
|
4122
|
-
const codex =
|
|
4123
|
-
codexTransport === "app-server-uds"
|
|
4124
|
-
? createCodexAppServerUdsEndpoint({
|
|
4125
|
-
cwd,
|
|
4126
|
-
timeoutMs,
|
|
4127
|
-
...(socketPath ? { socketPath, spawnServer: false } : {}),
|
|
4128
|
-
})
|
|
4129
|
-
: createCodexExecEndpoint({ workdir: cwd, timeout: timeoutMs });
|
|
4130
|
-
|
|
4131
|
-
const result = await runUdsOrchestration({ mode, task, claude, codex });
|
|
4132
|
-
printJson({ codexTransport, ...result });
|
|
4133
|
-
}
|
|
4134
|
-
|
|
4135
4374
|
function printJson(value) {
|
|
4136
4375
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
4137
4376
|
}
|
|
@@ -4168,8 +4407,6 @@ async function main() {
|
|
|
4168
4407
|
await goalDriven(flags);
|
|
4169
4408
|
} else if (command === "peer") {
|
|
4170
4409
|
await peer(flags);
|
|
4171
|
-
} else if (command === "orchestrate") {
|
|
4172
|
-
await orchestrate(flags);
|
|
4173
4410
|
} else {
|
|
4174
4411
|
throw new Error(`Unknown subcommand: ${command}\n${usage()}`);
|
|
4175
4412
|
}
|