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.
@@ -4,6 +4,8 @@ on:
4
4
  push:
5
5
  branches:
6
6
  - "**"
7
+ tags:
8
+ - "v*"
7
9
  pull_request:
8
10
  workflow_dispatch:
9
11
 
@@ -11,7 +11,7 @@ concurrency:
11
11
  cancel-in-progress: false
12
12
 
13
13
  permissions:
14
- contents: read
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 legacy payload when modern resume params are rejected', async () => {
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[1]?.[1] as Record<string, unknown>;
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
- // Compatibility fallback for older app-server builds that reject
342
- // experimentalRawEvents or strict policy combinations on resume.
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', legacyRequest);
347
+ await this.ws.request('thread/resume', compatibilityRequest);
351
348
  return;
352
- } catch (legacyError) {
353
- throw new Error(
354
- `thread/resume failed: ${(primaryError as Error).message}; fallback failed: ${(legacyError as Error).message}`
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 = 10_000;
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
- if (shouldRunFromChat) {
4214
- // Only attach a live subscription when the server-reported status
4215
- // is actively running; avoid resuming idle historical chats.
4216
- ensureThreadResumeSubscription(selectedChatId);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdex-mobile",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -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