@quantiya/codevibe-codex-plugin 2.0.104 → 2.0.105
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/dist/native-launch.js +5 -0
- package/dist/server.js +78 -65
- package/libexec/companion-launcher +5 -722
- package/node_modules/@quantiya/codevibe-core/dist/companion-mode/codex-native-dispatcher.d.ts +43 -0
- package/node_modules/@quantiya/codevibe-core/dist/companion-mode/codex-native-state.d.ts +1151 -0
- package/node_modules/@quantiya/codevibe-core/dist/index.d.ts +4 -0
- package/node_modules/@quantiya/codevibe-core/dist/index.js +560 -560
- package/node_modules/@quantiya/codevibe-core/dist/local-executor/binary-classifier.d.ts +117 -0
- package/node_modules/@quantiya/codevibe-core/dist/local-executor/office-extractor.d.ts +61 -0
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +7 -1
- package/node_modules/@quantiya/codevibe-core/package.json +1 -1
- package/package.json +3 -3
|
@@ -1,33 +1,10 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# This script launches Codex CLI inside a tmux session, enabling:
|
|
6
|
-
# - Mobile prompts when screen is locked (via tmux send-keys)
|
|
7
|
-
# - Same user experience as regular codex command
|
|
8
|
-
# - Automatic sync with iOS app via session log watching
|
|
9
|
-
#
|
|
10
|
-
# Usage:
|
|
11
|
-
# codevibe --agent codex [-- codex-args...]
|
|
12
|
-
#
|
|
13
|
-
# Environment:
|
|
14
|
-
# ENVIRONMENT # Set to 'production' (default) or 'development'
|
|
15
|
-
#
|
|
16
|
-
# Examples:
|
|
17
|
-
# codevibe --agent codex
|
|
18
|
-
# codevibe --agent codex -- "fix the bug"
|
|
19
|
-
#
|
|
20
|
-
|
|
2
|
+
# CodeVibe Codex Companion: native app-server controls,checkpointed mirroring.
|
|
3
|
+
# The TypeScript supervisor owns terminal/tmux/native lifecycle. No global
|
|
4
|
+
# hooks/configuration rewrite or pane-input fallback belongs in this wrapper.
|
|
21
5
|
set -e
|
|
22
|
-
|
|
23
|
-
# Default to production environment if not specified
|
|
24
6
|
export ENVIRONMENT="${ENVIRONMENT:-production}"
|
|
25
7
|
|
|
26
|
-
# Use TMPDIR if set (macOS sets this to user-specific temp), otherwise /tmp
|
|
27
|
-
CODEVIBE_TMPDIR="${TMPDIR:-/tmp}"
|
|
28
|
-
|
|
29
|
-
# Get the directory where this script is located (resolving symlinks)
|
|
30
|
-
# This is needed because npm global installs symlink bin scripts to /usr/local/bin/
|
|
31
8
|
SOURCE="${BASH_SOURCE[0]}"
|
|
32
9
|
while [ -L "$SOURCE" ]; do
|
|
33
10
|
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
@@ -36,25 +13,8 @@ while [ -L "$SOURCE" ]; do
|
|
|
36
13
|
done
|
|
37
14
|
SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
38
15
|
PLUGIN_DIR="$(dirname "$SCRIPT_DIR")"
|
|
39
|
-
# Match Codex's documented configuration discovery. This also lets isolated
|
|
40
|
-
# candidate runs keep hooks/config separate without changing HOME, which owns
|
|
41
|
-
# the user's independent CodeVibe authentication state.
|
|
42
|
-
CODEX_CONFIG_HOME="${CODEX_HOME:-$HOME/.codex}"
|
|
43
16
|
|
|
44
|
-
#
|
|
45
|
-
# When the install one-liner runs in a fresh terminal, Homebrew's
|
|
46
|
-
# installer writes the shellenv eval into ~/.zprofile (and similar)
|
|
47
|
-
# but the user's current shell hasn't sourced it yet. Subsequent
|
|
48
|
-
# CodeVibe Companion launches in that terminal then fail tmux discovery
|
|
49
|
-
# because /opt/homebrew/bin isn't on PATH. Prepend common locations
|
|
50
|
-
# so the wrapper recovers without forcing the user to open a new
|
|
51
|
-
# terminal. Prepend (not append) is deliberate: the Homebrew binary
|
|
52
|
-
# install.sh just laid down should win over any older system binary
|
|
53
|
-
# (e.g. a stale /usr/bin/node on Linux) at the same name. To preserve
|
|
54
|
-
# the relative ordering of augmented dirs, build a single prefix
|
|
55
|
-
# string and prepend it once — iterating prepend-per-dir would
|
|
56
|
-
# reverse intended order. ${PATH:+:$PATH} keeps an empty starting
|
|
57
|
-
# PATH from producing a trailing colon (which makes cwd searchable).
|
|
17
|
+
# Preserve the installer's ordinary PATH recovery without changing profiles.
|
|
58
18
|
_CV_NEW_PATHS=""
|
|
59
19
|
for _CV_DIR in /opt/homebrew/bin /opt/homebrew/sbin /usr/local/bin /usr/local/sbin /opt/local/bin /usr/bin /bin; do
|
|
60
20
|
case ":$PATH:" in
|
|
@@ -64,681 +24,4 @@ for _CV_DIR in /opt/homebrew/bin /opt/homebrew/sbin /usr/local/bin /usr/local/sb
|
|
|
64
24
|
done
|
|
65
25
|
[ -n "$_CV_NEW_PATHS" ] && export PATH="${_CV_NEW_PATHS#:}${PATH:+:$PATH}"
|
|
66
26
|
unset _CV_DIR _CV_NEW_PATHS
|
|
67
|
-
|
|
68
|
-
# ─── Wrapper telemetry (GA4 Measurement Protocol) ─────────────────────
|
|
69
|
-
# Diagnoses agent CLI failures: pre-flight bailouts, fast-die patterns,
|
|
70
|
-
# whether SessionStart hook fired, exit code. Background curl, fail
|
|
71
|
-
# silently, no PII (hashed hostname + per-run random id only). Honors
|
|
72
|
-
# CODEVIBE_TELEMETRY_SOURCE=test for internal testing.
|
|
73
|
-
_CV_MID="G-GS74YEQTB8"
|
|
74
|
-
_CV_SEC="lAfOF6OxRzSQ-NsLBRjhAg"
|
|
75
|
-
_CV_CID="$(echo "$(uname -n)-$(id -u)" | (sha256sum 2>/dev/null || shasum -a 256 2>/dev/null || echo "anonymous-fallback ") | cut -c1-36)"
|
|
76
|
-
_CV_RUN_ID="$(head -c 16 /dev/urandom 2>/dev/null | od -An -tx1 | tr -d ' \n' | cut -c1-32)"
|
|
77
|
-
[ -z "$_CV_RUN_ID" ] && _CV_RUN_ID="fallback-$(date +%s)-$$"
|
|
78
|
-
_CV_AGENT="codex"
|
|
79
|
-
_CV_SOURCE="${CODEVIBE_TELEMETRY_SOURCE:-production}"
|
|
80
|
-
_CV_STARTED_AT="$(date +%s)"
|
|
81
|
-
_CV_EXITED="" # set by terminal events; suppresses trap double-fire
|
|
82
|
-
_CV_PLUGIN_VERSION="$(node -p "require('$PLUGIN_DIR/package.json').version" 2>/dev/null || echo unknown)"
|
|
83
|
-
_CV_MCP_LOG="${CODEVIBE_TMPDIR}/codevibe-codex-mcp.log"
|
|
84
|
-
_CV_MCP_LOG_BASELINE=0
|
|
85
|
-
if [ -f "$_CV_MCP_LOG" ]; then
|
|
86
|
-
_CV_MCP_LOG_BASELINE=$(wc -l < "$_CV_MCP_LOG" 2>/dev/null | tr -d ' ')
|
|
87
|
-
[ -z "$_CV_MCP_LOG_BASELINE" ] && _CV_MCP_LOG_BASELINE=0
|
|
88
|
-
fi
|
|
89
|
-
_CV_TMUX_STARTED="false"
|
|
90
|
-
_CV_AGENT_INVOKED="false"
|
|
91
|
-
_CV_AGENT_STARTED_AT=0
|
|
92
|
-
_CV_CODEX_EXIT_FILE="${CODEVIBE_TMPDIR}/codevibe-codex-exit-$$"
|
|
93
|
-
|
|
94
|
-
# Strip an arbitrary string down to a JSON-safe identifier alphabet.
|
|
95
|
-
# Removes anything that could break the hand-built JSON payload below
|
|
96
|
-
# (quotes, backslashes, ANSI escapes, control bytes, tabs, newlines).
|
|
97
|
-
# Truncates to 40 chars to bound the impact of pathological CLI version
|
|
98
|
-
# output. Caller is responsible for emptiness check after sanitize.
|
|
99
|
-
cv_sanitize() {
|
|
100
|
-
printf '%s' "$1" | LC_ALL=C tr -cd 'A-Za-z0-9._\- ' | cut -c1-40
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
# Sanitize trusted-but-still-string values that go into the payload
|
|
104
|
-
# (plugin version, source label) so future schema additions can't
|
|
105
|
-
# accidentally reintroduce a JSON-injection path.
|
|
106
|
-
_CV_PLUGIN_VERSION="$(cv_sanitize "$_CV_PLUGIN_VERSION")"
|
|
107
|
-
[ -z "$_CV_PLUGIN_VERSION" ] && _CV_PLUGIN_VERSION="unknown"
|
|
108
|
-
_CV_SOURCE="$(cv_sanitize "$_CV_SOURCE")"
|
|
109
|
-
[ -z "$_CV_SOURCE" ] && _CV_SOURCE="production"
|
|
110
|
-
|
|
111
|
-
cv_telem() {
|
|
112
|
-
local event="$1"; shift
|
|
113
|
-
local params="$*"
|
|
114
|
-
curl -s -X POST \
|
|
115
|
-
"https://www.google-analytics.com/mp/collect?measurement_id=${_CV_MID}&api_secret=${_CV_SEC}" \
|
|
116
|
-
-H "Content-Type: application/json" \
|
|
117
|
-
-d "{\"client_id\":\"${_CV_CID}\",\"events\":[{\"name\":\"${event}\",\"params\":{\"agent\":\"${_CV_AGENT}\",\"plugin_version\":\"${_CV_PLUGIN_VERSION}\",\"source\":\"${_CV_SOURCE}\",\"run_id\":\"${_CV_RUN_ID}\"${params:+,$params}}}]}" \
|
|
118
|
-
</dev/null >/dev/null 2>&1 &
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
cv_failed() {
|
|
122
|
-
[ -n "$_CV_EXITED" ] && return 0
|
|
123
|
-
_CV_EXITED="failed"
|
|
124
|
-
cv_telem "wrapper_failed" "\"reason\":\"$1\",\"lifetime_seconds\":$(( $(date +%s) - _CV_STARTED_AT ))"
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
# Keep a cheap internal diagnostic path for package and end-to-end routing
|
|
128
|
-
# checks without consuming any public vendor argument.
|
|
129
|
-
if [ "${CODEVIBE_COMPANION_DIAGNOSTIC:-}" = "version" ]; then
|
|
130
|
-
echo "codevibe companion codex $_CV_PLUGIN_VERSION"
|
|
131
|
-
command -v codex >/dev/null 2>&1 && codex --version 2>/dev/null | head -1
|
|
132
|
-
exit 0
|
|
133
|
-
fi
|
|
134
|
-
|
|
135
|
-
# Metadata commands are intentionally short-lived and do not create a
|
|
136
|
-
# Companion session. Run them directly in the foreground so a TTY invocation
|
|
137
|
-
# cannot race a detached tmux session that exits before attach. Arguments and
|
|
138
|
-
# the vendor exit status are preserved exactly.
|
|
139
|
-
case "${1:-}" in
|
|
140
|
-
--version|-v|--help|-h)
|
|
141
|
-
if ! command -v codex >/dev/null 2>&1; then
|
|
142
|
-
echo "Error: codex CLI is not installed." >&2
|
|
143
|
-
echo "Install Codex CLI with: npm install -g @openai/codex" >&2
|
|
144
|
-
exit 127
|
|
145
|
-
fi
|
|
146
|
-
exec env -u QUORUM_REVIEWER_SUBPROCESS codex "$@"
|
|
147
|
-
;;
|
|
148
|
-
esac
|
|
149
|
-
|
|
150
|
-
# Capture environment facts for the session-flow wrapper_started event.
|
|
151
|
-
# Each probe is non-fatal — if a CLI is missing we record "missing" rather
|
|
152
|
-
# than aborting; pre-flight checks below still gate execution. Every
|
|
153
|
-
# string that lands in the JSON payload goes through cv_sanitize so an
|
|
154
|
-
# agent CLI emitting ANSI escapes or quotes in `--version` can't break
|
|
155
|
-
# the hand-built payload.
|
|
156
|
-
_CV_CODEX_VER="missing"
|
|
157
|
-
command -v codex >/dev/null 2>&1 && _CV_CODEX_VER="$(codex --version 2>/dev/null | cv_sanitize)"
|
|
158
|
-
[ -z "$_CV_CODEX_VER" ] && _CV_CODEX_VER="unknown"
|
|
159
|
-
_CV_NODE_VER="missing"
|
|
160
|
-
command -v node >/dev/null 2>&1 && _CV_NODE_VER="$(node -v 2>/dev/null | cv_sanitize)"
|
|
161
|
-
[ -z "$_CV_NODE_VER" ] && _CV_NODE_VER="unknown"
|
|
162
|
-
_CV_TMUX_VER="missing"
|
|
163
|
-
command -v tmux >/dev/null 2>&1 && _CV_TMUX_VER="$(tmux -V 2>/dev/null | cv_sanitize)"
|
|
164
|
-
[ -z "$_CV_TMUX_VER" ] && _CV_TMUX_VER="unknown"
|
|
165
|
-
_CV_OS_VER="$(uname -s | cv_sanitize)"
|
|
166
|
-
[ -z "$_CV_OS_VER" ] && _CV_OS_VER="unknown"
|
|
167
|
-
_CV_ARCH_VER="$(uname -m | cv_sanitize)"
|
|
168
|
-
[ -z "$_CV_ARCH_VER" ] && _CV_ARCH_VER="unknown"
|
|
169
|
-
_CV_CODEX_AUTH="false"; [ -f "$CODEX_CONFIG_HOME/auth.json" ] && _CV_CODEX_AUTH="true"
|
|
170
|
-
_CV_CODEX_CONFIG="false"; [ -f "$CODEX_CONFIG_HOME/config.toml" ] && _CV_CODEX_CONFIG="true"
|
|
171
|
-
_CV_INSIDE_TMUX="false"; [ -n "$TMUX" ] && _CV_INSIDE_TMUX="true"
|
|
172
|
-
_CV_IS_TTY="false"; { [ -t 0 ] && [ -t 1 ]; } && _CV_IS_TTY="true"
|
|
173
|
-
cv_telem "wrapper_started" "\"invocation\":\"session\",\"os\":\"$_CV_OS_VER\",\"arch\":\"$_CV_ARCH_VER\",\"codex_version\":\"$_CV_CODEX_VER\",\"node_version\":\"$_CV_NODE_VER\",\"tmux_version\":\"$_CV_TMUX_VER\",\"codex_auth_present\":$_CV_CODEX_AUTH,\"codex_config_present\":$_CV_CODEX_CONFIG,\"inside_tmux\":$_CV_INSIDE_TMUX,\"is_terminal\":$_CV_IS_TTY"
|
|
174
|
-
|
|
175
|
-
# Configuration
|
|
176
|
-
TMUX_SESSION_PREFIX="codevibe-codex"
|
|
177
|
-
LOG_FILE="${CODEVIBE_TMPDIR}/codevibe-codex-wrapper.log"
|
|
178
|
-
MCP_LOG_FILE="${CODEVIBE_TMPDIR}/codevibe-codex-mcp.log"
|
|
179
|
-
|
|
180
|
-
log() {
|
|
181
|
-
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
tmux_session_state() {
|
|
185
|
-
local target="$1"
|
|
186
|
-
local output
|
|
187
|
-
local rc
|
|
188
|
-
output="$(tmux has-session -t "$target" 2>&1)"
|
|
189
|
-
rc=$?
|
|
190
|
-
if [ "$rc" -eq 0 ]; then
|
|
191
|
-
printf '%s\n' "alive"
|
|
192
|
-
return
|
|
193
|
-
fi
|
|
194
|
-
case "$output" in
|
|
195
|
-
"can't find session:"*|"no server running on "*|"error connecting to "*" (No such file or directory)"|"error connecting to "*" (Connection refused)")
|
|
196
|
-
printf '%s\n' "absent"
|
|
197
|
-
;;
|
|
198
|
-
*)
|
|
199
|
-
log "WARN: tmux inspection failed for $target; preserving daemon: $output"
|
|
200
|
-
printf '%s\n' "unknown"
|
|
201
|
-
;;
|
|
202
|
-
esac
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
# Cleanup function to kill server when wrapper exits
|
|
206
|
-
cleanup() {
|
|
207
|
-
local wrapper_exit_code=$?
|
|
208
|
-
log "Cleanup triggered"
|
|
209
|
-
|
|
210
|
-
# `tmux attach-session` returns when the client DETACHES even though Codex
|
|
211
|
-
# continues running inside the tmux session. Killing the daemon here leaves
|
|
212
|
-
# that live Codex session disconnected on mobile. The daemon now monitors
|
|
213
|
-
# this exact tmux session and performs normal cleanup when the native
|
|
214
|
-
# session actually ends, so a detach transfers lifecycle ownership to it.
|
|
215
|
-
if [ "${_CV_TMUX_STARTED:-false}" = "true" ]; then
|
|
216
|
-
local tmux_state
|
|
217
|
-
tmux_state="$(tmux_session_state "$SESSION_NAME")"
|
|
218
|
-
if [ "$tmux_state" = "alive" ]; then
|
|
219
|
-
log "Tmux client detached while Codex remains active; leaving daemon running"
|
|
220
|
-
return
|
|
221
|
-
fi
|
|
222
|
-
if [ "$tmux_state" = "unknown" ]; then
|
|
223
|
-
log "Tmux state is ambiguous; preserving daemon and native session"
|
|
224
|
-
return
|
|
225
|
-
fi
|
|
226
|
-
# Only an absent session proves native termination. A successful tmux
|
|
227
|
-
# client can still wrap a native error; collect that status here so
|
|
228
|
-
# setup/attach failures after native termination use the same path.
|
|
229
|
-
local native_exit
|
|
230
|
-
native_exit="$(head -c 16 "$_CV_CODEX_EXIT_FILE" 2>/dev/null)" || native_exit=""
|
|
231
|
-
if [[ "$native_exit" =~ ^(0|[1-9][0-9]{0,2})$ ]] && [ "$native_exit" -le 255 ]; then
|
|
232
|
-
log "Native Codex session ended with status $native_exit"
|
|
233
|
-
if [ "$wrapper_exit_code" -ne 130 ] && [ "$wrapper_exit_code" -ne 143 ]; then
|
|
234
|
-
wrapper_exit_code=$native_exit
|
|
235
|
-
fi
|
|
236
|
-
elif [ "$wrapper_exit_code" -eq 0 ]; then
|
|
237
|
-
log "WARN: native session ended without a valid exit status"
|
|
238
|
-
wrapper_exit_code=1
|
|
239
|
-
fi
|
|
240
|
-
fi
|
|
241
|
-
|
|
242
|
-
# Fire wrapper_exited telemetry BEFORE killing the server so the MCP
|
|
243
|
-
# log is intact when we grep for SessionStart. cv_failed sets
|
|
244
|
-
# _CV_EXITED on pre-flight failures so this block won't double-fire.
|
|
245
|
-
if [ -z "$_CV_EXITED" ]; then
|
|
246
|
-
_CV_EXITED="exited"
|
|
247
|
-
local codex_exit="unknown"
|
|
248
|
-
if [ -f "$_CV_CODEX_EXIT_FILE" ]; then
|
|
249
|
-
codex_exit="$(cat "$_CV_CODEX_EXIT_FILE" 2>/dev/null | head -c 10 | tr -d '\n\r ')"
|
|
250
|
-
[ -z "$codex_exit" ] && codex_exit="unknown"
|
|
251
|
-
fi
|
|
252
|
-
local lifetime=$(( $(date +%s) - _CV_STARTED_AT ))
|
|
253
|
-
local codex_lifetime=0
|
|
254
|
-
if [ "$_CV_AGENT_STARTED_AT" -gt 0 ] 2>/dev/null; then
|
|
255
|
-
codex_lifetime=$(( $(date +%s) - _CV_AGENT_STARTED_AT ))
|
|
256
|
-
fi
|
|
257
|
-
local hook_fired="false"
|
|
258
|
-
if [ -f "$_CV_MCP_LOG" ]; then
|
|
259
|
-
if tail -n "+$((_CV_MCP_LOG_BASELINE + 1))" "$_CV_MCP_LOG" 2>/dev/null \
|
|
260
|
-
| grep -q "SessionStart" 2>/dev/null; then
|
|
261
|
-
hook_fired="true"
|
|
262
|
-
fi
|
|
263
|
-
fi
|
|
264
|
-
# Outcome priority: SIGINT/SIGTERM beats everything (user intent).
|
|
265
|
-
# Then "we never got far enough to invoke codex" — distinct from
|
|
266
|
-
# "we invoked codex via passthrough but never started a tmux of
|
|
267
|
-
# our own" (the latter is a normal direct-run, not an abort).
|
|
268
|
-
local outcome
|
|
269
|
-
if [ "$wrapper_exit_code" = "130" ] || [ "$wrapper_exit_code" = "143" ]; then
|
|
270
|
-
outcome="interrupted"
|
|
271
|
-
elif [ "$_CV_AGENT_INVOKED" = "false" ]; then
|
|
272
|
-
outcome="pre_invoke_abort"
|
|
273
|
-
elif [ "$codex_exit" != "unknown" ] && [ "$codex_exit" != "0" ]; then
|
|
274
|
-
outcome="error_exit"
|
|
275
|
-
elif [ "$codex_lifetime" -lt 5 ] 2>/dev/null; then
|
|
276
|
-
outcome="early_exit"
|
|
277
|
-
elif [ "$codex_lifetime" -lt 60 ] 2>/dev/null; then
|
|
278
|
-
outcome="clean_short"
|
|
279
|
-
else
|
|
280
|
-
outcome="clean_long"
|
|
281
|
-
fi
|
|
282
|
-
cv_telem "wrapper_exited" "\"exit_code\":$wrapper_exit_code,\"lifetime_seconds\":$lifetime,\"codex_exit_code\":\"$codex_exit\",\"codex_lifetime_seconds\":$codex_lifetime,\"tmux_session_started\":$_CV_TMUX_STARTED,\"agent_invoked\":$_CV_AGENT_INVOKED,\"session_start_hook_fired\":$hook_fired,\"terminal_outcome\":\"$outcome\""
|
|
283
|
-
fi
|
|
284
|
-
|
|
285
|
-
local server_exit=0
|
|
286
|
-
if [ -n "$SERVER_PID" ]; then
|
|
287
|
-
if kill -0 "$SERVER_PID" 2>/dev/null; then
|
|
288
|
-
log "Stopping server (PID: $SERVER_PID)"
|
|
289
|
-
kill "$SERVER_PID" 2>/dev/null || true
|
|
290
|
-
local waited=0
|
|
291
|
-
while kill -0 "$SERVER_PID" 2>/dev/null && [ "$waited" -lt 300 ]; do
|
|
292
|
-
sleep 0.1
|
|
293
|
-
waited=$((waited + 1))
|
|
294
|
-
done
|
|
295
|
-
if kill -0 "$SERVER_PID" 2>/dev/null; then
|
|
296
|
-
log "Server PID $SERVER_PID did not exit gracefully within 30s; killing with SIGKILL"
|
|
297
|
-
kill -9 "$SERVER_PID" 2>/dev/null || true
|
|
298
|
-
server_exit=137
|
|
299
|
-
fi
|
|
300
|
-
fi
|
|
301
|
-
local wait_rc=0
|
|
302
|
-
wait "$SERVER_PID" 2>/dev/null || wait_rc=$?
|
|
303
|
-
if [ "$wait_rc" -ne 0 ] && [ "$server_exit" -eq 0 ]; then
|
|
304
|
-
server_exit=$wait_rc
|
|
305
|
-
fi
|
|
306
|
-
fi
|
|
307
|
-
# Remove PID file and port file
|
|
308
|
-
rm -f "${CODEVIBE_TMPDIR}/codevibe-codex-server-$$.pid"
|
|
309
|
-
rm -f "${CODEVIBE_TMPDIR}/codevibe-codex-${SESSION_NAME}.port"
|
|
310
|
-
rm -f "$_CV_CODEX_EXIT_FILE"
|
|
311
|
-
|
|
312
|
-
# Remove our hooks from Codex's configured hooks.json (idempotent,
|
|
313
|
-
# concurrent-safe).
|
|
314
|
-
# Uses Node (always available — required to run the daemon) so users
|
|
315
|
-
# without jq don't end up with stale CodeVibe hook entries leftover
|
|
316
|
-
# after the wrapper exits. Ownership check is path-prefix + filename
|
|
317
|
-
# tail (NOT a bare substring "codevibe-codex" match) so user paths
|
|
318
|
-
# that happen to contain the string codevibe-codex (e.g. a username,
|
|
319
|
-
# a directory like /Users/codevibe-codex/) cannot trigger removal of
|
|
320
|
-
# the user's unrelated hooks.
|
|
321
|
-
if [ -f "$CODEX_CONFIG_HOME/hooks.json" ] && command -v node &> /dev/null; then
|
|
322
|
-
# Only remove if no other codevibe-codex sessions are running
|
|
323
|
-
if [ "$(pgrep -f 'codevibe-codex' | wc -l)" -le 1 ]; then
|
|
324
|
-
log "Removing CodeVibe hooks from $CODEX_CONFIG_HOME/hooks.json"
|
|
325
|
-
# The shared helper keeps install and cleanup ownership rules in
|
|
326
|
-
# lockstep, including task-named CodeVibe agent worktrees.
|
|
327
|
-
if CV_HOOKS_FILE="$CODEX_CONFIG_HOME/hooks.json" \
|
|
328
|
-
node "$PLUGIN_DIR/libexec/codex-hook-config.js" cleanup 2>>"$LOG_FILE"; then
|
|
329
|
-
:
|
|
330
|
-
else
|
|
331
|
-
log "WARN: failed to clean CodeVibe hooks from hooks.json (left as-is)"
|
|
332
|
-
fi
|
|
333
|
-
else
|
|
334
|
-
log "Other codevibe-codex sessions active, keeping hooks"
|
|
335
|
-
fi
|
|
336
|
-
fi
|
|
337
|
-
|
|
338
|
-
trap - EXIT INT TERM
|
|
339
|
-
if [ "$wrapper_exit_code" -ne 0 ]; then
|
|
340
|
-
exit "$wrapper_exit_code"
|
|
341
|
-
elif [ "$server_exit" -ne 0 ]; then
|
|
342
|
-
exit "$server_exit"
|
|
343
|
-
fi
|
|
344
|
-
exit 0
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
# Set up trap for cleanup
|
|
348
|
-
trap cleanup EXIT INT TERM
|
|
349
|
-
|
|
350
|
-
# Check if tmux is installed
|
|
351
|
-
if ! command -v tmux &> /dev/null; then
|
|
352
|
-
echo "Error: tmux is required but not installed."
|
|
353
|
-
echo "Install with: brew install tmux"
|
|
354
|
-
cv_failed "tmux_missing"
|
|
355
|
-
sleep 1
|
|
356
|
-
exit 1
|
|
357
|
-
fi
|
|
358
|
-
|
|
359
|
-
# Check if codex is installed
|
|
360
|
-
if ! command -v codex &> /dev/null; then
|
|
361
|
-
echo "Error: codex CLI is not installed."
|
|
362
|
-
echo "Install with: npm install -g @openai/codex"
|
|
363
|
-
cv_failed "codex_missing"
|
|
364
|
-
sleep 1
|
|
365
|
-
exit 1
|
|
366
|
-
fi
|
|
367
|
-
|
|
368
|
-
# Check if node is installed
|
|
369
|
-
if ! command -v node &> /dev/null; then
|
|
370
|
-
echo "Error: Node.js is required but not installed."
|
|
371
|
-
cv_failed "node_missing"
|
|
372
|
-
sleep 1
|
|
373
|
-
exit 1
|
|
374
|
-
fi
|
|
375
|
-
|
|
376
|
-
# Check if server is built
|
|
377
|
-
if [ ! -f "$PLUGIN_DIR/dist/server.js" ]; then
|
|
378
|
-
echo "Error: Server not built. Run 'npm run build' in the plugin directory first."
|
|
379
|
-
cv_failed "server_not_built"
|
|
380
|
-
sleep 1
|
|
381
|
-
exit 1
|
|
382
|
-
fi
|
|
383
|
-
|
|
384
|
-
# Direct routes retain verbatim native argument handling.
|
|
385
|
-
CODEX_LOCAL_ARGS=()
|
|
386
|
-
|
|
387
|
-
# Generate a unique session name.
|
|
388
|
-
SESSION_NAME="${TMUX_SESSION_PREFIX}-$$"
|
|
389
|
-
WORKING_DIR="$(pwd)"
|
|
390
|
-
export CODEVIBE_CODEX_WRAPPER_PID="$$"
|
|
391
|
-
|
|
392
|
-
log "Starting codevibe-codex with session: $SESSION_NAME"
|
|
393
|
-
log "Working directory: $WORKING_DIR"
|
|
394
|
-
log "Arguments: $*"
|
|
395
|
-
|
|
396
|
-
# Check if we're already inside tmux.
|
|
397
|
-
# We deliberately do NOT `exec` here — running codex as a child process
|
|
398
|
-
# lets the EXIT trap fire after it returns so wrapper_exited still gets
|
|
399
|
-
# emitted on these direct-run paths. Behaviorally identical for the user
|
|
400
|
-
# (codex remains the foreground process for the duration).
|
|
401
|
-
if [ -n "$TMUX" ]; then
|
|
402
|
-
log "Already inside tmux, running codex directly"
|
|
403
|
-
_CV_AGENT_INVOKED="true"
|
|
404
|
-
_CV_AGENT_STARTED_AT="$(date +%s)"
|
|
405
|
-
# `|| _CV_RC=$?` is load-bearing: with `set -e`, a non-zero exit from
|
|
406
|
-
# codex would abort the wrapper before we capture the exit code,
|
|
407
|
-
# leaving wrapper_exited with codex_exit_code="unknown". The `||`
|
|
408
|
-
# form catches non-zero without triggering set -e, while exit 0
|
|
409
|
-
# leaves _CV_RC at its 0 default. printf's `|| true` keeps a
|
|
410
|
-
# disk-full failure from clobbering diagnostics.
|
|
411
|
-
_CV_RC=0
|
|
412
|
-
env -u QUORUM_REVIEWER_SUBPROCESS codex "${CODEX_LOCAL_ARGS[@]}" "$@" || _CV_RC=$?
|
|
413
|
-
printf '%s' "$_CV_RC" > "$_CV_CODEX_EXIT_FILE" 2>/dev/null || true
|
|
414
|
-
exit "$_CV_RC"
|
|
415
|
-
fi
|
|
416
|
-
|
|
417
|
-
# Check if running in a terminal — same direct-run treatment as above.
|
|
418
|
-
if [ ! -t 0 ] || [ ! -t 1 ]; then
|
|
419
|
-
log "Not running in a terminal, running codex directly"
|
|
420
|
-
_CV_AGENT_INVOKED="true"
|
|
421
|
-
_CV_AGENT_STARTED_AT="$(date +%s)"
|
|
422
|
-
_CV_RC=0
|
|
423
|
-
env -u QUORUM_REVIEWER_SUBPROCESS codex "${CODEX_LOCAL_ARGS[@]}" "$@" || _CV_RC=$?
|
|
424
|
-
printf '%s' "$_CV_RC" > "$_CV_CODEX_EXIT_FILE" 2>/dev/null || true
|
|
425
|
-
exit "$_CV_RC"
|
|
426
|
-
fi
|
|
427
|
-
|
|
428
|
-
# Select in-process execution only for the own-tmux Companion route, before
|
|
429
|
-
# hook/cloud startup. Older CLIs retain their previous native execution mode.
|
|
430
|
-
if ! _CV_LOCAL_MODE=$(node "$PLUGIN_DIR/libexec/codex-local-mode.js" "$@"); then
|
|
431
|
-
cv_failed "codex_local_execution_unavailable"
|
|
432
|
-
exit 1
|
|
433
|
-
fi
|
|
434
|
-
case "$_CV_LOCAL_MODE" in
|
|
435
|
-
add) CODEX_LOCAL_ARGS=(--no-daemon) ;;
|
|
436
|
-
present|legacy) ;;
|
|
437
|
-
*) cv_failed "codex_local_execution_unavailable"; exit 1 ;;
|
|
438
|
-
esac
|
|
439
|
-
|
|
440
|
-
# Start the session log watcher server in background
|
|
441
|
-
log "Starting session log watcher server..."
|
|
442
|
-
export CODEX_WORKING_DIRECTORY="$WORKING_DIR"
|
|
443
|
-
export CODEVIBE_CODEX_TMUX_SESSION="$SESSION_NAME"
|
|
444
|
-
export CODEVIBE_CODEX_PRIMARY_COMPANION=1
|
|
445
|
-
export CODEVIBE_CODEX_PLUGIN_DIR="$PLUGIN_DIR"
|
|
446
|
-
|
|
447
|
-
# Install hooks.json for Codex CLI with absolute paths (idempotent)
|
|
448
|
-
# Codex CLI does NOT expand env vars in hook commands, so we must
|
|
449
|
-
# write absolute paths at runtime.
|
|
450
|
-
CODEX_HOOKS_FILE="$CODEX_CONFIG_HOME/hooks.json"
|
|
451
|
-
HOOKS_DIR="$PLUGIN_DIR/hooks"
|
|
452
|
-
|
|
453
|
-
generate_hooks_json() {
|
|
454
|
-
cat <<HOOKEOF
|
|
455
|
-
{
|
|
456
|
-
"hooks": {
|
|
457
|
-
"SessionStart": [{"matcher": "*", "hooks": [{"type": "command", "command": "bash ${HOOKS_DIR}/session-start.sh"}]}],
|
|
458
|
-
"UserPromptSubmit": [{"matcher": "*", "hooks": [{"type": "command", "command": "bash ${HOOKS_DIR}/user-prompt.sh"}]}],
|
|
459
|
-
"PreToolUse": [{"matcher": "*", "hooks": [{"type": "command", "command": "bash ${HOOKS_DIR}/pre-tool-use.sh"}]}],
|
|
460
|
-
"PermissionRequest": [{"matcher": "*", "hooks": [{"type": "command", "command": "bash ${HOOKS_DIR}/permission-request.sh"}]}],
|
|
461
|
-
"PostToolUse": [{"matcher": "*", "hooks": [{"type": "command", "command": "bash ${HOOKS_DIR}/post-tool-use.sh"}]}],
|
|
462
|
-
"Stop": [{"matcher": "*", "hooks": [{"type": "command", "command": "bash ${HOOKS_DIR}/stop.sh"}]}]
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
HOOKEOF
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
# Ensure hooks feature flag is enabled in Codex config
|
|
469
|
-
CODEX_CONFIG="$CODEX_CONFIG_HOME/config.toml"
|
|
470
|
-
mkdir -p "$CODEX_CONFIG_HOME"
|
|
471
|
-
if [ ! -f "$CODEX_CONFIG" ]; then
|
|
472
|
-
printf '[features]\nhooks = true\n' > "$CODEX_CONFIG"
|
|
473
|
-
log "Created config.toml with hooks feature flag"
|
|
474
|
-
else
|
|
475
|
-
if CODEX_CONFIG_PATH="$CODEX_CONFIG" node -e '
|
|
476
|
-
const fs = require("fs");
|
|
477
|
-
const path = process.env.CODEX_CONFIG_PATH;
|
|
478
|
-
const original = fs.readFileSync(path, "utf8");
|
|
479
|
-
const lines = original.split(/\r?\n/);
|
|
480
|
-
let inFeaturesScan = false;
|
|
481
|
-
const hasHooksInFeatures = lines.some((line) => {
|
|
482
|
-
const trimmed = line.trim();
|
|
483
|
-
const tableMatch = trimmed.match(/^\[([^\]]+)\]\s*$/);
|
|
484
|
-
if (tableMatch) {
|
|
485
|
-
inFeaturesScan = tableMatch[1] === "features";
|
|
486
|
-
return false;
|
|
487
|
-
}
|
|
488
|
-
return inFeaturesScan && /^\s*hooks\s*=/.test(line);
|
|
489
|
-
});
|
|
490
|
-
const out = [];
|
|
491
|
-
let inFeatures = false;
|
|
492
|
-
let sawFeatures = false;
|
|
493
|
-
let sawHooks = false;
|
|
494
|
-
let insertedHooks = false;
|
|
495
|
-
|
|
496
|
-
const flushHooks = () => {
|
|
497
|
-
if (sawFeatures && !sawHooks && !insertedHooks) {
|
|
498
|
-
out.push("hooks = true");
|
|
499
|
-
insertedHooks = true;
|
|
500
|
-
sawHooks = true;
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
for (const line of lines) {
|
|
505
|
-
const trimmed = line.trim();
|
|
506
|
-
const tableMatch = trimmed.match(/^\[([^\]]+)\]\s*$/);
|
|
507
|
-
if (tableMatch) {
|
|
508
|
-
if (inFeatures) {
|
|
509
|
-
flushHooks();
|
|
510
|
-
}
|
|
511
|
-
inFeatures = tableMatch[1] === "features";
|
|
512
|
-
sawFeatures = sawFeatures || inFeatures;
|
|
513
|
-
out.push(line);
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (inFeatures && /^\s*codex_hooks\s*=/.test(line)) {
|
|
518
|
-
if (!hasHooksInFeatures && !sawHooks) {
|
|
519
|
-
out.push(line.replace(/codex_hooks\s*=.*/, "hooks = true"));
|
|
520
|
-
sawHooks = true;
|
|
521
|
-
}
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
if (inFeatures && /^\s*hooks\s*=/.test(line)) {
|
|
526
|
-
out.push(line.replace(/hooks\s*=.*/, "hooks = true"));
|
|
527
|
-
sawHooks = true;
|
|
528
|
-
continue;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
out.push(line);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (inFeatures) {
|
|
535
|
-
flushHooks();
|
|
536
|
-
}
|
|
537
|
-
if (!sawFeatures) {
|
|
538
|
-
if (out.length > 0 && out[out.length - 1] !== "") {
|
|
539
|
-
out.push("");
|
|
540
|
-
}
|
|
541
|
-
out.push("[features]", "hooks = true");
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
const next = out.join("\n").replace(/\n*$/, "\n");
|
|
545
|
-
if (next !== original) {
|
|
546
|
-
fs.writeFileSync(path, next);
|
|
547
|
-
}
|
|
548
|
-
' 2>>"$LOG_FILE"; then
|
|
549
|
-
log "Ensured hooks feature flag in config.toml"
|
|
550
|
-
else
|
|
551
|
-
log "WARN: failed to migrate config.toml to [features].hooks; continuing with existing config"
|
|
552
|
-
fi
|
|
553
|
-
fi
|
|
554
|
-
|
|
555
|
-
# Hook installation outcome — feeds the hooks_install_outcome telemetry
|
|
556
|
-
# beacon below. Distinguishes fresh install, merge-into-existing,
|
|
557
|
-
# already-installed, and failure modes. Codex events flow ONLY through
|
|
558
|
-
# hooks (no JSONL fallback), so a silent skip here = a 0-event ghost
|
|
559
|
-
# session in DDB.
|
|
560
|
-
_CV_HOOKS_OUTCOME="unknown"
|
|
561
|
-
_CV_HOOKS_REASON=""
|
|
562
|
-
|
|
563
|
-
# Single Node-based hook installer: handles fresh install, idempotent
|
|
564
|
-
# merge into the user's existing hooks.json, and "already installed"
|
|
565
|
-
# detection — all with a stable ownership predicate.
|
|
566
|
-
#
|
|
567
|
-
# Design notes:
|
|
568
|
-
#
|
|
569
|
-
# - "already installed" detection: a structured comparison of the exact
|
|
570
|
-
# current hook commands at every expected event key. An older install path
|
|
571
|
-
# is owned but stale, so it is replaced instead of silently pinning hooks to
|
|
572
|
-
# a prior checkout/package version. This also replaces the former
|
|
573
|
-
# `grep -q "codevibe-codex"` substring test, which false-positived on
|
|
574
|
-
# unrelated user paths containing that string.
|
|
575
|
-
#
|
|
576
|
-
# - Merge dedupe: filters our previously-installed entries out of
|
|
577
|
-
# each event key's array, then appends the fresh template entries.
|
|
578
|
-
# Idempotent — multiple runs (including concurrent ones) converge
|
|
579
|
-
# to the same hooks.json. Filters at the INNER hooks[] item level
|
|
580
|
-
# so a matcher entry that combines a user hook with one of ours
|
|
581
|
-
# keeps the user hook (only the CodeVibe hooks get stripped, not
|
|
582
|
-
# the surrounding entry).
|
|
583
|
-
#
|
|
584
|
-
# - Ownership predicate: canonical package paths or CodeVibe's strict
|
|
585
|
-
# `.agent-worktrees/<agent>/codevibe-codex-*/hooks/` layout, plus a
|
|
586
|
-
# filename allowlist. Rejects unrelated user hook paths.
|
|
587
|
-
#
|
|
588
|
-
# - Per-process tmp file (.<pid>.<time>.tmp): two concurrent installs
|
|
589
|
-
# won't clobber each other's intermediate state. Final rename is
|
|
590
|
-
# POSIX-atomic — last writer wins with content equivalent to what
|
|
591
|
-
# the first writer would have produced (idempotency by construction).
|
|
592
|
-
#
|
|
593
|
-
# Wrapping in `if … ; then … else INSTALLER_RC=$? ; fi` is required to
|
|
594
|
-
# capture non-zero exit codes; under `set -e`, a bare `VAR=$(cmd)`
|
|
595
|
-
# followed by `RC=$?` would short-circuit on Node's non-zero exit
|
|
596
|
-
# before $? gets read, defeating the entire fail-closed mapping below.
|
|
597
|
-
GENERATED_HOOKS=$(generate_hooks_json)
|
|
598
|
-
if INSTALLER_OUTPUT=$(CV_EXISTING="$CODEX_HOOKS_FILE" \
|
|
599
|
-
CV_NEW="$GENERATED_HOOKS" \
|
|
600
|
-
node "$PLUGIN_DIR/libexec/codex-hook-config.js" merge \
|
|
601
|
-
2>>"$LOG_FILE"); then
|
|
602
|
-
INSTALLER_RC=0
|
|
603
|
-
else
|
|
604
|
-
INSTALLER_RC=$?
|
|
605
|
-
fi
|
|
606
|
-
if [ "$INSTALLER_RC" -eq 0 ]; then
|
|
607
|
-
case "$INSTALLER_OUTPUT" in
|
|
608
|
-
OUTCOME:already_installed*)
|
|
609
|
-
log "CodeVibe hooks already installed in $CODEX_HOOKS_FILE"
|
|
610
|
-
_CV_HOOKS_OUTCOME="already_installed"
|
|
611
|
-
;;
|
|
612
|
-
OUTCOME:merged*)
|
|
613
|
-
log "Merged CodeVibe hooks into existing $CODEX_HOOKS_FILE"
|
|
614
|
-
_CV_HOOKS_OUTCOME="merged"
|
|
615
|
-
;;
|
|
616
|
-
OUTCOME:fresh_install*)
|
|
617
|
-
log "Installed CodeVibe hooks to $CODEX_HOOKS_FILE"
|
|
618
|
-
_CV_HOOKS_OUTCOME="fresh_install"
|
|
619
|
-
;;
|
|
620
|
-
*)
|
|
621
|
-
_CV_HOOKS_OUTCOME="merge_failed"
|
|
622
|
-
_CV_HOOKS_REASON="unrecognized_outcome"
|
|
623
|
-
log "ERROR: hooks installer returned unrecognized outcome: $INSTALLER_OUTPUT"
|
|
624
|
-
;;
|
|
625
|
-
esac
|
|
626
|
-
else
|
|
627
|
-
case "$INSTALLER_RC" in
|
|
628
|
-
2) _CV_HOOKS_OUTCOME="merge_failed"; _CV_HOOKS_REASON="parse_existing_error" ;;
|
|
629
|
-
3) _CV_HOOKS_OUTCOME="merge_failed"; _CV_HOOKS_REASON="parse_template_error" ;;
|
|
630
|
-
4) _CV_HOOKS_OUTCOME="merge_failed"; _CV_HOOKS_REASON="write_failed" ;;
|
|
631
|
-
*) _CV_HOOKS_OUTCOME="merge_failed"; _CV_HOOKS_REASON="installer_exit_$INSTALLER_RC" ;;
|
|
632
|
-
esac
|
|
633
|
-
log "ERROR: hooks installer failed (rc=$INSTALLER_RC, reason=$_CV_HOOKS_REASON)"
|
|
634
|
-
fi
|
|
635
|
-
cv_telem "hooks_install_outcome" "\"outcome\":\"$_CV_HOOKS_OUTCOME\",\"reason\":\"$_CV_HOOKS_REASON\""
|
|
636
|
-
|
|
637
|
-
# If hook installation didn't land, there's no point starting the daemon —
|
|
638
|
-
# Codex events flow ONLY through hooks. Bail with a clear message so the
|
|
639
|
-
# user can fix the underlying file/permission issue rather than running
|
|
640
|
-
# a silent ghost session.
|
|
641
|
-
case "$_CV_HOOKS_OUTCOME" in
|
|
642
|
-
fresh_install|merged|already_installed) ;;
|
|
643
|
-
*)
|
|
644
|
-
echo "Error: failed to install CodeVibe hooks into $CODEX_HOOKS_FILE ($_CV_HOOKS_OUTCOME)."
|
|
645
|
-
echo " Without hooks installed, Codex sessions cannot sync to mobile."
|
|
646
|
-
echo " Check $LOG_FILE for the underlying error."
|
|
647
|
-
cv_failed "hooks_install_$_CV_HOOKS_OUTCOME"
|
|
648
|
-
sleep 1
|
|
649
|
-
exit 1
|
|
650
|
-
;;
|
|
651
|
-
esac
|
|
652
|
-
|
|
653
|
-
echo "CodeVibe: if Codex /hooks shows CodeVibe hooks in Review or inactive,"
|
|
654
|
-
echo " trust/enable them for full timeline fidelity. Until then,"
|
|
655
|
-
echo " CodeVibe uses compatibility mode: approvals still mirror the"
|
|
656
|
-
echo " desktop prompt, but timeline events may be delayed or incomplete."
|
|
657
|
-
|
|
658
|
-
# Start server and capture its PID
|
|
659
|
-
node "$PLUGIN_DIR/dist/server.js" >> "$MCP_LOG_FILE" 2>&1 &
|
|
660
|
-
SERVER_PID=$!
|
|
661
|
-
echo "$SERVER_PID" > "${CODEVIBE_TMPDIR}/codevibe-codex-server-$$.pid"
|
|
662
|
-
|
|
663
|
-
log "Server started with PID: $SERVER_PID"
|
|
664
|
-
|
|
665
|
-
# Wait a moment for server to initialize
|
|
666
|
-
sleep 1
|
|
667
|
-
|
|
668
|
-
# Check if server is still running (exits if auth failed)
|
|
669
|
-
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
|
670
|
-
# Our child: its exit code says why (78 = stored credentials unreadable, which
|
|
671
|
-
# signing in again does not fix). set -e: collect it without aborting.
|
|
672
|
-
SERVER_RC=0
|
|
673
|
-
wait "$SERVER_PID" 2>/dev/null || SERVER_RC=$?
|
|
674
|
-
log "ERROR: Server failed to start (exit $SERVER_RC)"
|
|
675
|
-
echo ""
|
|
676
|
-
if [ "$SERVER_RC" = "78" ]; then
|
|
677
|
-
awk '/CodeVibe could not read its stored credentials/ { block = ""; on = 1 } on && !/^\[/ { block = block $0 "\n" } END { printf "%s", block }' "$MCP_LOG_FILE" 2>/dev/null
|
|
678
|
-
echo ""
|
|
679
|
-
echo "Server failed to start. Full log: $MCP_LOG_FILE"
|
|
680
|
-
cv_failed "credential_storage_unavailable"
|
|
681
|
-
else
|
|
682
|
-
# Show the last few lines of the log for context (e.g., auth error)
|
|
683
|
-
tail -3 "$MCP_LOG_FILE" 2>/dev/null | grep -v '^\[' | head -1
|
|
684
|
-
echo ""
|
|
685
|
-
echo "Server failed to start. Check $MCP_LOG_FILE for details."
|
|
686
|
-
cv_failed "server_died_on_startup"
|
|
687
|
-
fi
|
|
688
|
-
sleep 1
|
|
689
|
-
exit 1
|
|
690
|
-
fi
|
|
691
|
-
|
|
692
|
-
# Create tmux session and run codex
|
|
693
|
-
log "Creating tmux session: $SESSION_NAME"
|
|
694
|
-
|
|
695
|
-
# Build the codex command with proper escaping
|
|
696
|
-
CODEX_CMD="codex"
|
|
697
|
-
for arg in "${CODEX_LOCAL_ARGS[@]}" "$@"; do
|
|
698
|
-
# The sentinel prevents command substitution from stripping trailing
|
|
699
|
-
# newlines belonging to the argument. Remove only the appended sentinel.
|
|
700
|
-
escaped_arg=$(printf '%s' "$arg" | sed "s/'/'\\\\''/g"; printf x)
|
|
701
|
-
escaped_arg=${escaped_arg%x}
|
|
702
|
-
CODEX_CMD="$CODEX_CMD '$escaped_arg'"
|
|
703
|
-
done
|
|
704
|
-
|
|
705
|
-
# Create the session running codex.
|
|
706
|
-
# The inner shell writes codex's exit code to $_CV_CODEX_EXIT_FILE so the
|
|
707
|
-
# wrapper's cleanup trap can report it via `wrapper_exited` telemetry —
|
|
708
|
-
# tmux's own attach exit code is independent of the inner process exit.
|
|
709
|
-
tmux new-session -d -s "$SESSION_NAME" -x "$(tput cols)" -y "$(tput lines)" \
|
|
710
|
-
"unset QUORUM_REVIEWER_SUBPROCESS; export CODEVIBE_CODEX_TMUX_SESSION='$SESSION_NAME'; export CODEVIBE_CODEX_PRIMARY_COMPANION=1; export ENVIRONMENT='$ENVIRONMENT'; $CODEX_CMD; printf '%s' \"\$?\" > '$_CV_CODEX_EXIT_FILE'; exit"
|
|
711
|
-
_CV_TMUX_STARTED="true"
|
|
712
|
-
_CV_AGENT_INVOKED="true"
|
|
713
|
-
_CV_AGENT_STARTED_AT="$(date +%s)"
|
|
714
|
-
|
|
715
|
-
# Enable mouse support for scrolling
|
|
716
|
-
tmux set-option -t "$SESSION_NAME" -g mouse on
|
|
717
|
-
|
|
718
|
-
# Enable copy/paste with system clipboard
|
|
719
|
-
tmux set-option -t "$SESSION_NAME" set-clipboard on
|
|
720
|
-
tmux set-window-option -t "$SESSION_NAME" mode-keys vi
|
|
721
|
-
if command -v pbcopy >/dev/null 2>&1; then
|
|
722
|
-
CLIP_CMD="pbcopy"
|
|
723
|
-
elif grep -qi microsoft /proc/sys/kernel/osrelease 2>/dev/null && command -v clip.exe >/dev/null 2>&1; then
|
|
724
|
-
CLIP_CMD="clip.exe"
|
|
725
|
-
elif command -v wl-copy >/dev/null 2>&1; then
|
|
726
|
-
CLIP_CMD="wl-copy"
|
|
727
|
-
elif command -v xclip >/dev/null 2>&1; then
|
|
728
|
-
CLIP_CMD="xclip -selection clipboard"
|
|
729
|
-
fi
|
|
730
|
-
if [ -n "${CLIP_CMD:-}" ]; then
|
|
731
|
-
tmux bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "$CLIP_CMD"
|
|
732
|
-
tmux bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "$CLIP_CMD"
|
|
733
|
-
fi
|
|
734
|
-
|
|
735
|
-
# Store session mapping for mobile prompts
|
|
736
|
-
echo "$SESSION_NAME" > "${CODEVIBE_TMPDIR}/codevibe-codex-tmux-session-$$"
|
|
737
|
-
|
|
738
|
-
log "Attaching to tmux session: $SESSION_NAME"
|
|
739
|
-
|
|
740
|
-
# Attach reports the client status. Cleanup collects native status only after
|
|
741
|
-
# this exact session is absent; a detach keeps the daemon and native alive.
|
|
742
|
-
tmux attach-session -t "$SESSION_NAME"
|
|
743
|
-
|
|
744
|
-
log "Tmux client returned"
|
|
27
|
+
exec node "$PLUGIN_DIR/dist/native-launch.js" "$@"
|