clawdex-mobile 1.1.0 → 1.1.2
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/.github/workflows/ci.yml +2 -0
- package/.github/workflows/npm-release.yml +28 -1
- package/apps/mobile/src/api/__tests__/client.test.ts +43 -2
- package/apps/mobile/src/api/client.ts +19 -10
- package/apps/mobile/src/screens/MainScreen.tsx +10 -6
- package/docs/codex-app-server-cli-gap-tracker.md +81 -0
- package/package.json +1 -1
- package/scripts/codex-live-demo.sh +69 -0
- package/services/rust-bridge/.env.example +3 -0
- package/services/rust-bridge/src/main.rs +1180 -70
package/.github/workflows/ci.yml
CHANGED
|
@@ -11,7 +11,7 @@ concurrency:
|
|
|
11
11
|
cancel-in-progress: false
|
|
12
12
|
|
|
13
13
|
permissions:
|
|
14
|
-
contents:
|
|
14
|
+
contents: write
|
|
15
15
|
|
|
16
16
|
jobs:
|
|
17
17
|
publish:
|
|
@@ -32,9 +32,25 @@ jobs:
|
|
|
32
32
|
cache: npm
|
|
33
33
|
cache-dependency-path: package-lock.json
|
|
34
34
|
|
|
35
|
+
- name: Setup Rust toolchain
|
|
36
|
+
uses: dtolnay/rust-toolchain@stable
|
|
37
|
+
|
|
38
|
+
- name: Cache Rust dependencies
|
|
39
|
+
uses: Swatinem/rust-cache@v2
|
|
40
|
+
|
|
35
41
|
- name: Install dependencies
|
|
36
42
|
run: npm ci
|
|
37
43
|
|
|
44
|
+
- name: Verify Rust bridge compiles
|
|
45
|
+
working-directory: services/rust-bridge
|
|
46
|
+
run: cargo check --locked
|
|
47
|
+
|
|
48
|
+
- name: Run monorepo quality gates
|
|
49
|
+
run: |
|
|
50
|
+
npm run lint
|
|
51
|
+
npm run typecheck
|
|
52
|
+
npm run test
|
|
53
|
+
|
|
38
54
|
- name: Verify package is publishable
|
|
39
55
|
run: |
|
|
40
56
|
node -e "const p=require('./package.json'); if (p.private) { throw new Error('package.json has private=true; cannot publish'); } if (!p.name || !p.version) { throw new Error('package.json must include name + version'); }"
|
|
@@ -65,3 +81,14 @@ jobs:
|
|
|
65
81
|
env:
|
|
66
82
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
67
83
|
run: npm publish --access public
|
|
84
|
+
|
|
85
|
+
- name: Create or update GitHub release
|
|
86
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
87
|
+
env:
|
|
88
|
+
GH_TOKEN: ${{ github.token }}
|
|
89
|
+
run: |
|
|
90
|
+
if gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1; then
|
|
91
|
+
gh release edit "${GITHUB_REF_NAME}" --latest
|
|
92
|
+
else
|
|
93
|
+
gh release create "${GITHUB_REF_NAME}" --title "${GITHUB_REF_NAME}" --generate-notes --latest
|
|
94
|
+
fi
|
|
@@ -474,7 +474,7 @@ describe('HostBridgeApiClient', () => {
|
|
|
474
474
|
);
|
|
475
475
|
});
|
|
476
476
|
|
|
477
|
-
it('resumeThread() retries with
|
|
477
|
+
it('resumeThread() retries with compatibility payload when modern resume params are rejected', async () => {
|
|
478
478
|
const ws = createWsMock();
|
|
479
479
|
ws.request
|
|
480
480
|
.mockRejectedValueOnce(new Error('unknown field `experimentalRawEvents`'))
|
|
@@ -498,11 +498,52 @@ describe('HostBridgeApiClient', () => {
|
|
|
498
498
|
expect.objectContaining({
|
|
499
499
|
threadId: 'thr_resume',
|
|
500
500
|
approvalPolicy: 'on-request',
|
|
501
|
+
developerInstructions: expect.any(String),
|
|
502
|
+
experimentalRawEvents: true,
|
|
503
|
+
})
|
|
504
|
+
);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('resumeThread() falls back to legacy payload when compatibility retry is rejected', async () => {
|
|
508
|
+
const ws = createWsMock();
|
|
509
|
+
ws.request
|
|
510
|
+
.mockRejectedValueOnce(new Error('unknown field `experimentalRawEvents`'))
|
|
511
|
+
.mockRejectedValueOnce(new Error('invalid params for resume options'))
|
|
512
|
+
.mockResolvedValueOnce({});
|
|
513
|
+
|
|
514
|
+
const client = new HostBridgeApiClient({ ws: ws as unknown as HostBridgeWsClient });
|
|
515
|
+
await expect(client.resumeThread('thr_resume_legacy')).resolves.toBeUndefined();
|
|
516
|
+
|
|
517
|
+
expect(ws.request).toHaveBeenNthCalledWith(
|
|
518
|
+
1,
|
|
519
|
+
'thread/resume',
|
|
520
|
+
expect.objectContaining({
|
|
521
|
+
threadId: 'thr_resume_legacy',
|
|
522
|
+
experimentalRawEvents: true,
|
|
523
|
+
approvalPolicy: 'untrusted',
|
|
524
|
+
})
|
|
525
|
+
);
|
|
526
|
+
expect(ws.request).toHaveBeenNthCalledWith(
|
|
527
|
+
2,
|
|
528
|
+
'thread/resume',
|
|
529
|
+
expect.objectContaining({
|
|
530
|
+
threadId: 'thr_resume_legacy',
|
|
531
|
+
approvalPolicy: 'on-request',
|
|
532
|
+
developerInstructions: expect.any(String),
|
|
533
|
+
experimentalRawEvents: true,
|
|
534
|
+
})
|
|
535
|
+
);
|
|
536
|
+
expect(ws.request).toHaveBeenNthCalledWith(
|
|
537
|
+
3,
|
|
538
|
+
'thread/resume',
|
|
539
|
+
expect.objectContaining({
|
|
540
|
+
threadId: 'thr_resume_legacy',
|
|
541
|
+
approvalPolicy: 'on-request',
|
|
501
542
|
developerInstructions: null,
|
|
502
543
|
})
|
|
503
544
|
);
|
|
504
545
|
|
|
505
|
-
const legacyPayload = ws.request.mock.calls[
|
|
546
|
+
const legacyPayload = ws.request.mock.calls[2]?.[1] as Record<string, unknown>;
|
|
506
547
|
expect(legacyPayload).not.toHaveProperty('experimentalRawEvents');
|
|
507
548
|
});
|
|
508
549
|
|
|
@@ -338,21 +338,30 @@ export class HostBridgeApiClient {
|
|
|
338
338
|
await this.ws.request('thread/resume', primaryRequest);
|
|
339
339
|
return;
|
|
340
340
|
} catch (primaryError) {
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
const legacyRequest = {
|
|
341
|
+
// First fallback: keep raw-event streaming enabled, but relax approval policy.
|
|
342
|
+
const compatibilityRequest = {
|
|
344
343
|
...primaryRequest,
|
|
345
344
|
approvalPolicy: fallbackApprovalPolicy,
|
|
346
|
-
developerInstructions: null,
|
|
347
345
|
};
|
|
348
|
-
delete (legacyRequest as { experimentalRawEvents?: boolean }).experimentalRawEvents;
|
|
349
346
|
try {
|
|
350
|
-
await this.ws.request('thread/resume',
|
|
347
|
+
await this.ws.request('thread/resume', compatibilityRequest);
|
|
351
348
|
return;
|
|
352
|
-
} catch (
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
349
|
+
} catch (compatibilityError) {
|
|
350
|
+
// Final compatibility fallback for older app-server builds that reject
|
|
351
|
+
// experimentalRawEvents/developerInstructions on resume.
|
|
352
|
+
const legacyRequest = {
|
|
353
|
+
...compatibilityRequest,
|
|
354
|
+
developerInstructions: null,
|
|
355
|
+
};
|
|
356
|
+
delete (legacyRequest as { experimentalRawEvents?: boolean }).experimentalRawEvents;
|
|
357
|
+
try {
|
|
358
|
+
await this.ws.request('thread/resume', legacyRequest);
|
|
359
|
+
return;
|
|
360
|
+
} catch (legacyError) {
|
|
361
|
+
throw new Error(
|
|
362
|
+
`thread/resume failed: ${(primaryError as Error).message}; compatibility failed: ${(compatibilityError as Error).message}; legacy fallback failed: ${(legacyError as Error).message}`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
356
365
|
}
|
|
357
366
|
}
|
|
358
367
|
}
|
|
@@ -130,7 +130,7 @@ const MAX_VISIBLE_TOOL_BLOCKS = 3;
|
|
|
130
130
|
const RUN_WATCHDOG_MS = 60_000;
|
|
131
131
|
const LIKELY_RUNNING_RECENT_UPDATE_MS = 30_000;
|
|
132
132
|
const ACTIVE_CHAT_SYNC_INTERVAL_MS = 2_000;
|
|
133
|
-
const IDLE_CHAT_SYNC_INTERVAL_MS =
|
|
133
|
+
const IDLE_CHAT_SYNC_INTERVAL_MS = 2_500;
|
|
134
134
|
const THREAD_RESUME_RETRY_MS = 1_500;
|
|
135
135
|
const CHAT_MODEL_PREFERENCES_FILE = 'chat-model-preferences.json';
|
|
136
136
|
const CHAT_MODEL_PREFERENCES_VERSION = 1;
|
|
@@ -4084,6 +4084,10 @@ export const MainScreen = forwardRef<MainScreenHandle, MainScreenProps>(
|
|
|
4084
4084
|
const threadId =
|
|
4085
4085
|
readString(params?.threadId) ?? readString(params?.thread_id);
|
|
4086
4086
|
if (threadId && threadId === currentId) {
|
|
4087
|
+
// External clients (CLI/TUI) may start turns without pushing full live
|
|
4088
|
+
// notifications through this app-server process. Force a lightweight
|
|
4089
|
+
// resume attempt to attach to fresh stream state early.
|
|
4090
|
+
ensureThreadResumeSubscription(threadId, { force: true });
|
|
4087
4091
|
api
|
|
4088
4092
|
.getChatSummary(threadId)
|
|
4089
4093
|
.then((summary) => {
|
|
@@ -4210,11 +4214,11 @@ export const MainScreen = forwardRef<MainScreenHandle, MainScreenProps>(
|
|
|
4210
4214
|
const shouldRunFromWatchdog = runWatchdogUntilRef.current > Date.now();
|
|
4211
4215
|
const shouldShowRunning = shouldRunFromChat || shouldRunFromWatchdog;
|
|
4212
4216
|
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
}
|
|
4217
|
+
// Keep a light resume heartbeat even while idle so externally-started
|
|
4218
|
+
// turns are discovered quickly and can stream status/tool updates.
|
|
4219
|
+
ensureThreadResumeSubscription(selectedChatId, {
|
|
4220
|
+
force: shouldRunFromChat,
|
|
4221
|
+
});
|
|
4218
4222
|
|
|
4219
4223
|
if (shouldShowRunning && !hasPendingApproval && !hasPendingUserInput) {
|
|
4220
4224
|
setActivity((prev) => {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Codex App-Server + CLI Gap Tracker
|
|
2
|
+
|
|
3
|
+
Last updated: February 24, 2026
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
This tracker compares `clawdex-mobile` against current Codex app-server + CLI capabilities and records what still needs to be added.
|
|
7
|
+
|
|
8
|
+
## Gap 1: App-Server Protocol Parity
|
|
9
|
+
Status: In progress (first implementation pass completed)
|
|
10
|
+
|
|
11
|
+
### Implemented in this pass
|
|
12
|
+
- Expanded rust-bridge forwarded app-server client methods to include newer slash/API endpoints.
|
|
13
|
+
- Added legacy approval request compatibility for `applyPatchApproval` and `execCommandApproval`.
|
|
14
|
+
- Added decision translation between modern and legacy approval response formats.
|
|
15
|
+
- Added explicit handling for `item/tool/call` server requests (returns structured unsupported result instead of generic method-not-found).
|
|
16
|
+
- Added explicit handling for `account/chatgptAuthTokens/refresh` server requests:
|
|
17
|
+
- Uses `BRIDGE_CHATGPT_ACCESS_TOKEN` + `BRIDGE_CHATGPT_ACCOUNT_ID` when present.
|
|
18
|
+
- Emits descriptive error when not configured.
|
|
19
|
+
|
|
20
|
+
### Forwarded methods added
|
|
21
|
+
- `account/login/cancel`
|
|
22
|
+
- `account/login/start`
|
|
23
|
+
- `account/logout`
|
|
24
|
+
- `account/rateLimits/read`
|
|
25
|
+
- `account/read`
|
|
26
|
+
- `collaborationMode/list`
|
|
27
|
+
- `config/batchWrite`
|
|
28
|
+
- `config/mcpServer/reload`
|
|
29
|
+
- `config/read`
|
|
30
|
+
- `config/value/write`
|
|
31
|
+
- `configRequirements/read`
|
|
32
|
+
- `experimentalFeature/list`
|
|
33
|
+
- `feedback/upload`
|
|
34
|
+
- `fuzzyFileSearch/sessionStart`
|
|
35
|
+
- `fuzzyFileSearch/sessionStop`
|
|
36
|
+
- `fuzzyFileSearch/sessionUpdate`
|
|
37
|
+
- `mcpServer/oauth/login`
|
|
38
|
+
- `mcpServerStatus/list`
|
|
39
|
+
- `mock/experimentalMethod`
|
|
40
|
+
- `skills/config/write`
|
|
41
|
+
- `skills/remote/export`
|
|
42
|
+
- `skills/remote/list`
|
|
43
|
+
- `thread/backgroundTerminals/clean`
|
|
44
|
+
|
|
45
|
+
### Remaining inside Gap 1
|
|
46
|
+
- Native execution of dynamic tool calls (`item/tool/call`) is still not implemented in mobile/bridge; currently returns `success: false`.
|
|
47
|
+
- Token refresh relies on environment variables; no mobile UI flow exists yet for account token refresh.
|
|
48
|
+
|
|
49
|
+
## Remaining Gaps (Beyond Gap 1)
|
|
50
|
+
|
|
51
|
+
### Gap 2: Slash Command Coverage in Mobile
|
|
52
|
+
- Many Codex CLI slash commands are not exposed as first-class actions in mobile UX.
|
|
53
|
+
- Mobile currently has partial command shortcuts and multiple unsupported command branches.
|
|
54
|
+
|
|
55
|
+
### Gap 3: Account/Auth UX
|
|
56
|
+
- No dedicated mobile flow for login state, logout, account details, or rate limits.
|
|
57
|
+
- Auth refresh is still operationally env-driven in bridge, not user-driven in app.
|
|
58
|
+
|
|
59
|
+
### Gap 4: MCP + Tooling UX
|
|
60
|
+
- No end-to-end UI for MCP server status, reload, OAuth login, or remote skills list/export.
|
|
61
|
+
- Dynamic tool calls do not execute on mobile yet.
|
|
62
|
+
|
|
63
|
+
### Gap 5: Collaboration/Plan Mode UX
|
|
64
|
+
- `collaborationMode/list` can now be forwarded, but there is no complete plan-mode UX in mobile.
|
|
65
|
+
- `request_user_input` has baseline support, but no richer structured workflows.
|
|
66
|
+
|
|
67
|
+
### Gap 6: Resilience + Reconnect
|
|
68
|
+
- WebSocket reconnect/backoff behavior is still limited on mobile.
|
|
69
|
+
- Slow/broken client recovery remains a known risk path.
|
|
70
|
+
|
|
71
|
+
### Gap 7: Security Hardening
|
|
72
|
+
- Bridge remains trusted-network oriented with optional no-auth local mode.
|
|
73
|
+
- High-risk endpoints (`bridge/terminal/exec`, `bridge/git/*`) need stronger authz controls for wider deployment.
|
|
74
|
+
|
|
75
|
+
### Gap 8: Contract/Regression Testing
|
|
76
|
+
- No automated contract sync against generated app-server schema.
|
|
77
|
+
- Missing CI guardrails to detect newly added app-server methods or server-request variants.
|
|
78
|
+
|
|
79
|
+
### Gap 9: Docs and Operator Runbooks
|
|
80
|
+
- Need user-facing docs for new app-server capabilities as they are surfaced in mobile.
|
|
81
|
+
- Need operational docs for auth/token refresh and MCP/OAuth troubleshooting.
|
package/package.json
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Demo launcher for a patched Codex TUI build that live-syncs rollout updates.
|
|
5
|
+
# It replaces the old tmux restart workaround.
|
|
6
|
+
|
|
7
|
+
DEFAULT_CODEX_SRC="/tmp/openai-codex-latest/codex-rs"
|
|
8
|
+
CODEX_SRC="${CODEX_SRC:-$DEFAULT_CODEX_SRC}"
|
|
9
|
+
PATCHED_APP_RS="${PATCHED_APP_RS:-$CODEX_SRC/tui/src/app.rs}"
|
|
10
|
+
PATCHED_TUI_BIN="${PATCHED_TUI_BIN:-$CODEX_SRC/target/debug/codex-tui}"
|
|
11
|
+
|
|
12
|
+
usage() {
|
|
13
|
+
cat <<'EOF'
|
|
14
|
+
Usage:
|
|
15
|
+
scripts/codex-live-demo.sh [codex-tui args...]
|
|
16
|
+
|
|
17
|
+
Environment overrides:
|
|
18
|
+
CODEX_SRC (default: /tmp/openai-codex-latest/codex-rs)
|
|
19
|
+
PATCHED_APP_RS (default: $CODEX_SRC/tui/src/app.rs)
|
|
20
|
+
PATCHED_TUI_BIN (default: $CODEX_SRC/target/debug/codex-tui)
|
|
21
|
+
|
|
22
|
+
Notes:
|
|
23
|
+
- This starts the patched TUI binary directly.
|
|
24
|
+
- Bridge/app-server can continue using stock codex:
|
|
25
|
+
CODEX_CLI_BIN=/opt/homebrew/bin/codex
|
|
26
|
+
- If PATCHED_TUI_BIN is missing, the script will build it via:
|
|
27
|
+
cargo test -p codex-tui
|
|
28
|
+
EOF
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
32
|
+
usage
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
if ! command -v cargo >/dev/null 2>&1; then
|
|
37
|
+
echo "cargo is required" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
if [[ ! -f "$PATCHED_APP_RS" ]]; then
|
|
42
|
+
echo "patched source file not found: $PATCHED_APP_RS" >&2
|
|
43
|
+
echo "set CODEX_SRC or PATCHED_APP_RS to your patched codex-rs checkout" >&2
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
if ! command -v rg >/dev/null 2>&1 || ! rg -q 'ROLLOUT_LIVE_SYNC_INTERVAL' "$PATCHED_APP_RS"; then
|
|
48
|
+
echo "warning: patch marker not found in $PATCHED_APP_RS" >&2
|
|
49
|
+
echo "continuing anyway; verify your checkout includes rollout live sync changes" >&2
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if [[ ! -x "$PATCHED_TUI_BIN" ]]; then
|
|
53
|
+
echo "building patched codex-tui at: $CODEX_SRC"
|
|
54
|
+
(cd "$CODEX_SRC" && cargo test -p codex-tui >/dev/null)
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if [[ ! -x "$PATCHED_TUI_BIN" ]]; then
|
|
58
|
+
echo "patched codex-tui binary not found after build: $PATCHED_TUI_BIN" >&2
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
echo "launching patched Codex TUI:"
|
|
63
|
+
echo " $PATCHED_TUI_BIN $*"
|
|
64
|
+
echo
|
|
65
|
+
echo "for bridge/app-server, keep stock codex binary configured:"
|
|
66
|
+
echo " CODEX_CLI_BIN=/opt/homebrew/bin/codex"
|
|
67
|
+
echo
|
|
68
|
+
|
|
69
|
+
exec "$PATCHED_TUI_BIN" "$@"
|
|
@@ -7,5 +7,8 @@ BRIDGE_ALLOW_QUERY_TOKEN_AUTH=false
|
|
|
7
7
|
BRIDGE_ALLOW_OUTSIDE_ROOT_CWD=true
|
|
8
8
|
BRIDGE_DISABLE_TERMINAL_EXEC=false
|
|
9
9
|
BRIDGE_TERMINAL_ALLOWED_COMMANDS=pwd,ls,cat,git
|
|
10
|
+
BRIDGE_CHATGPT_ACCESS_TOKEN=
|
|
11
|
+
BRIDGE_CHATGPT_ACCOUNT_ID=
|
|
12
|
+
BRIDGE_CHATGPT_PLAN_TYPE=
|
|
10
13
|
CODEX_CLI_BIN=codex
|
|
11
14
|
CODEX_CLI_TIMEOUT_MS=180000
|