clawdex-mobile 5.0.5-internal.2 → 5.0.5-internal.4
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 +296 -100
- package/services/rust-bridge/src/main.rs +110 -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,115 @@ 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
|
+
engine_list_contains() {
|
|
148
|
+
local needle="$1"
|
|
149
|
+
shift
|
|
150
|
+
local value=""
|
|
151
|
+
for value in "$@"; do
|
|
152
|
+
if [[ "$value" == "$needle" ]]; then
|
|
153
|
+
return 0
|
|
154
|
+
fi
|
|
155
|
+
done
|
|
156
|
+
return 1
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
sync_active_engine_from_selection() {
|
|
160
|
+
if (( ${#SELECTED_ENGINES[@]} == 0 )); then
|
|
161
|
+
ACTIVE_ENGINE="codex"
|
|
162
|
+
return
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
if engine_list_contains "$ACTIVE_ENGINE" "${SELECTED_ENGINES[@]}"; then
|
|
166
|
+
return
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
ACTIVE_ENGINE="${SELECTED_ENGINES[0]}"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
format_engine_list() {
|
|
173
|
+
local engine=""
|
|
174
|
+
local result=""
|
|
175
|
+
|
|
176
|
+
for engine in "$@"; do
|
|
177
|
+
if [[ -n "$result" ]]; then
|
|
178
|
+
result+=", "
|
|
179
|
+
fi
|
|
180
|
+
result+="$(format_engine_name "$engine")"
|
|
181
|
+
done
|
|
182
|
+
|
|
183
|
+
printf '%s' "$result"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
engine_from_menu_label() {
|
|
114
187
|
case "$1" in
|
|
115
|
-
|
|
116
|
-
printf 'opencode'
|
|
117
|
-
;;
|
|
118
|
-
opencode)
|
|
188
|
+
"Codex")
|
|
119
189
|
printf 'codex'
|
|
120
190
|
;;
|
|
191
|
+
"OpenCode")
|
|
192
|
+
printf 'opencode'
|
|
193
|
+
;;
|
|
121
194
|
*)
|
|
122
195
|
return 1
|
|
123
196
|
;;
|
|
124
197
|
esac
|
|
125
198
|
}
|
|
126
199
|
|
|
200
|
+
load_existing_engine_selection() {
|
|
201
|
+
local raw=""
|
|
202
|
+
local engine=""
|
|
203
|
+
|
|
204
|
+
raw="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ENABLED_ENGINES")"
|
|
205
|
+
if [[ -n "$raw" ]] && parse_engine_list_csv "$raw"; then
|
|
206
|
+
engine="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ACTIVE_ENGINE")"
|
|
207
|
+
if validate_engine_name "$engine"; then
|
|
208
|
+
ACTIVE_ENGINE="$engine"
|
|
209
|
+
else
|
|
210
|
+
ACTIVE_ENGINE="${SELECTED_ENGINES[0]}"
|
|
211
|
+
fi
|
|
212
|
+
sync_active_engine_from_selection
|
|
213
|
+
return 0
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
engine="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ACTIVE_ENGINE")"
|
|
217
|
+
if ! validate_engine_name "$engine"; then
|
|
218
|
+
engine="codex"
|
|
219
|
+
fi
|
|
220
|
+
SELECTED_ENGINES=("$engine")
|
|
221
|
+
ACTIVE_ENGINE="$engine"
|
|
222
|
+
}
|
|
223
|
+
|
|
127
224
|
parse_args() {
|
|
128
225
|
while (($# > 0)); do
|
|
129
226
|
case "$1" in
|
|
@@ -143,7 +240,23 @@ parse_args() {
|
|
|
143
240
|
exit 1
|
|
144
241
|
fi
|
|
145
242
|
ACTIVE_ENGINE="$2"
|
|
146
|
-
|
|
243
|
+
SELECTED_ENGINES=("$2")
|
|
244
|
+
ENGINE_SELECTION_PRESET="true"
|
|
245
|
+
shift 2
|
|
246
|
+
;;
|
|
247
|
+
--engines)
|
|
248
|
+
if (($# < 2)); then
|
|
249
|
+
echo "error: --engines requires a comma-separated value (for example: codex,opencode)." >&2
|
|
250
|
+
print_usage >&2
|
|
251
|
+
exit 1
|
|
252
|
+
fi
|
|
253
|
+
if ! parse_engine_list_csv "$2"; then
|
|
254
|
+
echo "error: unsupported --engines value '$2'. Use one or more of 'codex' and 'opencode'." >&2
|
|
255
|
+
print_usage >&2
|
|
256
|
+
exit 1
|
|
257
|
+
fi
|
|
258
|
+
ACTIVE_ENGINE="${SELECTED_ENGINES[0]}"
|
|
259
|
+
ENGINE_SELECTION_PRESET="true"
|
|
147
260
|
shift 2
|
|
148
261
|
;;
|
|
149
262
|
-h|--help)
|
|
@@ -499,6 +612,123 @@ menu_select() {
|
|
|
499
612
|
printf "\r\033[2K%s %s %s: %s\n" "$rail" "$branch" "$prompt" "$MENU_RESULT"
|
|
500
613
|
}
|
|
501
614
|
|
|
615
|
+
menu_multiselect() {
|
|
616
|
+
local prompt="$1"
|
|
617
|
+
shift
|
|
618
|
+
local -a options=("$@")
|
|
619
|
+
local option_count="${#options[@]}"
|
|
620
|
+
local selected=0
|
|
621
|
+
local lines_to_render=$((option_count + 3))
|
|
622
|
+
local i=0
|
|
623
|
+
local key=""
|
|
624
|
+
local key_rest=""
|
|
625
|
+
local rail="$RAIL_GLYPH"
|
|
626
|
+
local branch="$RAIL_BRANCH"
|
|
627
|
+
local child="$RAIL_CHILD"
|
|
628
|
+
local help_text="${DIM}Space toggles. Enter continues.${RESET}"
|
|
629
|
+
local summary=""
|
|
630
|
+
local mark=""
|
|
631
|
+
local selected_count=0
|
|
632
|
+
local option=""
|
|
633
|
+
local preselected=",${MENU_MULTI_PRESELECTED},"
|
|
634
|
+
local -a checked=()
|
|
635
|
+
|
|
636
|
+
if (( option_count == 0 )); then
|
|
637
|
+
abort_wizard "menu_multiselect requires at least one option."
|
|
638
|
+
fi
|
|
639
|
+
|
|
640
|
+
for option in "${options[@]}"; do
|
|
641
|
+
if [[ "$preselected" == *",$option,"* ]]; then
|
|
642
|
+
checked+=(1)
|
|
643
|
+
else
|
|
644
|
+
checked+=(0)
|
|
645
|
+
fi
|
|
646
|
+
done
|
|
647
|
+
|
|
648
|
+
tput civis >/dev/null 2>&1 || true
|
|
649
|
+
|
|
650
|
+
while true; do
|
|
651
|
+
printf "\r\033[2K%s\n" "$rail"
|
|
652
|
+
printf "\r\033[2K%s %s %s\n" "$rail" "$branch" "$prompt"
|
|
653
|
+
for ((i = 0; i < option_count; i++)); do
|
|
654
|
+
if (( checked[i] == 1 )); then
|
|
655
|
+
mark="[x]"
|
|
656
|
+
else
|
|
657
|
+
mark="[ ]"
|
|
658
|
+
fi
|
|
659
|
+
|
|
660
|
+
if (( i == selected )); then
|
|
661
|
+
printf "\r\033[2K%s %s ${GREEN}%s${RESET} %s\n" "$rail" "$child" "$mark" "${options[$i]}"
|
|
662
|
+
else
|
|
663
|
+
printf "\r\033[2K%s %s ${DIM}%s${RESET} %s\n" "$rail" "$child" "$mark" "${options[$i]}"
|
|
664
|
+
fi
|
|
665
|
+
done
|
|
666
|
+
printf "\r\033[2K%s %s %s\n" "$rail" "$child" "$help_text"
|
|
667
|
+
|
|
668
|
+
IFS= read -rsn1 key || abort_wizard
|
|
669
|
+
|
|
670
|
+
if [[ "$key" == $'\x03' ]]; then
|
|
671
|
+
abort_wizard
|
|
672
|
+
fi
|
|
673
|
+
|
|
674
|
+
if [[ "$key" == $'\x1b' ]]; then
|
|
675
|
+
key_rest=""
|
|
676
|
+
IFS= read -rsn2 key_rest || true
|
|
677
|
+
key+="$key_rest"
|
|
678
|
+
fi
|
|
679
|
+
|
|
680
|
+
case "$key" in
|
|
681
|
+
"")
|
|
682
|
+
selected_count=0
|
|
683
|
+
for ((i = 0; i < option_count; i++)); do
|
|
684
|
+
if (( checked[i] == 1 )); then
|
|
685
|
+
selected_count=$((selected_count + 1))
|
|
686
|
+
fi
|
|
687
|
+
done
|
|
688
|
+
if (( selected_count > 0 )); then
|
|
689
|
+
break
|
|
690
|
+
fi
|
|
691
|
+
help_text="${YELLOW}Select at least one option. Space toggles. Enter continues.${RESET}"
|
|
692
|
+
;;
|
|
693
|
+
" ")
|
|
694
|
+
checked[selected]=$((1 - checked[selected]))
|
|
695
|
+
help_text="${DIM}Space toggles. Enter continues.${RESET}"
|
|
696
|
+
;;
|
|
697
|
+
$'\x1b[A'|k|K)
|
|
698
|
+
selected=$(((selected - 1 + option_count) % option_count))
|
|
699
|
+
;;
|
|
700
|
+
$'\x1b[B'|j|J)
|
|
701
|
+
selected=$(((selected + 1) % option_count))
|
|
702
|
+
;;
|
|
703
|
+
q|Q)
|
|
704
|
+
abort_wizard
|
|
705
|
+
;;
|
|
706
|
+
*)
|
|
707
|
+
;;
|
|
708
|
+
esac
|
|
709
|
+
|
|
710
|
+
printf "\033[%dA" "$lines_to_render"
|
|
711
|
+
done
|
|
712
|
+
|
|
713
|
+
MENU_MULTI_RESULT_ITEMS=()
|
|
714
|
+
for ((i = 0; i < option_count; i++)); do
|
|
715
|
+
if (( checked[i] == 1 )); then
|
|
716
|
+
MENU_MULTI_RESULT_ITEMS+=("${options[$i]}")
|
|
717
|
+
fi
|
|
718
|
+
done
|
|
719
|
+
|
|
720
|
+
MENU_MULTI_RESULT="$(IFS=,; printf '%s' "${MENU_MULTI_RESULT_ITEMS[*]}")"
|
|
721
|
+
summary="$(IFS=', '; printf '%s' "${MENU_MULTI_RESULT_ITEMS[*]}")"
|
|
722
|
+
|
|
723
|
+
tput cnorm >/dev/null 2>&1 || true
|
|
724
|
+
printf "\033[%dA" "$lines_to_render"
|
|
725
|
+
for ((i = 0; i < lines_to_render; i++)); do
|
|
726
|
+
printf "\r\033[2K\n"
|
|
727
|
+
done
|
|
728
|
+
printf "\033[%dA" "$lines_to_render"
|
|
729
|
+
printf "\r\033[2K%s %s %s: %s\n" "$rail" "$branch" "$prompt" "$summary"
|
|
730
|
+
}
|
|
731
|
+
|
|
502
732
|
confirm_prompt() {
|
|
503
733
|
local prompt="$1"
|
|
504
734
|
local default="${2:-N}"
|
|
@@ -687,7 +917,7 @@ print_existing_setup_summary() {
|
|
|
687
917
|
local port=""
|
|
688
918
|
local token=""
|
|
689
919
|
local network_mode=""
|
|
690
|
-
local
|
|
920
|
+
local harnesses=""
|
|
691
921
|
local source_path=""
|
|
692
922
|
|
|
693
923
|
if [[ ! -f "$SECURE_ENV_FILE" ]]; then
|
|
@@ -698,9 +928,9 @@ print_existing_setup_summary() {
|
|
|
698
928
|
port="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_PORT")"
|
|
699
929
|
token="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_AUTH_TOKEN")"
|
|
700
930
|
network_mode="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_NETWORK_MODE")"
|
|
701
|
-
|
|
702
|
-
if
|
|
703
|
-
|
|
931
|
+
harnesses="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ENABLED_ENGINES")"
|
|
932
|
+
if [[ -z "$harnesses" ]]; then
|
|
933
|
+
harnesses="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ACTIVE_ENGINE")"
|
|
704
934
|
fi
|
|
705
935
|
|
|
706
936
|
if [[ -z "$host" ]] && [[ -z "$port" ]] && [[ -z "$token" ]]; then
|
|
@@ -717,8 +947,8 @@ print_existing_setup_summary() {
|
|
|
717
947
|
if [[ -n "$network_mode" ]]; then
|
|
718
948
|
echo "bridge.networkMode: $network_mode"
|
|
719
949
|
fi
|
|
720
|
-
if [[ -n "$
|
|
721
|
-
echo "bridge.
|
|
950
|
+
if [[ -n "$harnesses" ]]; then
|
|
951
|
+
echo "bridge.harnesses: $harnesses"
|
|
722
952
|
fi
|
|
723
953
|
if [[ -n "$token" ]]; then
|
|
724
954
|
echo "bridge.token: present"
|
|
@@ -803,22 +1033,28 @@ choose_bridge_network_mode() {
|
|
|
803
1033
|
}
|
|
804
1034
|
|
|
805
1035
|
choose_runtime_engine() {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
"
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1036
|
+
local label=""
|
|
1037
|
+
MENU_MULTI_PRESELECTED="Codex"
|
|
1038
|
+
if engine_list_contains "codex" "${SELECTED_ENGINES[@]}"; then
|
|
1039
|
+
MENU_MULTI_PRESELECTED="Codex"
|
|
1040
|
+
else
|
|
1041
|
+
MENU_MULTI_PRESELECTED=""
|
|
1042
|
+
fi
|
|
1043
|
+
if engine_list_contains "opencode" "${SELECTED_ENGINES[@]}"; then
|
|
1044
|
+
if [[ -n "$MENU_MULTI_PRESELECTED" ]]; then
|
|
1045
|
+
MENU_MULTI_PRESELECTED+=","
|
|
1046
|
+
fi
|
|
1047
|
+
MENU_MULTI_PRESELECTED+="OpenCode"
|
|
1048
|
+
fi
|
|
1049
|
+
|
|
1050
|
+
info "Select the harnesses this phone should control."
|
|
1051
|
+
menu_multiselect "Harnesses to control" "Codex" "OpenCode"
|
|
1052
|
+
SELECTED_ENGINES=()
|
|
1053
|
+
for label in "${MENU_MULTI_RESULT_ITEMS[@]}"; do
|
|
1054
|
+
SELECTED_ENGINES+=("$(engine_from_menu_label "$label")")
|
|
1055
|
+
done
|
|
1056
|
+
sync_active_engine_from_selection
|
|
1057
|
+
info "Selected harnesses: $(format_engine_list "${SELECTED_ENGINES[@]}")."
|
|
822
1058
|
}
|
|
823
1059
|
|
|
824
1060
|
infer_network_mode_from_host() {
|
|
@@ -932,55 +1168,21 @@ ensure_opencode_cli() {
|
|
|
932
1168
|
fi
|
|
933
1169
|
}
|
|
934
1170
|
|
|
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
|
|
1171
|
+
ensure_selected_engine_clis() {
|
|
1172
|
+
local engine=""
|
|
1173
|
+
for engine in "${SELECTED_ENGINES[@]}"; do
|
|
1174
|
+
case "$engine" in
|
|
1175
|
+
codex)
|
|
962
1176
|
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
|
|
1177
|
+
;;
|
|
1178
|
+
opencode)
|
|
975
1179
|
ensure_opencode_cli
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
;;
|
|
983
|
-
esac
|
|
1180
|
+
;;
|
|
1181
|
+
*)
|
|
1182
|
+
abort_wizard "Unsupported harness '$engine'."
|
|
1183
|
+
;;
|
|
1184
|
+
esac
|
|
1185
|
+
done
|
|
984
1186
|
}
|
|
985
1187
|
|
|
986
1188
|
ensure_tailscale_cli() {
|
|
@@ -1362,29 +1564,24 @@ if [[ "$CONFIG_ACTION" == "reset" ]]; then
|
|
|
1362
1564
|
ok "Previous secure config removed: $SECURE_ENV_FILE"
|
|
1363
1565
|
fi
|
|
1364
1566
|
|
|
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
|
|
1567
|
+
load_existing_engine_selection
|
|
1370
1568
|
if [[ "$CONFIG_ACTION" == "keep" ]]; then
|
|
1371
|
-
if [[ "$
|
|
1372
|
-
|
|
1373
|
-
info "Keeping existing default engine: $(format_engine_name "$ACTIVE_ENGINE")."
|
|
1569
|
+
if [[ "$ENGINE_SELECTION_PRESET" == "false" ]]; then
|
|
1570
|
+
info "Keeping existing harnesses: $(format_engine_list "${SELECTED_ENGINES[@]}")."
|
|
1374
1571
|
else
|
|
1375
|
-
info "
|
|
1572
|
+
info "Harness selection preset via flag: $(format_engine_list "${SELECTED_ENGINES[@]}")."
|
|
1376
1573
|
fi
|
|
1377
1574
|
else
|
|
1378
|
-
|
|
1575
|
+
section "Harnesses"
|
|
1576
|
+
if [[ "$ENGINE_SELECTION_PRESET" == "false" ]]; then
|
|
1379
1577
|
choose_runtime_engine
|
|
1380
1578
|
else
|
|
1381
|
-
info "
|
|
1579
|
+
info "Harness selection preset via flag: $(format_engine_list "${SELECTED_ENGINES[@]}")."
|
|
1382
1580
|
fi
|
|
1383
1581
|
fi
|
|
1384
1582
|
|
|
1385
1583
|
section "Runtime dependency"
|
|
1386
|
-
|
|
1387
|
-
ensure_optional_secondary_engine_cli
|
|
1584
|
+
ensure_selected_engine_clis
|
|
1388
1585
|
|
|
1389
1586
|
if [[ "$CONFIG_ACTION" != "keep" ]]; then
|
|
1390
1587
|
section "Bridge network mode"
|
|
@@ -1409,7 +1606,7 @@ if [[ "$CONFIG_ACTION" != "keep" ]]; then
|
|
|
1409
1606
|
esac
|
|
1410
1607
|
|
|
1411
1608
|
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"
|
|
1609
|
+
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
1610
|
else
|
|
1414
1611
|
ok "Keeping existing secure config."
|
|
1415
1612
|
NETWORK_MODE="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_NETWORK_MODE")"
|
|
@@ -1434,10 +1631,10 @@ else
|
|
|
1434
1631
|
fi
|
|
1435
1632
|
|
|
1436
1633
|
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 [[ "$
|
|
1634
|
+
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"
|
|
1635
|
+
elif [[ "$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ENABLED_ENGINES")" != "$(IFS=,; printf '%s' "${SELECTED_ENGINES[*]}")" ]]; then
|
|
1439
1636
|
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"
|
|
1637
|
+
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
1638
|
fi
|
|
1442
1639
|
fi
|
|
1443
1640
|
|
|
@@ -1467,8 +1664,7 @@ BRIDGE_PORT="${BRIDGE_PORT:-8787}"
|
|
|
1467
1664
|
section "Summary"
|
|
1468
1665
|
rail_echo "Bridge mode: $NETWORK_MODE"
|
|
1469
1666
|
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}"
|
|
1667
|
+
rail_echo "Harnesses: $(format_engine_list "${SELECTED_ENGINES[@]}")"
|
|
1472
1668
|
rail_echo "Secure env: $SECURE_ENV_FILE"
|
|
1473
1669
|
if [[ "$FLOW" == "quickstart" ]]; then
|
|
1474
1670
|
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,52 @@ 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 engine = parse_bridge_runtime_engine(&normalized)
|
|
5381
|
+
.ok_or_else(|| format!("unsupported BRIDGE_ENABLED_ENGINES entry: {normalized}"))?;
|
|
5382
|
+
if seen.insert(engine) {
|
|
5383
|
+
parsed.push(engine);
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
|
|
5387
|
+
if parsed.is_empty() {
|
|
5388
|
+
return Err(
|
|
5389
|
+
"BRIDGE_ENABLED_ENGINES must include one or more of: codex, opencode".to_string(),
|
|
5390
|
+
);
|
|
5391
|
+
}
|
|
5392
|
+
|
|
5393
|
+
Ok(parsed)
|
|
5394
|
+
}
|
|
5395
|
+
|
|
5396
|
+
fn parse_enabled_bridge_engines_env() -> Result<Option<Vec<BridgeRuntimeEngine>>, String> {
|
|
5397
|
+
let raw = match env::var("BRIDGE_ENABLED_ENGINES") {
|
|
5398
|
+
Ok(raw) => raw,
|
|
5399
|
+
Err(_) => return Ok(None),
|
|
5400
|
+
};
|
|
5401
|
+
|
|
5402
|
+
Ok(Some(parse_enabled_bridge_engines_csv(&raw)?))
|
|
5403
|
+
}
|
|
5404
|
+
|
|
5405
|
+
fn legacy_default_enabled_engines(
|
|
5406
|
+
requested_active_engine: BridgeRuntimeEngine,
|
|
5407
|
+
) -> Vec<BridgeRuntimeEngine> {
|
|
5408
|
+
match requested_active_engine {
|
|
5409
|
+
BridgeRuntimeEngine::Codex => {
|
|
5410
|
+
vec![BridgeRuntimeEngine::Codex, BridgeRuntimeEngine::Opencode]
|
|
5411
|
+
}
|
|
5412
|
+
BridgeRuntimeEngine::Opencode => {
|
|
5413
|
+
vec![BridgeRuntimeEngine::Opencode, BridgeRuntimeEngine::Codex]
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
}
|
|
5417
|
+
|
|
5351
5418
|
impl BridgeRuntimeEngine {
|
|
5352
5419
|
fn as_str(self) -> &'static str {
|
|
5353
5420
|
match self {
|
|
@@ -7399,6 +7466,7 @@ mod tests {
|
|
|
7399
7466
|
cli_bin: "cat".to_string(),
|
|
7400
7467
|
opencode_cli_bin: "opencode".to_string(),
|
|
7401
7468
|
active_engine: BridgeRuntimeEngine::Codex,
|
|
7469
|
+
enabled_engines: vec![BridgeRuntimeEngine::Codex, BridgeRuntimeEngine::Opencode],
|
|
7402
7470
|
opencode_host: "127.0.0.1".to_string(),
|
|
7403
7471
|
opencode_port: 4090,
|
|
7404
7472
|
opencode_server_username: "opencode".to_string(),
|
|
@@ -7969,6 +8037,16 @@ mod tests {
|
|
|
7969
8037
|
shutdown_test_backend(&state.backend).await;
|
|
7970
8038
|
}
|
|
7971
8039
|
|
|
8040
|
+
#[test]
|
|
8041
|
+
fn parse_enabled_bridge_engines_csv_preserves_order_and_removes_duplicates() {
|
|
8042
|
+
let parsed =
|
|
8043
|
+
parse_enabled_bridge_engines_csv("opencode,codex,opencode").expect("engine csv");
|
|
8044
|
+
assert_eq!(
|
|
8045
|
+
parsed,
|
|
8046
|
+
vec![BridgeRuntimeEngine::Opencode, BridgeRuntimeEngine::Codex]
|
|
8047
|
+
);
|
|
8048
|
+
}
|
|
8049
|
+
|
|
7972
8050
|
#[tokio::test]
|
|
7973
8051
|
async fn bridge_capabilities_reflect_single_engine_state() {
|
|
7974
8052
|
let hub = Arc::new(ClientHub::new());
|
|
@@ -8796,6 +8874,7 @@ mod tests {
|
|
|
8796
8874
|
cli_bin: "codex".to_string(),
|
|
8797
8875
|
opencode_cli_bin: "opencode".to_string(),
|
|
8798
8876
|
active_engine: BridgeRuntimeEngine::Codex,
|
|
8877
|
+
enabled_engines: vec![BridgeRuntimeEngine::Codex, BridgeRuntimeEngine::Opencode],
|
|
8799
8878
|
opencode_host: "127.0.0.1".to_string(),
|
|
8800
8879
|
opencode_port: 4090,
|
|
8801
8880
|
opencode_server_username: "opencode".to_string(),
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|