clawdex-mobile 5.0.5-internal.2 → 5.0.5-internal.6
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/README.md +6 -6
- package/docs/setup-and-operations.md +13 -10
- package/package.json +1 -1
- package/scripts/setup-secure-dev.sh +52 -2
- package/scripts/setup-wizard.sh +333 -100
- package/services/rust-bridge/Cargo.lock +1 -1
- package/services/rust-bridge/Cargo.toml +1 -1
- package/services/rust-bridge/src/main.rs +120 -31
- package/vendor/bridge-binaries/darwin-arm64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/darwin-x64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-arm64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-armv7l/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-x64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/win32-x64/codex-rust-bridge.exe +0 -0
package/README.md
CHANGED
|
@@ -58,16 +58,16 @@ OpenCode is supported directly from the CLI now.
|
|
|
58
58
|
```bash
|
|
59
59
|
npm install -g opencode-ai
|
|
60
60
|
npm install -g clawdex-mobile@latest
|
|
61
|
-
clawdex init --
|
|
61
|
+
clawdex init --engines codex,opencode
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
That writes `
|
|
64
|
+
That writes `BRIDGE_ENABLED_ENGINES=codex,opencode` to `.env.secure`, so the mobile app can control both harnesses from one bridge.
|
|
65
65
|
|
|
66
66
|
Notes:
|
|
67
67
|
|
|
68
|
-
- `clawdex init` without
|
|
69
|
-
-
|
|
70
|
-
-
|
|
68
|
+
- `clawdex init` without flags now lets you multi-select harnesses in the wizard with Space, then Enter to continue.
|
|
69
|
+
- Use `clawdex init --engine codex` or `clawdex init --engine opencode` if you want a single-harness setup.
|
|
70
|
+
- Use `clawdex init --engines codex,opencode` if you want both non-interactively.
|
|
71
71
|
|
|
72
72
|
## Monorepo Development
|
|
73
73
|
|
|
@@ -89,7 +89,7 @@ Use `npm run setup:wizard -- --no-start` if you only want to write config.
|
|
|
89
89
|
|
|
90
90
|
## Main Commands
|
|
91
91
|
|
|
92
|
-
- `clawdex init [--engine codex|opencode] [--no-start]`
|
|
92
|
+
- `clawdex init [--engine codex|opencode] [--engines codex,opencode] [--no-start]`
|
|
93
93
|
- `clawdex stop`
|
|
94
94
|
- `clawdex upgrade` / `clawdex update`
|
|
95
95
|
- `clawdex version`
|
|
@@ -2,23 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
This guide is the detailed companion to the top-level `README.md`.
|
|
4
4
|
|
|
5
|
-
## Choosing
|
|
5
|
+
## Choosing Harnesses
|
|
6
6
|
|
|
7
|
-
The
|
|
7
|
+
The setup wizard now lets you choose which harnesses the phone should control.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
If you want both Codex and OpenCode:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
clawdex init --
|
|
12
|
+
clawdex init --engines codex,opencode
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
From a source checkout, the equivalent command is:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm run setup:wizard -- --
|
|
18
|
+
npm run setup:wizard -- --engines codex,opencode
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
That writes `
|
|
21
|
+
That writes `BRIDGE_ENABLED_ENGINES=codex,opencode` into `.env.secure`, so the bridge starts both backends and the mobile app can control both from one UI.
|
|
22
|
+
|
|
23
|
+
If you want only one harness, use `--engine codex` or `--engine opencode`.
|
|
22
24
|
|
|
23
25
|
## Onboarding Output Cues
|
|
24
26
|
|
|
@@ -52,7 +54,7 @@ npm run secure:setup
|
|
|
52
54
|
To generate OpenCode-first config instead:
|
|
53
55
|
|
|
54
56
|
```bash
|
|
55
|
-
|
|
57
|
+
BRIDGE_ENABLED_ENGINES=codex,opencode npm run secure:setup
|
|
56
58
|
```
|
|
57
59
|
|
|
58
60
|
Creates/updates:
|
|
@@ -69,10 +71,10 @@ npm run secure:bridge
|
|
|
69
71
|
If you want a one-off OpenCode launch without rewriting `.env.secure`:
|
|
70
72
|
|
|
71
73
|
```bash
|
|
72
|
-
|
|
74
|
+
BRIDGE_ENABLED_ENGINES=codex,opencode npm run secure:bridge
|
|
73
75
|
```
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
When both CLIs are selected, the bridge starts both backends and merges chat lists while still routing each thread by engine.
|
|
76
78
|
|
|
77
79
|
### 4) Pair from the mobile app
|
|
78
80
|
|
|
@@ -139,7 +141,8 @@ npm run teardown -- --yes
|
|
|
139
141
|
| `BRIDGE_AUTH_TOKEN` | required auth token |
|
|
140
142
|
| `BRIDGE_ALLOW_QUERY_TOKEN_AUTH` | query-token auth fallback |
|
|
141
143
|
| `CODEX_CLI_BIN` | codex executable |
|
|
142
|
-
| `BRIDGE_ACTIVE_ENGINE` | preferred backend
|
|
144
|
+
| `BRIDGE_ACTIVE_ENGINE` | internal preferred routing backend used when multiple harnesses are enabled |
|
|
145
|
+
| `BRIDGE_ENABLED_ENGINES` | selected harnesses to expose (`codex`, `opencode`, or both) |
|
|
143
146
|
| `OPENCODE_CLI_BIN` | opencode executable for dual-engine startup |
|
|
144
147
|
| `BRIDGE_OPENCODE_HOST` | loopback host for spawned opencode server |
|
|
145
148
|
| `BRIDGE_OPENCODE_PORT` | loopback port for spawned opencode server |
|
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ SECURE_ENV_FILE="$ROOT_DIR/.env.secure"
|
|
|
10
10
|
MOBILE_ENV_FILE="$ROOT_DIR/apps/mobile/.env"
|
|
11
11
|
MOBILE_ENV_EXAMPLE="$ROOT_DIR/apps/mobile/.env.example"
|
|
12
12
|
BRIDGE_ACTIVE_ENGINE="${BRIDGE_ACTIVE_ENGINE:-codex}"
|
|
13
|
+
BRIDGE_ENABLED_ENGINES="${BRIDGE_ENABLED_ENGINES:-$BRIDGE_ACTIVE_ENGINE}"
|
|
13
14
|
OPENCODE_CLI_BIN="${OPENCODE_CLI_BIN:-opencode}"
|
|
14
15
|
|
|
15
16
|
upsert_env_key() {
|
|
@@ -237,6 +238,55 @@ case "$BRIDGE_ACTIVE_ENGINE" in
|
|
|
237
238
|
;;
|
|
238
239
|
esac
|
|
239
240
|
|
|
241
|
+
validate_enabled_engines() {
|
|
242
|
+
local raw="$1"
|
|
243
|
+
local normalized=""
|
|
244
|
+
local part=""
|
|
245
|
+
local seen=","
|
|
246
|
+
local -a parts=()
|
|
247
|
+
local -a parsed=()
|
|
248
|
+
|
|
249
|
+
IFS=',' read -r -a parts <<<"$raw"
|
|
250
|
+
for part in "${parts[@]}"; do
|
|
251
|
+
normalized="$(printf '%s' "$part" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
|
252
|
+
if [[ -z "$normalized" ]]; then
|
|
253
|
+
continue
|
|
254
|
+
fi
|
|
255
|
+
case "$normalized" in
|
|
256
|
+
codex|opencode)
|
|
257
|
+
;;
|
|
258
|
+
*)
|
|
259
|
+
return 1
|
|
260
|
+
;;
|
|
261
|
+
esac
|
|
262
|
+
if [[ "$seen" == *",$normalized,"* ]]; then
|
|
263
|
+
continue
|
|
264
|
+
fi
|
|
265
|
+
parsed+=("$normalized")
|
|
266
|
+
seen="${seen}${normalized},"
|
|
267
|
+
done
|
|
268
|
+
|
|
269
|
+
if (( ${#parsed[@]} == 0 )); then
|
|
270
|
+
return 1
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
BRIDGE_ENABLED_ENGINES="$(IFS=,; printf '%s' "${parsed[*]}")"
|
|
274
|
+
return 0
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if ! validate_enabled_engines "$BRIDGE_ENABLED_ENGINES"; then
|
|
278
|
+
echo "error: BRIDGE_ENABLED_ENGINES must contain one or more of 'codex' and 'opencode'." >&2
|
|
279
|
+
exit 1
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
case ",$BRIDGE_ENABLED_ENGINES," in
|
|
283
|
+
*,"$BRIDGE_ACTIVE_ENGINE",*)
|
|
284
|
+
;;
|
|
285
|
+
*)
|
|
286
|
+
BRIDGE_ACTIVE_ENGINE="${BRIDGE_ENABLED_ENGINES%%,*}"
|
|
287
|
+
;;
|
|
288
|
+
esac
|
|
289
|
+
|
|
240
290
|
if [[ -n "$BRIDGE_HOST" ]]; then
|
|
241
291
|
HOST_SOURCE="override"
|
|
242
292
|
else
|
|
@@ -269,6 +319,7 @@ BRIDGE_PORT=$BRIDGE_PORT
|
|
|
269
319
|
BRIDGE_AUTH_TOKEN=$BRIDGE_TOKEN
|
|
270
320
|
BRIDGE_ALLOW_QUERY_TOKEN_AUTH=true
|
|
271
321
|
BRIDGE_ACTIVE_ENGINE=$BRIDGE_ACTIVE_ENGINE
|
|
322
|
+
BRIDGE_ENABLED_ENGINES=$BRIDGE_ENABLED_ENGINES
|
|
272
323
|
CODEX_CLI_BIN=codex
|
|
273
324
|
OPENCODE_CLI_BIN=$OPENCODE_CLI_BIN
|
|
274
325
|
BRIDGE_WORKDIR=$ROOT_DIR
|
|
@@ -289,8 +340,7 @@ echo ""
|
|
|
289
340
|
echo "Bridge network mode: $BRIDGE_NETWORK_MODE"
|
|
290
341
|
echo "Bridge host: $BRIDGE_HOST ($HOST_SOURCE)"
|
|
291
342
|
echo "Bridge port: $BRIDGE_PORT"
|
|
292
|
-
echo "
|
|
293
|
-
echo "Both engines will be exposed in the app when both CLIs are installed."
|
|
343
|
+
echo "Harnesses: $BRIDGE_ENABLED_ENGINES"
|
|
294
344
|
echo "Token source: $SECURE_ENV_FILE"
|
|
295
345
|
if has_local_mobile_workspace; then
|
|
296
346
|
echo "Mobile env updated: $MOBILE_ENV_FILE"
|
package/scripts/setup-wizard.sh
CHANGED
|
@@ -29,10 +29,14 @@ TAILSCALE_IP=""
|
|
|
29
29
|
BRIDGE_HOST=""
|
|
30
30
|
BRIDGE_PORT=""
|
|
31
31
|
ACTIVE_ENGINE="${BRIDGE_ACTIVE_ENGINE:-codex}"
|
|
32
|
-
|
|
32
|
+
declare -a SELECTED_ENGINES=()
|
|
33
|
+
ENGINE_SELECTION_PRESET="false"
|
|
33
34
|
AUTO_START="true"
|
|
34
35
|
SECURE_ENV_FILE="$ROOT_DIR/.env.secure"
|
|
35
36
|
MENU_RESULT=""
|
|
37
|
+
MENU_MULTI_RESULT=""
|
|
38
|
+
MENU_MULTI_PRESELECTED=""
|
|
39
|
+
declare -a MENU_MULTI_RESULT_ITEMS=()
|
|
36
40
|
SECTION_COUNT=0
|
|
37
41
|
RAIL_GLYPH="${DIM}│${RESET}"
|
|
38
42
|
RAIL_BRANCH="${DIM}├─${RESET}"
|
|
@@ -47,10 +51,6 @@ ok() { rail_echo "${GREEN}$*${RESET}"; }
|
|
|
47
51
|
fail() { printf "%s ${RED}%s${RESET}\n" "$RAIL_GLYPH" "$*" >&2; }
|
|
48
52
|
SETUP_VERBOSE_INSTALLS="${CLAWDEX_SETUP_VERBOSE:-false}"
|
|
49
53
|
|
|
50
|
-
if [[ -n "${BRIDGE_ACTIVE_ENGINE:-}" ]]; then
|
|
51
|
-
ENGINE_PRESET="true"
|
|
52
|
-
fi
|
|
53
|
-
|
|
54
54
|
run_quiet_command() {
|
|
55
55
|
local label="$1"
|
|
56
56
|
shift
|
|
@@ -83,7 +83,9 @@ Usage: $(basename "$0") [options]
|
|
|
83
83
|
Options:
|
|
84
84
|
--no-start Configure everything but do not start bridge
|
|
85
85
|
--engine <codex|opencode>
|
|
86
|
-
|
|
86
|
+
Select a single harness non-interactively
|
|
87
|
+
--engines <codex,opencode>
|
|
88
|
+
Select one or more harnesses non-interactively
|
|
87
89
|
-h, --help Show this help
|
|
88
90
|
EOF
|
|
89
91
|
}
|
|
@@ -110,20 +112,147 @@ format_engine_name() {
|
|
|
110
112
|
esac
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
|
|
115
|
+
parse_engine_list_csv() {
|
|
116
|
+
local raw="$1"
|
|
117
|
+
local normalized=""
|
|
118
|
+
local seen=","
|
|
119
|
+
local part=""
|
|
120
|
+
local -a parts=()
|
|
121
|
+
local -a parsed=()
|
|
122
|
+
|
|
123
|
+
IFS=',' read -r -a parts <<<"$raw"
|
|
124
|
+
for part in "${parts[@]}"; do
|
|
125
|
+
normalized="$(printf '%s' "$part" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
|
126
|
+
if [[ -z "$normalized" ]]; then
|
|
127
|
+
continue
|
|
128
|
+
fi
|
|
129
|
+
if ! validate_engine_name "$normalized"; then
|
|
130
|
+
return 1
|
|
131
|
+
fi
|
|
132
|
+
if [[ "$seen" == *",$normalized,"* ]]; then
|
|
133
|
+
continue
|
|
134
|
+
fi
|
|
135
|
+
parsed+=("$normalized")
|
|
136
|
+
seen="${seen}${normalized},"
|
|
137
|
+
done
|
|
138
|
+
|
|
139
|
+
if (( ${#parsed[@]} == 0 )); then
|
|
140
|
+
return 1
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
SELECTED_ENGINES=("${parsed[@]}")
|
|
144
|
+
return 0
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
parse_existing_engine_list_csv() {
|
|
148
|
+
local raw="$1"
|
|
149
|
+
local normalized=""
|
|
150
|
+
local seen=","
|
|
151
|
+
local part=""
|
|
152
|
+
local -a parts=()
|
|
153
|
+
local -a parsed=()
|
|
154
|
+
|
|
155
|
+
IFS=',' read -r -a parts <<<"$raw"
|
|
156
|
+
for part in "${parts[@]}"; do
|
|
157
|
+
normalized="$(printf '%s' "$part" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
|
158
|
+
if [[ -z "$normalized" ]]; then
|
|
159
|
+
continue
|
|
160
|
+
fi
|
|
161
|
+
if ! validate_engine_name "$normalized"; then
|
|
162
|
+
continue
|
|
163
|
+
fi
|
|
164
|
+
if [[ "$seen" == *",$normalized,"* ]]; then
|
|
165
|
+
continue
|
|
166
|
+
fi
|
|
167
|
+
parsed+=("$normalized")
|
|
168
|
+
seen="${seen}${normalized},"
|
|
169
|
+
done
|
|
170
|
+
|
|
171
|
+
if (( ${#parsed[@]} == 0 )); then
|
|
172
|
+
return 1
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
SELECTED_ENGINES=("${parsed[@]}")
|
|
176
|
+
return 0
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
engine_list_contains() {
|
|
180
|
+
local needle="$1"
|
|
181
|
+
shift
|
|
182
|
+
local value=""
|
|
183
|
+
for value in "$@"; do
|
|
184
|
+
if [[ "$value" == "$needle" ]]; then
|
|
185
|
+
return 0
|
|
186
|
+
fi
|
|
187
|
+
done
|
|
188
|
+
return 1
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
sync_active_engine_from_selection() {
|
|
192
|
+
if (( ${#SELECTED_ENGINES[@]} == 0 )); then
|
|
193
|
+
ACTIVE_ENGINE="codex"
|
|
194
|
+
return
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
if engine_list_contains "$ACTIVE_ENGINE" "${SELECTED_ENGINES[@]}"; then
|
|
198
|
+
return
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
ACTIVE_ENGINE="${SELECTED_ENGINES[0]}"
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
format_engine_list() {
|
|
205
|
+
local engine=""
|
|
206
|
+
local result=""
|
|
207
|
+
|
|
208
|
+
for engine in "$@"; do
|
|
209
|
+
if [[ -n "$result" ]]; then
|
|
210
|
+
result+=", "
|
|
211
|
+
fi
|
|
212
|
+
result+="$(format_engine_name "$engine")"
|
|
213
|
+
done
|
|
214
|
+
|
|
215
|
+
printf '%s' "$result"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
engine_from_menu_label() {
|
|
114
219
|
case "$1" in
|
|
115
|
-
|
|
116
|
-
printf 'opencode'
|
|
117
|
-
;;
|
|
118
|
-
opencode)
|
|
220
|
+
"Codex")
|
|
119
221
|
printf 'codex'
|
|
120
222
|
;;
|
|
223
|
+
"OpenCode")
|
|
224
|
+
printf 'opencode'
|
|
225
|
+
;;
|
|
121
226
|
*)
|
|
122
227
|
return 1
|
|
123
228
|
;;
|
|
124
229
|
esac
|
|
125
230
|
}
|
|
126
231
|
|
|
232
|
+
load_existing_engine_selection() {
|
|
233
|
+
local raw=""
|
|
234
|
+
local engine=""
|
|
235
|
+
|
|
236
|
+
raw="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ENABLED_ENGINES")"
|
|
237
|
+
if [[ -n "$raw" ]] && parse_existing_engine_list_csv "$raw"; then
|
|
238
|
+
engine="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ACTIVE_ENGINE")"
|
|
239
|
+
if validate_engine_name "$engine"; then
|
|
240
|
+
ACTIVE_ENGINE="$engine"
|
|
241
|
+
else
|
|
242
|
+
ACTIVE_ENGINE="${SELECTED_ENGINES[0]}"
|
|
243
|
+
fi
|
|
244
|
+
sync_active_engine_from_selection
|
|
245
|
+
return 0
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
engine="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ACTIVE_ENGINE")"
|
|
249
|
+
if ! validate_engine_name "$engine"; then
|
|
250
|
+
engine="codex"
|
|
251
|
+
fi
|
|
252
|
+
SELECTED_ENGINES=("$engine")
|
|
253
|
+
ACTIVE_ENGINE="$engine"
|
|
254
|
+
}
|
|
255
|
+
|
|
127
256
|
parse_args() {
|
|
128
257
|
while (($# > 0)); do
|
|
129
258
|
case "$1" in
|
|
@@ -143,7 +272,23 @@ parse_args() {
|
|
|
143
272
|
exit 1
|
|
144
273
|
fi
|
|
145
274
|
ACTIVE_ENGINE="$2"
|
|
146
|
-
|
|
275
|
+
SELECTED_ENGINES=("$2")
|
|
276
|
+
ENGINE_SELECTION_PRESET="true"
|
|
277
|
+
shift 2
|
|
278
|
+
;;
|
|
279
|
+
--engines)
|
|
280
|
+
if (($# < 2)); then
|
|
281
|
+
echo "error: --engines requires a comma-separated value (for example: codex,opencode)." >&2
|
|
282
|
+
print_usage >&2
|
|
283
|
+
exit 1
|
|
284
|
+
fi
|
|
285
|
+
if ! parse_engine_list_csv "$2"; then
|
|
286
|
+
echo "error: unsupported --engines value '$2'. Use one or more of 'codex' and 'opencode'." >&2
|
|
287
|
+
print_usage >&2
|
|
288
|
+
exit 1
|
|
289
|
+
fi
|
|
290
|
+
ACTIVE_ENGINE="${SELECTED_ENGINES[0]}"
|
|
291
|
+
ENGINE_SELECTION_PRESET="true"
|
|
147
292
|
shift 2
|
|
148
293
|
;;
|
|
149
294
|
-h|--help)
|
|
@@ -499,6 +644,123 @@ menu_select() {
|
|
|
499
644
|
printf "\r\033[2K%s %s %s: %s\n" "$rail" "$branch" "$prompt" "$MENU_RESULT"
|
|
500
645
|
}
|
|
501
646
|
|
|
647
|
+
menu_multiselect() {
|
|
648
|
+
local prompt="$1"
|
|
649
|
+
shift
|
|
650
|
+
local -a options=("$@")
|
|
651
|
+
local option_count="${#options[@]}"
|
|
652
|
+
local selected=0
|
|
653
|
+
local lines_to_render=$((option_count + 3))
|
|
654
|
+
local i=0
|
|
655
|
+
local key=""
|
|
656
|
+
local key_rest=""
|
|
657
|
+
local rail="$RAIL_GLYPH"
|
|
658
|
+
local branch="$RAIL_BRANCH"
|
|
659
|
+
local child="$RAIL_CHILD"
|
|
660
|
+
local help_text="${DIM}Space toggles. Enter continues.${RESET}"
|
|
661
|
+
local summary=""
|
|
662
|
+
local mark=""
|
|
663
|
+
local selected_count=0
|
|
664
|
+
local option=""
|
|
665
|
+
local preselected=",${MENU_MULTI_PRESELECTED},"
|
|
666
|
+
local -a checked=()
|
|
667
|
+
|
|
668
|
+
if (( option_count == 0 )); then
|
|
669
|
+
abort_wizard "menu_multiselect requires at least one option."
|
|
670
|
+
fi
|
|
671
|
+
|
|
672
|
+
for option in "${options[@]}"; do
|
|
673
|
+
if [[ "$preselected" == *",$option,"* ]]; then
|
|
674
|
+
checked+=(1)
|
|
675
|
+
else
|
|
676
|
+
checked+=(0)
|
|
677
|
+
fi
|
|
678
|
+
done
|
|
679
|
+
|
|
680
|
+
tput civis >/dev/null 2>&1 || true
|
|
681
|
+
|
|
682
|
+
while true; do
|
|
683
|
+
printf "\r\033[2K%s\n" "$rail"
|
|
684
|
+
printf "\r\033[2K%s %s %s\n" "$rail" "$branch" "$prompt"
|
|
685
|
+
for ((i = 0; i < option_count; i++)); do
|
|
686
|
+
if (( checked[i] == 1 )); then
|
|
687
|
+
mark="[x]"
|
|
688
|
+
else
|
|
689
|
+
mark="[ ]"
|
|
690
|
+
fi
|
|
691
|
+
|
|
692
|
+
if (( i == selected )); then
|
|
693
|
+
printf "\r\033[2K%s %s ${GREEN}%s${RESET} %s\n" "$rail" "$child" "$mark" "${options[$i]}"
|
|
694
|
+
else
|
|
695
|
+
printf "\r\033[2K%s %s ${DIM}%s${RESET} %s\n" "$rail" "$child" "$mark" "${options[$i]}"
|
|
696
|
+
fi
|
|
697
|
+
done
|
|
698
|
+
printf "\r\033[2K%s %s %s\n" "$rail" "$child" "$help_text"
|
|
699
|
+
|
|
700
|
+
IFS= read -rsn1 key || abort_wizard
|
|
701
|
+
|
|
702
|
+
if [[ "$key" == $'\x03' ]]; then
|
|
703
|
+
abort_wizard
|
|
704
|
+
fi
|
|
705
|
+
|
|
706
|
+
if [[ "$key" == $'\x1b' ]]; then
|
|
707
|
+
key_rest=""
|
|
708
|
+
IFS= read -rsn2 key_rest || true
|
|
709
|
+
key+="$key_rest"
|
|
710
|
+
fi
|
|
711
|
+
|
|
712
|
+
case "$key" in
|
|
713
|
+
"")
|
|
714
|
+
selected_count=0
|
|
715
|
+
for ((i = 0; i < option_count; i++)); do
|
|
716
|
+
if (( checked[i] == 1 )); then
|
|
717
|
+
selected_count=$((selected_count + 1))
|
|
718
|
+
fi
|
|
719
|
+
done
|
|
720
|
+
if (( selected_count > 0 )); then
|
|
721
|
+
break
|
|
722
|
+
fi
|
|
723
|
+
help_text="${YELLOW}Select at least one option. Space toggles. Enter continues.${RESET}"
|
|
724
|
+
;;
|
|
725
|
+
" ")
|
|
726
|
+
checked[selected]=$((1 - checked[selected]))
|
|
727
|
+
help_text="${DIM}Space toggles. Enter continues.${RESET}"
|
|
728
|
+
;;
|
|
729
|
+
$'\x1b[A'|k|K)
|
|
730
|
+
selected=$(((selected - 1 + option_count) % option_count))
|
|
731
|
+
;;
|
|
732
|
+
$'\x1b[B'|j|J)
|
|
733
|
+
selected=$(((selected + 1) % option_count))
|
|
734
|
+
;;
|
|
735
|
+
q|Q)
|
|
736
|
+
abort_wizard
|
|
737
|
+
;;
|
|
738
|
+
*)
|
|
739
|
+
;;
|
|
740
|
+
esac
|
|
741
|
+
|
|
742
|
+
printf "\033[%dA" "$lines_to_render"
|
|
743
|
+
done
|
|
744
|
+
|
|
745
|
+
MENU_MULTI_RESULT_ITEMS=()
|
|
746
|
+
for ((i = 0; i < option_count; i++)); do
|
|
747
|
+
if (( checked[i] == 1 )); then
|
|
748
|
+
MENU_MULTI_RESULT_ITEMS+=("${options[$i]}")
|
|
749
|
+
fi
|
|
750
|
+
done
|
|
751
|
+
|
|
752
|
+
MENU_MULTI_RESULT="$(IFS=,; printf '%s' "${MENU_MULTI_RESULT_ITEMS[*]}")"
|
|
753
|
+
summary="$(IFS=', '; printf '%s' "${MENU_MULTI_RESULT_ITEMS[*]}")"
|
|
754
|
+
|
|
755
|
+
tput cnorm >/dev/null 2>&1 || true
|
|
756
|
+
printf "\033[%dA" "$lines_to_render"
|
|
757
|
+
for ((i = 0; i < lines_to_render; i++)); do
|
|
758
|
+
printf "\r\033[2K\n"
|
|
759
|
+
done
|
|
760
|
+
printf "\033[%dA" "$lines_to_render"
|
|
761
|
+
printf "\r\033[2K%s %s %s: %s\n" "$rail" "$branch" "$prompt" "$summary"
|
|
762
|
+
}
|
|
763
|
+
|
|
502
764
|
confirm_prompt() {
|
|
503
765
|
local prompt="$1"
|
|
504
766
|
local default="${2:-N}"
|
|
@@ -687,7 +949,7 @@ print_existing_setup_summary() {
|
|
|
687
949
|
local port=""
|
|
688
950
|
local token=""
|
|
689
951
|
local network_mode=""
|
|
690
|
-
local
|
|
952
|
+
local harnesses=""
|
|
691
953
|
local source_path=""
|
|
692
954
|
|
|
693
955
|
if [[ ! -f "$SECURE_ENV_FILE" ]]; then
|
|
@@ -698,9 +960,14 @@ print_existing_setup_summary() {
|
|
|
698
960
|
port="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_PORT")"
|
|
699
961
|
token="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_AUTH_TOKEN")"
|
|
700
962
|
network_mode="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_NETWORK_MODE")"
|
|
701
|
-
|
|
702
|
-
if !
|
|
703
|
-
|
|
963
|
+
harnesses="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ENABLED_ENGINES")"
|
|
964
|
+
if [[ -n "$harnesses" ]] && ! parse_existing_engine_list_csv "$harnesses"; then
|
|
965
|
+
harnesses=""
|
|
966
|
+
elif [[ -n "$harnesses" ]]; then
|
|
967
|
+
harnesses="$(IFS=,; printf '%s' "${SELECTED_ENGINES[*]}")"
|
|
968
|
+
fi
|
|
969
|
+
if [[ -z "$harnesses" ]]; then
|
|
970
|
+
harnesses="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ACTIVE_ENGINE")"
|
|
704
971
|
fi
|
|
705
972
|
|
|
706
973
|
if [[ -z "$host" ]] && [[ -z "$port" ]] && [[ -z "$token" ]]; then
|
|
@@ -717,8 +984,8 @@ print_existing_setup_summary() {
|
|
|
717
984
|
if [[ -n "$network_mode" ]]; then
|
|
718
985
|
echo "bridge.networkMode: $network_mode"
|
|
719
986
|
fi
|
|
720
|
-
if [[ -n "$
|
|
721
|
-
echo "bridge.
|
|
987
|
+
if [[ -n "$harnesses" ]]; then
|
|
988
|
+
echo "bridge.harnesses: $harnesses"
|
|
722
989
|
fi
|
|
723
990
|
if [[ -n "$token" ]]; then
|
|
724
991
|
echo "bridge.token: present"
|
|
@@ -803,22 +1070,28 @@ choose_bridge_network_mode() {
|
|
|
803
1070
|
}
|
|
804
1071
|
|
|
805
1072
|
choose_runtime_engine() {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
"
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1073
|
+
local label=""
|
|
1074
|
+
MENU_MULTI_PRESELECTED="Codex"
|
|
1075
|
+
if engine_list_contains "codex" "${SELECTED_ENGINES[@]}"; then
|
|
1076
|
+
MENU_MULTI_PRESELECTED="Codex"
|
|
1077
|
+
else
|
|
1078
|
+
MENU_MULTI_PRESELECTED=""
|
|
1079
|
+
fi
|
|
1080
|
+
if engine_list_contains "opencode" "${SELECTED_ENGINES[@]}"; then
|
|
1081
|
+
if [[ -n "$MENU_MULTI_PRESELECTED" ]]; then
|
|
1082
|
+
MENU_MULTI_PRESELECTED+=","
|
|
1083
|
+
fi
|
|
1084
|
+
MENU_MULTI_PRESELECTED+="OpenCode"
|
|
1085
|
+
fi
|
|
1086
|
+
|
|
1087
|
+
info "Select the harnesses this phone should control."
|
|
1088
|
+
menu_multiselect "Harnesses to control" "Codex" "OpenCode"
|
|
1089
|
+
SELECTED_ENGINES=()
|
|
1090
|
+
for label in "${MENU_MULTI_RESULT_ITEMS[@]}"; do
|
|
1091
|
+
SELECTED_ENGINES+=("$(engine_from_menu_label "$label")")
|
|
1092
|
+
done
|
|
1093
|
+
sync_active_engine_from_selection
|
|
1094
|
+
info "Selected harnesses: $(format_engine_list "${SELECTED_ENGINES[@]}")."
|
|
822
1095
|
}
|
|
823
1096
|
|
|
824
1097
|
infer_network_mode_from_host() {
|
|
@@ -932,55 +1205,21 @@ ensure_opencode_cli() {
|
|
|
932
1205
|
fi
|
|
933
1206
|
}
|
|
934
1207
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
opencode)
|
|
941
|
-
ensure_opencode_cli
|
|
942
|
-
;;
|
|
943
|
-
*)
|
|
944
|
-
abort_wizard "Unsupported preferred engine '$ACTIVE_ENGINE'."
|
|
945
|
-
;;
|
|
946
|
-
esac
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
ensure_optional_secondary_engine_cli() {
|
|
950
|
-
local secondary_engine=""
|
|
951
|
-
|
|
952
|
-
secondary_engine="$(secondary_engine_name "$ACTIVE_ENGINE")" || return 0
|
|
953
|
-
|
|
954
|
-
case "$secondary_engine" in
|
|
955
|
-
codex)
|
|
956
|
-
if command -v codex >/dev/null 2>&1; then
|
|
957
|
-
info "Codex is also installed. The bridge will expose both engines."
|
|
958
|
-
return 0
|
|
959
|
-
fi
|
|
960
|
-
|
|
961
|
-
if confirm_prompt "Install Codex too so both engines are available in the app?" "N"; then
|
|
1208
|
+
ensure_selected_engine_clis() {
|
|
1209
|
+
local engine=""
|
|
1210
|
+
for engine in "${SELECTED_ENGINES[@]}"; do
|
|
1211
|
+
case "$engine" in
|
|
1212
|
+
codex)
|
|
962
1213
|
ensure_codex_cli
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
info "Continuing without Codex. Install it later if you want both engines."
|
|
966
|
-
fi
|
|
967
|
-
;;
|
|
968
|
-
opencode)
|
|
969
|
-
if command -v opencode >/dev/null 2>&1; then
|
|
970
|
-
info "OpenCode is also installed. The bridge will expose both engines."
|
|
971
|
-
return 0
|
|
972
|
-
fi
|
|
973
|
-
|
|
974
|
-
if confirm_prompt "Install OpenCode too so both engines are available in the app?" "N"; then
|
|
1214
|
+
;;
|
|
1215
|
+
opencode)
|
|
975
1216
|
ensure_opencode_cli
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
;;
|
|
983
|
-
esac
|
|
1217
|
+
;;
|
|
1218
|
+
*)
|
|
1219
|
+
abort_wizard "Unsupported harness '$engine'."
|
|
1220
|
+
;;
|
|
1221
|
+
esac
|
|
1222
|
+
done
|
|
984
1223
|
}
|
|
985
1224
|
|
|
986
1225
|
ensure_tailscale_cli() {
|
|
@@ -1362,29 +1601,24 @@ if [[ "$CONFIG_ACTION" == "reset" ]]; then
|
|
|
1362
1601
|
ok "Previous secure config removed: $SECURE_ENV_FILE"
|
|
1363
1602
|
fi
|
|
1364
1603
|
|
|
1365
|
-
|
|
1366
|
-
EXISTING_ACTIVE_ENGINE="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ACTIVE_ENGINE")"
|
|
1367
|
-
if ! validate_engine_name "$EXISTING_ACTIVE_ENGINE"; then
|
|
1368
|
-
EXISTING_ACTIVE_ENGINE="codex"
|
|
1369
|
-
fi
|
|
1604
|
+
load_existing_engine_selection
|
|
1370
1605
|
if [[ "$CONFIG_ACTION" == "keep" ]]; then
|
|
1371
|
-
if [[ "$
|
|
1372
|
-
|
|
1373
|
-
info "Keeping existing default engine: $(format_engine_name "$ACTIVE_ENGINE")."
|
|
1606
|
+
if [[ "$ENGINE_SELECTION_PRESET" == "false" ]]; then
|
|
1607
|
+
info "Keeping existing harnesses: $(format_engine_list "${SELECTED_ENGINES[@]}")."
|
|
1374
1608
|
else
|
|
1375
|
-
info "
|
|
1609
|
+
info "Harness selection preset via flag: $(format_engine_list "${SELECTED_ENGINES[@]}")."
|
|
1376
1610
|
fi
|
|
1377
1611
|
else
|
|
1378
|
-
|
|
1612
|
+
section "Harnesses"
|
|
1613
|
+
if [[ "$ENGINE_SELECTION_PRESET" == "false" ]]; then
|
|
1379
1614
|
choose_runtime_engine
|
|
1380
1615
|
else
|
|
1381
|
-
info "
|
|
1616
|
+
info "Harness selection preset via flag: $(format_engine_list "${SELECTED_ENGINES[@]}")."
|
|
1382
1617
|
fi
|
|
1383
1618
|
fi
|
|
1384
1619
|
|
|
1385
1620
|
section "Runtime dependency"
|
|
1386
|
-
|
|
1387
|
-
ensure_optional_secondary_engine_cli
|
|
1621
|
+
ensure_selected_engine_clis
|
|
1388
1622
|
|
|
1389
1623
|
if [[ "$CONFIG_ACTION" != "keep" ]]; then
|
|
1390
1624
|
section "Bridge network mode"
|
|
@@ -1409,7 +1643,7 @@ if [[ "$CONFIG_ACTION" != "keep" ]]; then
|
|
|
1409
1643
|
esac
|
|
1410
1644
|
|
|
1411
1645
|
section "Write secure config"
|
|
1412
|
-
BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" "$SCRIPT_DIR/setup-secure-dev.sh"
|
|
1646
|
+
BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" BRIDGE_ENABLED_ENGINES="$(IFS=,; printf '%s' "${SELECTED_ENGINES[*]}")" "$SCRIPT_DIR/setup-secure-dev.sh"
|
|
1413
1647
|
else
|
|
1414
1648
|
ok "Keeping existing secure config."
|
|
1415
1649
|
NETWORK_MODE="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_NETWORK_MODE")"
|
|
@@ -1434,10 +1668,10 @@ else
|
|
|
1434
1668
|
fi
|
|
1435
1669
|
|
|
1436
1670
|
section "Write secure config"
|
|
1437
|
-
BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" "$SCRIPT_DIR/setup-secure-dev.sh"
|
|
1438
|
-
elif [[ "$
|
|
1671
|
+
BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" BRIDGE_ENABLED_ENGINES="$(IFS=,; printf '%s' "${SELECTED_ENGINES[*]}")" "$SCRIPT_DIR/setup-secure-dev.sh"
|
|
1672
|
+
elif [[ "$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ENABLED_ENGINES")" != "$(IFS=,; printf '%s' "${SELECTED_ENGINES[*]}")" ]]; then
|
|
1439
1673
|
section "Write secure config"
|
|
1440
|
-
BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" "$SCRIPT_DIR/setup-secure-dev.sh"
|
|
1674
|
+
BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" BRIDGE_ENABLED_ENGINES="$(IFS=,; printf '%s' "${SELECTED_ENGINES[*]}")" "$SCRIPT_DIR/setup-secure-dev.sh"
|
|
1441
1675
|
fi
|
|
1442
1676
|
fi
|
|
1443
1677
|
|
|
@@ -1467,8 +1701,7 @@ BRIDGE_PORT="${BRIDGE_PORT:-8787}"
|
|
|
1467
1701
|
section "Summary"
|
|
1468
1702
|
rail_echo "Bridge mode: $NETWORK_MODE"
|
|
1469
1703
|
rail_echo "Bridge endpoint: http://$BRIDGE_HOST:$BRIDGE_PORT"
|
|
1470
|
-
rail_echo "
|
|
1471
|
-
rail_echo "${DIM}Both engines appear in the app when both CLIs are installed.${RESET}"
|
|
1704
|
+
rail_echo "Harnesses: $(format_engine_list "${SELECTED_ENGINES[@]}")"
|
|
1472
1705
|
rail_echo "Secure env: $SECURE_ENV_FILE"
|
|
1473
1706
|
if [[ "$FLOW" == "quickstart" ]]; then
|
|
1474
1707
|
rail_echo "${DIM}Tip: re-run with Manual mode for full control at each step.${RESET}"
|
|
@@ -75,6 +75,7 @@ struct BridgeConfig {
|
|
|
75
75
|
cli_bin: String,
|
|
76
76
|
opencode_cli_bin: String,
|
|
77
77
|
active_engine: BridgeRuntimeEngine,
|
|
78
|
+
enabled_engines: Vec<BridgeRuntimeEngine>,
|
|
78
79
|
opencode_host: String,
|
|
79
80
|
opencode_port: u16,
|
|
80
81
|
opencode_server_username: String,
|
|
@@ -105,11 +106,18 @@ impl BridgeConfig {
|
|
|
105
106
|
let cli_bin = env::var("CODEX_CLI_BIN").unwrap_or_else(|_| "codex".to_string());
|
|
106
107
|
let opencode_cli_bin =
|
|
107
108
|
env::var("OPENCODE_CLI_BIN").unwrap_or_else(|_| "opencode".to_string());
|
|
108
|
-
let
|
|
109
|
+
let requested_active_engine = match env::var("BRIDGE_ACTIVE_ENGINE") {
|
|
109
110
|
Ok(raw) => parse_bridge_runtime_engine(raw.trim())
|
|
110
111
|
.ok_or_else(|| format!("unsupported BRIDGE_ACTIVE_ENGINE value: {raw}"))?,
|
|
111
112
|
Err(_) => BridgeRuntimeEngine::Codex,
|
|
112
113
|
};
|
|
114
|
+
let enabled_engines = parse_enabled_bridge_engines_env()?
|
|
115
|
+
.unwrap_or_else(|| legacy_default_enabled_engines(requested_active_engine));
|
|
116
|
+
let active_engine = if enabled_engines.contains(&requested_active_engine) {
|
|
117
|
+
requested_active_engine
|
|
118
|
+
} else {
|
|
119
|
+
enabled_engines[0]
|
|
120
|
+
};
|
|
113
121
|
let opencode_host =
|
|
114
122
|
env::var("BRIDGE_OPENCODE_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
|
|
115
123
|
let opencode_port = env::var("BRIDGE_OPENCODE_PORT")
|
|
@@ -159,6 +167,7 @@ impl BridgeConfig {
|
|
|
159
167
|
cli_bin,
|
|
160
168
|
opencode_cli_bin,
|
|
161
169
|
active_engine,
|
|
170
|
+
enabled_engines,
|
|
162
171
|
opencode_host,
|
|
163
172
|
opencode_port,
|
|
164
173
|
opencode_server_username,
|
|
@@ -224,7 +233,7 @@ struct AppState {
|
|
|
224
233
|
}
|
|
225
234
|
|
|
226
235
|
#[allow(dead_code)]
|
|
227
|
-
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
|
|
236
|
+
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)]
|
|
228
237
|
#[serde(rename_all = "lowercase")]
|
|
229
238
|
enum BridgeRuntimeEngine {
|
|
230
239
|
Codex,
|
|
@@ -267,45 +276,57 @@ struct RuntimeBackend {
|
|
|
267
276
|
impl RuntimeBackend {
|
|
268
277
|
async fn start(config: &Arc<BridgeConfig>, hub: Arc<ClientHub>) -> Result<Arc<Self>, String> {
|
|
269
278
|
let preferred_engine = config.active_engine;
|
|
279
|
+
let codex_enabled = config.enabled_engines.contains(&BridgeRuntimeEngine::Codex);
|
|
280
|
+
let opencode_enabled = config
|
|
281
|
+
.enabled_engines
|
|
282
|
+
.contains(&BridgeRuntimeEngine::Opencode);
|
|
270
283
|
let mut codex = None;
|
|
271
284
|
let mut opencode = None;
|
|
272
285
|
|
|
273
286
|
match preferred_engine {
|
|
274
287
|
BridgeRuntimeEngine::Codex => {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
288
|
+
if codex_enabled {
|
|
289
|
+
let app_server = AppServerBridge::start(
|
|
290
|
+
&config.cli_bin,
|
|
291
|
+
BridgeRuntimeEngine::Codex,
|
|
292
|
+
hub.clone(),
|
|
293
|
+
)
|
|
294
|
+
.await?;
|
|
295
|
+
spawn_rollout_live_sync(hub.clone());
|
|
296
|
+
codex = Some(app_server);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if opencode_enabled {
|
|
300
|
+
match OpencodeBackend::start(config, hub).await {
|
|
301
|
+
Ok(backend) => opencode = Some(backend),
|
|
302
|
+
Err(error) => eprintln!(
|
|
303
|
+
"opencode backend unavailable; continuing with selected harnesses only: {error}"
|
|
304
|
+
),
|
|
305
|
+
}
|
|
289
306
|
}
|
|
290
307
|
}
|
|
291
308
|
BridgeRuntimeEngine::Opencode => {
|
|
292
|
-
|
|
293
|
-
|
|
309
|
+
if opencode_enabled {
|
|
310
|
+
let backend = OpencodeBackend::start(config, hub.clone()).await?;
|
|
311
|
+
opencode = Some(backend);
|
|
312
|
+
}
|
|
294
313
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
314
|
+
if codex_enabled {
|
|
315
|
+
match AppServerBridge::start(
|
|
316
|
+
&config.cli_bin,
|
|
317
|
+
BridgeRuntimeEngine::Codex,
|
|
318
|
+
hub.clone(),
|
|
319
|
+
)
|
|
320
|
+
.await
|
|
321
|
+
{
|
|
322
|
+
Ok(app_server) => {
|
|
323
|
+
spawn_rollout_live_sync(hub);
|
|
324
|
+
codex = Some(app_server);
|
|
325
|
+
}
|
|
326
|
+
Err(error) => eprintln!(
|
|
327
|
+
"codex backend unavailable; continuing with selected harnesses only: {error}"
|
|
328
|
+
),
|
|
305
329
|
}
|
|
306
|
-
Err(error) => eprintln!(
|
|
307
|
-
"codex backend unavailable; continuing with opencode only: {error}"
|
|
308
|
-
),
|
|
309
330
|
}
|
|
310
331
|
}
|
|
311
332
|
}
|
|
@@ -5348,6 +5369,53 @@ fn parse_csv_env(name: &str, fallback: &[&str]) -> HashSet<String> {
|
|
|
5348
5369
|
}
|
|
5349
5370
|
}
|
|
5350
5371
|
|
|
5372
|
+
fn parse_enabled_bridge_engines_csv(raw: &str) -> Result<Vec<BridgeRuntimeEngine>, String> {
|
|
5373
|
+
let mut parsed = Vec::new();
|
|
5374
|
+
let mut seen = HashSet::new();
|
|
5375
|
+
for entry in raw.split(',') {
|
|
5376
|
+
let normalized = entry.trim().to_ascii_lowercase();
|
|
5377
|
+
if normalized.is_empty() {
|
|
5378
|
+
continue;
|
|
5379
|
+
}
|
|
5380
|
+
let Some(engine) = parse_bridge_runtime_engine(&normalized) else {
|
|
5381
|
+
continue;
|
|
5382
|
+
};
|
|
5383
|
+
if seen.insert(engine) {
|
|
5384
|
+
parsed.push(engine);
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5387
|
+
|
|
5388
|
+
if parsed.is_empty() {
|
|
5389
|
+
return Err(
|
|
5390
|
+
"BRIDGE_ENABLED_ENGINES must include one or more of: codex, opencode".to_string(),
|
|
5391
|
+
);
|
|
5392
|
+
}
|
|
5393
|
+
|
|
5394
|
+
Ok(parsed)
|
|
5395
|
+
}
|
|
5396
|
+
|
|
5397
|
+
fn parse_enabled_bridge_engines_env() -> Result<Option<Vec<BridgeRuntimeEngine>>, String> {
|
|
5398
|
+
let raw = match env::var("BRIDGE_ENABLED_ENGINES") {
|
|
5399
|
+
Ok(raw) => raw,
|
|
5400
|
+
Err(_) => return Ok(None),
|
|
5401
|
+
};
|
|
5402
|
+
|
|
5403
|
+
Ok(Some(parse_enabled_bridge_engines_csv(&raw)?))
|
|
5404
|
+
}
|
|
5405
|
+
|
|
5406
|
+
fn legacy_default_enabled_engines(
|
|
5407
|
+
requested_active_engine: BridgeRuntimeEngine,
|
|
5408
|
+
) -> Vec<BridgeRuntimeEngine> {
|
|
5409
|
+
match requested_active_engine {
|
|
5410
|
+
BridgeRuntimeEngine::Codex => {
|
|
5411
|
+
vec![BridgeRuntimeEngine::Codex, BridgeRuntimeEngine::Opencode]
|
|
5412
|
+
}
|
|
5413
|
+
BridgeRuntimeEngine::Opencode => {
|
|
5414
|
+
vec![BridgeRuntimeEngine::Opencode, BridgeRuntimeEngine::Codex]
|
|
5415
|
+
}
|
|
5416
|
+
}
|
|
5417
|
+
}
|
|
5418
|
+
|
|
5351
5419
|
impl BridgeRuntimeEngine {
|
|
5352
5420
|
fn as_str(self) -> &'static str {
|
|
5353
5421
|
match self {
|
|
@@ -7399,6 +7467,7 @@ mod tests {
|
|
|
7399
7467
|
cli_bin: "cat".to_string(),
|
|
7400
7468
|
opencode_cli_bin: "opencode".to_string(),
|
|
7401
7469
|
active_engine: BridgeRuntimeEngine::Codex,
|
|
7470
|
+
enabled_engines: vec![BridgeRuntimeEngine::Codex, BridgeRuntimeEngine::Opencode],
|
|
7402
7471
|
opencode_host: "127.0.0.1".to_string(),
|
|
7403
7472
|
opencode_port: 4090,
|
|
7404
7473
|
opencode_server_username: "opencode".to_string(),
|
|
@@ -7969,6 +8038,25 @@ mod tests {
|
|
|
7969
8038
|
shutdown_test_backend(&state.backend).await;
|
|
7970
8039
|
}
|
|
7971
8040
|
|
|
8041
|
+
#[test]
|
|
8042
|
+
fn parse_enabled_bridge_engines_csv_preserves_order_and_removes_duplicates() {
|
|
8043
|
+
let parsed =
|
|
8044
|
+
parse_enabled_bridge_engines_csv("opencode,codex,opencode").expect("engine csv");
|
|
8045
|
+
assert_eq!(
|
|
8046
|
+
parsed,
|
|
8047
|
+
vec![BridgeRuntimeEngine::Opencode, BridgeRuntimeEngine::Codex]
|
|
8048
|
+
);
|
|
8049
|
+
}
|
|
8050
|
+
|
|
8051
|
+
#[test]
|
|
8052
|
+
fn parse_enabled_bridge_engines_csv_ignores_unknown_entries() {
|
|
8053
|
+
let parsed = parse_enabled_bridge_engines_csv("codex,t3code,opencode").expect("engine csv");
|
|
8054
|
+
assert_eq!(
|
|
8055
|
+
parsed,
|
|
8056
|
+
vec![BridgeRuntimeEngine::Codex, BridgeRuntimeEngine::Opencode]
|
|
8057
|
+
);
|
|
8058
|
+
}
|
|
8059
|
+
|
|
7972
8060
|
#[tokio::test]
|
|
7973
8061
|
async fn bridge_capabilities_reflect_single_engine_state() {
|
|
7974
8062
|
let hub = Arc::new(ClientHub::new());
|
|
@@ -8796,6 +8884,7 @@ mod tests {
|
|
|
8796
8884
|
cli_bin: "codex".to_string(),
|
|
8797
8885
|
opencode_cli_bin: "opencode".to_string(),
|
|
8798
8886
|
active_engine: BridgeRuntimeEngine::Codex,
|
|
8887
|
+
enabled_engines: vec![BridgeRuntimeEngine::Codex, BridgeRuntimeEngine::Opencode],
|
|
8799
8888
|
opencode_host: "127.0.0.1".to_string(),
|
|
8800
8889
|
opencode_port: 4090,
|
|
8801
8890
|
opencode_server_username: "opencode".to_string(),
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|