codex-relay 1.5.1 → 1.6.0

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 CHANGED
@@ -20,7 +20,12 @@ npx codex-relay@latest
20
20
 
21
21
  The CLI prints a QR code, a mobile URL, and a `codex-relay://pair...` pairing payload. Scan the QR code from the mobile app. If the relay detects multiple possible network addresses, the QR includes them and the app automatically uses the first address it can reach. If scanning is not available, paste the full pairing payload into the app.
22
22
 
23
- When the app shows an approval code, approve it on the computer:
23
+ When the app shows an approval code, the interactive relay terminal shows the
24
+ same code and asks `Approve? [y/N]`. Check that the codes match, then type `y`
25
+ and press Enter in that terminal. Other input leaves the request unapproved.
26
+ No second terminal is needed.
27
+
28
+ For background relays or non-interactive output, use the approval command:
24
29
 
25
30
  ```sh
26
31
  npx codex-relay@latest approve XXXX-XXXX
@@ -28,6 +33,14 @@ npx codex-relay@latest approve XXXX-XXXX
28
33
 
29
34
  After approval, the phone can list Codex threads, start new work, stream messages, and handle approval prompts from the local Codex runtime.
30
35
 
36
+ The thread list shows the 20 most recently active, non-archived root sessions across
37
+ workspaces. This does not delete or archive older sessions. Set
38
+ `CODEX_RELAY_THREAD_LIST_LIMIT=100` before starting the relay to show more history.
39
+ The relay reads Codex's session index so refreshing the list does not repeatedly
40
+ parse large legacy rollout files. An empty index triggers a bounded history lookup;
41
+ legacy files copied into a partially populated index may need to be discovered by
42
+ Codex before they appear in the relay.
43
+
31
44
  ## Shared Terminal and Mobile Sessions
32
45
 
33
46
  On macOS, Codex Relay prefers Codex's shared Unix socket so terminal and mobile clients can follow the same live sessions. If the shared app-server cannot start or initialize, the relay prints a warning and continues with a private app-server.
@@ -1,2 +1,2 @@
1
- import { $ as ResolveApprovalResponseSchema, $t as promptSkillDisplayName, A as PairRequestSchema, At as WorkspaceChangesResponseSchema, B as PushNotificationIntentSchema, Bt as WorkspaceTailscaleServeResponseSchema, C as ListModelsResponseSchema, Ct as UpdateRuntimePreferencesRequestSchema, D as ListWorkspaceDirectoriesResponseSchema, Dt as WORKSPACE_PREVIEW_OPEN_PROTOCOL, E as ListThreadsResponseSchema, Et as VersionResponseSchema, F as PromptAttachmentSchema, Ft as WorkspaceMarkdownPreviewTargetSchema, G as RateLimitBucketSchema, Gt as WorkspaceTerminalSessionResponseSchema, H as PushNotificationSettingsResponseSchema, Ht as WorkspaceTerminalOutputChunkSchema, I as PromptAttachmentSummarySchema, It as WorkspacePreviewNavigationRequestSchema, J as ReasoningEffortSchema, Jt as chatMessageDetailsFromPromptContext, K as RateLimitWindowSchema, Kt as WorkspaceTerminalStartRequestSchema, L as PromptContextInputSchema, Lt as WorkspacePreviewTabSchema, M as PendingInputRequestOptionSchema, Mt as WorkspaceFileContentResponseSchema, N as PendingInputRequestQuestionSchema, Nt as WorkspaceFileMentionSchema, O as ListWorkspaceFilesResponseSchema, Ot as WORKSPACE_PREVIEW_TAB_VALUES, P as PendingInputRequestSchema, Pt as WorkspaceGitActionResponseSchema, Q as ResolveApprovalRequestSchema, Qt as promptMarkdownWithSkills, R as PromptContextSchema, Rt as WorkspaceSelectionRequestSchema, S as KnownReasoningEffortSchema, St as ThreadSummarySchema, T as ListSkillsResponseSchema, Tt as UpdateWorkspaceFileContentRequestSchema, U as QueuedThreadInputActionResponseSchema, Ut as WorkspaceTerminalOutputResponseSchema, V as PushNotificationPreferencesSchema, Vt as WorkspaceTerminalInputRequestSchema, W as QueuedThreadInputSchema, Wt as WorkspaceTerminalResizeRequestSchema, X as RenameThreadRequestSchema, Xt as isPromptSkillMarkdownMention, Y as RegisterPushNotificationRequestSchema, Yt as createOpenApiDocument, Z as RenameThreadResponseSchema, Zt as normalizePromptContext, _ as EncryptedPayloadSchema, _t as ThreadGoalStatusSchema, a as ArchiveThreadResponseSchema, at as RuntimePreferencesResponseSchema, b as InterruptThreadRunResponseSchema, bt as ThreadRunOptionsSchema, c as ChatMessageRoleSchema, ct as StatusResponseSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as SubmitThreadInputResponseSchema, en as promptSkillMentionLabel, et as RewindThreadRequestSchema, f as CodexModelSchema, ft as ThreadCollaborationModeSchema, g as CreateThreadResponseSchema, gt as ThreadGoalSchema, h as CreateThreadRequestSchema, ht as ThreadGoalResponseSchema, i as ApprovalModeSchema, it as RuntimePreferencesByWorkspacePathSchema, j as PairResponseSchema, jt as WorkspaceDirectoryEntrySchema, k as PairEncryptedPayloadSchema, kt as WebPreviewTargetSchema, l as ChatMessageSchema, lt as StreamThreadRunEventSchema, m as ContextWindowUsageSchema, mt as ThreadDetailResponseSchema, n as AgentSkillSourceSchema, nn as promptSkillMentionTextCandidates, nt as RunThreadResponseSchema, o as ChatMessageKindSchema, ot as RuntimePreferencesSchema, p as CommitPushWorkspaceRequestSchema, pt as ThreadContextWindowResponseSchema, q as RateLimitsResponseSchema, qt as apiPaths, r as ApprovalDecisionSchema, rn as stripPromptSkillMentions, rt as RuntimeModeSchema, s as ChatMessagePromptDetailsSchema, st as SandboxModeSchema, t as AgentSkillSchema, tn as promptSkillMentionMarkdown, tt as RunThreadRequestSchema, u as ChatMessageStateSchema, ut as StreamThreadRunRequestSchema, v as ErrorResponseSchema, vt as ThreadMessageDetailFieldSchema, w as ListQueuedThreadInputsResponseSchema, wt as UpdateThreadGoalRequestSchema, x as IsoDateTimeSchema, xt as ThreadStateSchema, y as ImageAttachmentUploadResponseSchema, yt as ThreadMessageDetailResponseSchema, z as PromptSkillSchema, zt as WorkspaceTailscaleServeRequestSchema } from "./api-schema2.js";
2
- export { AgentSkillSchema, AgentSkillSourceSchema, ApprovalDecisionSchema, ApprovalModeSchema, ArchiveThreadResponseSchema, ChatMessageKindSchema, ChatMessagePromptDetailsSchema, ChatMessageRoleSchema, ChatMessageSchema, ChatMessageStateSchema, CheckoutWorkspaceBranchRequestSchema, CodexModelSchema, CommitPushWorkspaceRequestSchema, ContextWindowUsageSchema, CreateThreadRequestSchema, CreateThreadResponseSchema, EncryptedPayloadSchema, ErrorResponseSchema, ImageAttachmentUploadResponseSchema, InterruptThreadRunResponseSchema, IsoDateTimeSchema, KnownReasoningEffortSchema, ListModelsResponseSchema, ListQueuedThreadInputsResponseSchema, ListSkillsResponseSchema, ListThreadsResponseSchema, ListWorkspaceDirectoriesResponseSchema, ListWorkspaceFilesResponseSchema, PairEncryptedPayloadSchema, PairRequestSchema, PairResponseSchema, PendingInputRequestOptionSchema, PendingInputRequestQuestionSchema, PendingInputRequestSchema, PromptAttachmentSchema, PromptAttachmentSummarySchema, PromptContextInputSchema, PromptContextSchema, PromptSkillSchema, PushNotificationIntentSchema, PushNotificationPreferencesSchema, PushNotificationSettingsResponseSchema, QueuedThreadInputActionResponseSchema, QueuedThreadInputSchema, RateLimitBucketSchema, RateLimitWindowSchema, RateLimitsResponseSchema, ReasoningEffortSchema, RegisterPushNotificationRequestSchema, RenameThreadRequestSchema, RenameThreadResponseSchema, ResolveApprovalRequestSchema, ResolveApprovalResponseSchema, RewindThreadRequestSchema, RunThreadRequestSchema, RunThreadResponseSchema, RuntimeModeSchema, RuntimePreferencesByWorkspacePathSchema, RuntimePreferencesResponseSchema, RuntimePreferencesSchema, SandboxModeSchema, StatusResponseSchema, StreamThreadRunEventSchema, StreamThreadRunRequestSchema, SubmitThreadInputResponseSchema, ThreadCollaborationModeSchema, ThreadContextWindowResponseSchema, ThreadDetailResponseSchema, ThreadGoalResponseSchema, ThreadGoalSchema, ThreadGoalStatusSchema, ThreadMessageDetailFieldSchema, ThreadMessageDetailResponseSchema, ThreadRunOptionsSchema, ThreadStateSchema, ThreadSummarySchema, UpdateRuntimePreferencesRequestSchema, UpdateThreadGoalRequestSchema, UpdateWorkspaceFileContentRequestSchema, VersionResponseSchema, WORKSPACE_PREVIEW_OPEN_PROTOCOL, WORKSPACE_PREVIEW_TAB_VALUES, WebPreviewTargetSchema, WorkspaceChangesResponseSchema, WorkspaceDirectoryEntrySchema, WorkspaceFileContentResponseSchema, WorkspaceFileMentionSchema, WorkspaceGitActionResponseSchema, WorkspaceMarkdownPreviewTargetSchema, WorkspacePreviewNavigationRequestSchema, WorkspacePreviewTabSchema, WorkspaceSelectionRequestSchema, WorkspaceTailscaleServeRequestSchema, WorkspaceTailscaleServeResponseSchema, WorkspaceTerminalInputRequestSchema, WorkspaceTerminalOutputChunkSchema, WorkspaceTerminalOutputResponseSchema, WorkspaceTerminalResizeRequestSchema, WorkspaceTerminalSessionResponseSchema, WorkspaceTerminalStartRequestSchema, apiPaths, chatMessageDetailsFromPromptContext, createOpenApiDocument, isPromptSkillMarkdownMention, normalizePromptContext, promptMarkdownWithSkills, promptSkillDisplayName, promptSkillMentionLabel, promptSkillMentionMarkdown, promptSkillMentionTextCandidates, stripPromptSkillMentions };
1
+ import { $ as ResolveApprovalResponseSchema, $t as promptMarkdownWithSkills, A as PairRequestSchema, At as WorkspaceChangesResponseSchema, B as PushNotificationIntentSchema, Bt as WorkspaceTailscaleServeResponseSchema, C as ListModelsResponseSchema, Ct as UpdateRuntimePreferencesRequestSchema, D as ListWorkspaceDirectoriesResponseSchema, Dt as WORKSPACE_PREVIEW_OPEN_PROTOCOL, E as ListThreadsResponseSchema, Et as VersionResponseSchema, F as PromptAttachmentSchema, Ft as WorkspaceMarkdownPreviewTargetSchema, G as RateLimitBucketSchema, Gt as WorkspaceTerminalSessionResponseSchema, H as PushNotificationSettingsResponseSchema, Ht as WorkspaceTerminalOutputChunkSchema, I as PromptAttachmentSummarySchema, It as WorkspacePreviewNavigationRequestSchema, J as ReasoningEffortSchema, Jt as chatMessageDetailsFromPromptContext, K as RateLimitWindowSchema, Kt as WorkspaceTerminalStartRequestSchema, L as PromptContextInputSchema, Lt as WorkspacePreviewTabSchema, M as PendingInputRequestOptionSchema, Mt as WorkspaceFileContentResponseSchema, N as PendingInputRequestQuestionSchema, Nt as WorkspaceFileMentionSchema, O as ListWorkspaceFilesResponseSchema, Ot as WORKSPACE_PREVIEW_TAB_VALUES, P as PendingInputRequestSchema, Pt as WorkspaceGitActionResponseSchema, Q as ResolveApprovalRequestSchema, Qt as normalizePromptContext, R as PromptContextSchema, Rt as WorkspaceSelectionRequestSchema, S as KnownReasoningEffortSchema, St as ThreadSummarySchema, T as ListSkillsResponseSchema, Tt as UpdateWorkspaceFileContentRequestSchema, U as QueuedThreadInputActionResponseSchema, Ut as WorkspaceTerminalOutputResponseSchema, V as PushNotificationPreferencesSchema, Vt as WorkspaceTerminalInputRequestSchema, W as QueuedThreadInputSchema, Wt as WorkspaceTerminalResizeRequestSchema, X as RenameThreadRequestSchema, Xt as isPromptSkillMarkdownMention, Y as RegisterPushNotificationRequestSchema, Yt as createOpenApiDocument, Z as RenameThreadResponseSchema, Zt as lowestRateLimitRemainingPercent, _ as EncryptedPayloadSchema, _t as ThreadGoalStatusSchema, a as ArchiveThreadResponseSchema, an as stripPromptSkillMentions, at as RuntimePreferencesResponseSchema, b as InterruptThreadRunResponseSchema, bt as ThreadRunOptionsSchema, c as ChatMessageRoleSchema, ct as StatusResponseSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as SubmitThreadInputResponseSchema, en as promptSkillDisplayName, et as RewindThreadRequestSchema, f as CodexModelSchema, ft as ThreadCollaborationModeSchema, g as CreateThreadResponseSchema, gt as ThreadGoalSchema, h as CreateThreadRequestSchema, ht as ThreadGoalResponseSchema, i as ApprovalModeSchema, in as rateLimitRemainingPercent, it as RuntimePreferencesByWorkspacePathSchema, j as PairResponseSchema, jt as WorkspaceDirectoryEntrySchema, k as PairEncryptedPayloadSchema, kt as WebPreviewTargetSchema, l as ChatMessageSchema, lt as StreamThreadRunEventSchema, m as ContextWindowUsageSchema, mt as ThreadDetailResponseSchema, n as AgentSkillSourceSchema, nn as promptSkillMentionMarkdown, nt as RunThreadResponseSchema, o as ChatMessageKindSchema, ot as RuntimePreferencesSchema, p as CommitPushWorkspaceRequestSchema, pt as ThreadContextWindowResponseSchema, q as RateLimitsResponseSchema, qt as apiPaths, r as ApprovalDecisionSchema, rn as promptSkillMentionTextCandidates, rt as RuntimeModeSchema, s as ChatMessagePromptDetailsSchema, st as SandboxModeSchema, t as AgentSkillSchema, tn as promptSkillMentionLabel, tt as RunThreadRequestSchema, u as ChatMessageStateSchema, ut as StreamThreadRunRequestSchema, v as ErrorResponseSchema, vt as ThreadMessageDetailFieldSchema, w as ListQueuedThreadInputsResponseSchema, wt as UpdateThreadGoalRequestSchema, x as IsoDateTimeSchema, xt as ThreadStateSchema, y as ImageAttachmentUploadResponseSchema, yt as ThreadMessageDetailResponseSchema, z as PromptSkillSchema, zt as WorkspaceTailscaleServeRequestSchema } from "./api-schema2.js";
2
+ export { AgentSkillSchema, AgentSkillSourceSchema, ApprovalDecisionSchema, ApprovalModeSchema, ArchiveThreadResponseSchema, ChatMessageKindSchema, ChatMessagePromptDetailsSchema, ChatMessageRoleSchema, ChatMessageSchema, ChatMessageStateSchema, CheckoutWorkspaceBranchRequestSchema, CodexModelSchema, CommitPushWorkspaceRequestSchema, ContextWindowUsageSchema, CreateThreadRequestSchema, CreateThreadResponseSchema, EncryptedPayloadSchema, ErrorResponseSchema, ImageAttachmentUploadResponseSchema, InterruptThreadRunResponseSchema, IsoDateTimeSchema, KnownReasoningEffortSchema, ListModelsResponseSchema, ListQueuedThreadInputsResponseSchema, ListSkillsResponseSchema, ListThreadsResponseSchema, ListWorkspaceDirectoriesResponseSchema, ListWorkspaceFilesResponseSchema, PairEncryptedPayloadSchema, PairRequestSchema, PairResponseSchema, PendingInputRequestOptionSchema, PendingInputRequestQuestionSchema, PendingInputRequestSchema, PromptAttachmentSchema, PromptAttachmentSummarySchema, PromptContextInputSchema, PromptContextSchema, PromptSkillSchema, PushNotificationIntentSchema, PushNotificationPreferencesSchema, PushNotificationSettingsResponseSchema, QueuedThreadInputActionResponseSchema, QueuedThreadInputSchema, RateLimitBucketSchema, RateLimitWindowSchema, RateLimitsResponseSchema, ReasoningEffortSchema, RegisterPushNotificationRequestSchema, RenameThreadRequestSchema, RenameThreadResponseSchema, ResolveApprovalRequestSchema, ResolveApprovalResponseSchema, RewindThreadRequestSchema, RunThreadRequestSchema, RunThreadResponseSchema, RuntimeModeSchema, RuntimePreferencesByWorkspacePathSchema, RuntimePreferencesResponseSchema, RuntimePreferencesSchema, SandboxModeSchema, StatusResponseSchema, StreamThreadRunEventSchema, StreamThreadRunRequestSchema, SubmitThreadInputResponseSchema, ThreadCollaborationModeSchema, ThreadContextWindowResponseSchema, ThreadDetailResponseSchema, ThreadGoalResponseSchema, ThreadGoalSchema, ThreadGoalStatusSchema, ThreadMessageDetailFieldSchema, ThreadMessageDetailResponseSchema, ThreadRunOptionsSchema, ThreadStateSchema, ThreadSummarySchema, UpdateRuntimePreferencesRequestSchema, UpdateThreadGoalRequestSchema, UpdateWorkspaceFileContentRequestSchema, VersionResponseSchema, WORKSPACE_PREVIEW_OPEN_PROTOCOL, WORKSPACE_PREVIEW_TAB_VALUES, WebPreviewTargetSchema, WorkspaceChangesResponseSchema, WorkspaceDirectoryEntrySchema, WorkspaceFileContentResponseSchema, WorkspaceFileMentionSchema, WorkspaceGitActionResponseSchema, WorkspaceMarkdownPreviewTargetSchema, WorkspacePreviewNavigationRequestSchema, WorkspacePreviewTabSchema, WorkspaceSelectionRequestSchema, WorkspaceTailscaleServeRequestSchema, WorkspaceTailscaleServeResponseSchema, WorkspaceTerminalInputRequestSchema, WorkspaceTerminalOutputChunkSchema, WorkspaceTerminalOutputResponseSchema, WorkspaceTerminalResizeRequestSchema, WorkspaceTerminalSessionResponseSchema, WorkspaceTerminalStartRequestSchema, apiPaths, chatMessageDetailsFromPromptContext, createOpenApiDocument, isPromptSkillMarkdownMention, lowestRateLimitRemainingPercent, normalizePromptContext, promptMarkdownWithSkills, promptSkillDisplayName, promptSkillMentionLabel, promptSkillMentionMarkdown, promptSkillMentionTextCandidates, rateLimitRemainingPercent, stripPromptSkillMentions };
@@ -107,6 +107,7 @@ const RuntimePreferencesResponseSchema = z.object({
107
107
  const PushNotificationIntentSchema = z.enum(["turn_terminal", "action_required"]);
108
108
  const PushNotificationPreferencesSchema = z.object({
109
109
  actionRequired: z.boolean(),
110
+ includeRemainingUsage: z.boolean().default(false),
110
111
  turnTerminal: z.boolean()
111
112
  });
112
113
  const RegisterPushNotificationRequestSchema = z.object({
@@ -169,6 +170,13 @@ const RateLimitBucketSchema = z.object({
169
170
  rateLimitReachedType: z.string().nullable().optional()
170
171
  });
171
172
  const RateLimitsResponseSchema = z.object({ buckets: z.array(RateLimitBucketSchema) });
173
+ function rateLimitRemainingPercent(usedPercent) {
174
+ return 100 - Math.max(0, Math.min(100, usedPercent));
175
+ }
176
+ function lowestRateLimitRemainingPercent(buckets) {
177
+ const remaining = buckets.flatMap((bucket) => [bucket.primary, bucket.secondary].flatMap((window) => window ? [rateLimitRemainingPercent(window.usedPercent)] : []));
178
+ return remaining.length > 0 ? Math.min(...remaining) : void 0;
179
+ }
172
180
  const ThreadContextWindowResponseSchema = z.object({
173
181
  threadId: z.string().min(1),
174
182
  usage: ContextWindowUsageSchema.nullable(),
@@ -1355,6 +1363,10 @@ function createOpenApiDocument() {
1355
1363
  required: ["actionRequired", "turnTerminal"],
1356
1364
  properties: {
1357
1365
  actionRequired: { type: "boolean" },
1366
+ includeRemainingUsage: {
1367
+ type: "boolean",
1368
+ default: false
1369
+ },
1358
1370
  turnTerminal: { type: "boolean" }
1359
1371
  }
1360
1372
  },
@@ -1867,4 +1879,4 @@ function jsonResponse(schemaName) {
1867
1879
  };
1868
1880
  }
1869
1881
  //#endregion
1870
- export { ResolveApprovalResponseSchema as $, promptSkillDisplayName as $t, PairRequestSchema as A, WorkspaceChangesResponseSchema as At, PushNotificationIntentSchema as B, WorkspaceTailscaleServeResponseSchema as Bt, ListModelsResponseSchema as C, UpdateRuntimePreferencesRequestSchema as Ct, ListWorkspaceDirectoriesResponseSchema as D, WORKSPACE_PREVIEW_OPEN_PROTOCOL as Dt, ListThreadsResponseSchema as E, VersionResponseSchema as Et, PromptAttachmentSchema as F, WorkspaceMarkdownPreviewTargetSchema as Ft, RateLimitBucketSchema as G, WorkspaceTerminalSessionResponseSchema as Gt, PushNotificationSettingsResponseSchema as H, WorkspaceTerminalOutputChunkSchema as Ht, PromptAttachmentSummarySchema as I, WorkspacePreviewNavigationRequestSchema as It, ReasoningEffortSchema as J, chatMessageDetailsFromPromptContext as Jt, RateLimitWindowSchema as K, WorkspaceTerminalStartRequestSchema as Kt, PromptContextInputSchema as L, WorkspacePreviewTabSchema as Lt, PendingInputRequestOptionSchema as M, WorkspaceFileContentResponseSchema as Mt, PendingInputRequestQuestionSchema as N, WorkspaceFileMentionSchema as Nt, ListWorkspaceFilesResponseSchema as O, WORKSPACE_PREVIEW_TAB_VALUES as Ot, PendingInputRequestSchema as P, WorkspaceGitActionResponseSchema as Pt, ResolveApprovalRequestSchema as Q, promptMarkdownWithSkills as Qt, PromptContextSchema as R, WorkspaceSelectionRequestSchema as Rt, KnownReasoningEffortSchema as S, ThreadSummarySchema as St, ListSkillsResponseSchema as T, UpdateWorkspaceFileContentRequestSchema as Tt, QueuedThreadInputActionResponseSchema as U, WorkspaceTerminalOutputResponseSchema as Ut, PushNotificationPreferencesSchema as V, WorkspaceTerminalInputRequestSchema as Vt, QueuedThreadInputSchema as W, WorkspaceTerminalResizeRequestSchema as Wt, RenameThreadRequestSchema as X, isPromptSkillMarkdownMention as Xt, RegisterPushNotificationRequestSchema as Y, createOpenApiDocument as Yt, RenameThreadResponseSchema as Z, normalizePromptContext as Zt, EncryptedPayloadSchema as _, ThreadGoalStatusSchema as _t, ArchiveThreadResponseSchema as a, RuntimePreferencesResponseSchema as at, InterruptThreadRunResponseSchema as b, ThreadRunOptionsSchema as bt, ChatMessageRoleSchema as c, StatusResponseSchema as ct, CheckoutWorkspaceBranchRequestSchema as d, SubmitThreadInputResponseSchema as dt, promptSkillMentionLabel as en, RewindThreadRequestSchema as et, CodexModelSchema as f, ThreadCollaborationModeSchema as ft, CreateThreadResponseSchema as g, ThreadGoalSchema as gt, CreateThreadRequestSchema as h, ThreadGoalResponseSchema as ht, ApprovalModeSchema as i, RuntimePreferencesByWorkspacePathSchema as it, PairResponseSchema as j, WorkspaceDirectoryEntrySchema as jt, PairEncryptedPayloadSchema as k, WebPreviewTargetSchema as kt, ChatMessageSchema as l, StreamThreadRunEventSchema as lt, ContextWindowUsageSchema as m, ThreadDetailResponseSchema as mt, AgentSkillSourceSchema as n, promptSkillMentionTextCandidates as nn, RunThreadResponseSchema as nt, ChatMessageKindSchema as o, RuntimePreferencesSchema as ot, CommitPushWorkspaceRequestSchema as p, ThreadContextWindowResponseSchema as pt, RateLimitsResponseSchema as q, apiPaths as qt, ApprovalDecisionSchema as r, stripPromptSkillMentions as rn, RuntimeModeSchema as rt, ChatMessagePromptDetailsSchema as s, SandboxModeSchema as st, AgentSkillSchema as t, promptSkillMentionMarkdown as tn, RunThreadRequestSchema as tt, ChatMessageStateSchema as u, StreamThreadRunRequestSchema as ut, ErrorResponseSchema as v, ThreadMessageDetailFieldSchema as vt, ListQueuedThreadInputsResponseSchema as w, UpdateThreadGoalRequestSchema as wt, IsoDateTimeSchema as x, ThreadStateSchema as xt, ImageAttachmentUploadResponseSchema as y, ThreadMessageDetailResponseSchema as yt, PromptSkillSchema as z, WorkspaceTailscaleServeRequestSchema as zt };
1882
+ export { ResolveApprovalResponseSchema as $, promptMarkdownWithSkills as $t, PairRequestSchema as A, WorkspaceChangesResponseSchema as At, PushNotificationIntentSchema as B, WorkspaceTailscaleServeResponseSchema as Bt, ListModelsResponseSchema as C, UpdateRuntimePreferencesRequestSchema as Ct, ListWorkspaceDirectoriesResponseSchema as D, WORKSPACE_PREVIEW_OPEN_PROTOCOL as Dt, ListThreadsResponseSchema as E, VersionResponseSchema as Et, PromptAttachmentSchema as F, WorkspaceMarkdownPreviewTargetSchema as Ft, RateLimitBucketSchema as G, WorkspaceTerminalSessionResponseSchema as Gt, PushNotificationSettingsResponseSchema as H, WorkspaceTerminalOutputChunkSchema as Ht, PromptAttachmentSummarySchema as I, WorkspacePreviewNavigationRequestSchema as It, ReasoningEffortSchema as J, chatMessageDetailsFromPromptContext as Jt, RateLimitWindowSchema as K, WorkspaceTerminalStartRequestSchema as Kt, PromptContextInputSchema as L, WorkspacePreviewTabSchema as Lt, PendingInputRequestOptionSchema as M, WorkspaceFileContentResponseSchema as Mt, PendingInputRequestQuestionSchema as N, WorkspaceFileMentionSchema as Nt, ListWorkspaceFilesResponseSchema as O, WORKSPACE_PREVIEW_TAB_VALUES as Ot, PendingInputRequestSchema as P, WorkspaceGitActionResponseSchema as Pt, ResolveApprovalRequestSchema as Q, normalizePromptContext as Qt, PromptContextSchema as R, WorkspaceSelectionRequestSchema as Rt, KnownReasoningEffortSchema as S, ThreadSummarySchema as St, ListSkillsResponseSchema as T, UpdateWorkspaceFileContentRequestSchema as Tt, QueuedThreadInputActionResponseSchema as U, WorkspaceTerminalOutputResponseSchema as Ut, PushNotificationPreferencesSchema as V, WorkspaceTerminalInputRequestSchema as Vt, QueuedThreadInputSchema as W, WorkspaceTerminalResizeRequestSchema as Wt, RenameThreadRequestSchema as X, isPromptSkillMarkdownMention as Xt, RegisterPushNotificationRequestSchema as Y, createOpenApiDocument as Yt, RenameThreadResponseSchema as Z, lowestRateLimitRemainingPercent as Zt, EncryptedPayloadSchema as _, ThreadGoalStatusSchema as _t, ArchiveThreadResponseSchema as a, stripPromptSkillMentions as an, RuntimePreferencesResponseSchema as at, InterruptThreadRunResponseSchema as b, ThreadRunOptionsSchema as bt, ChatMessageRoleSchema as c, StatusResponseSchema as ct, CheckoutWorkspaceBranchRequestSchema as d, SubmitThreadInputResponseSchema as dt, promptSkillDisplayName as en, RewindThreadRequestSchema as et, CodexModelSchema as f, ThreadCollaborationModeSchema as ft, CreateThreadResponseSchema as g, ThreadGoalSchema as gt, CreateThreadRequestSchema as h, ThreadGoalResponseSchema as ht, ApprovalModeSchema as i, rateLimitRemainingPercent as in, RuntimePreferencesByWorkspacePathSchema as it, PairResponseSchema as j, WorkspaceDirectoryEntrySchema as jt, PairEncryptedPayloadSchema as k, WebPreviewTargetSchema as kt, ChatMessageSchema as l, StreamThreadRunEventSchema as lt, ContextWindowUsageSchema as m, ThreadDetailResponseSchema as mt, AgentSkillSourceSchema as n, promptSkillMentionMarkdown as nn, RunThreadResponseSchema as nt, ChatMessageKindSchema as o, RuntimePreferencesSchema as ot, CommitPushWorkspaceRequestSchema as p, ThreadContextWindowResponseSchema as pt, RateLimitsResponseSchema as q, apiPaths as qt, ApprovalDecisionSchema as r, promptSkillMentionTextCandidates as rn, RuntimeModeSchema as rt, ChatMessagePromptDetailsSchema as s, SandboxModeSchema as st, AgentSkillSchema as t, promptSkillMentionLabel as tn, RunThreadRequestSchema as tt, ChatMessageStateSchema as u, StreamThreadRunRequestSchema as ut, ErrorResponseSchema as v, ThreadMessageDetailFieldSchema as vt, ListQueuedThreadInputsResponseSchema as w, UpdateThreadGoalRequestSchema as wt, IsoDateTimeSchema as x, ThreadStateSchema as xt, ImageAttachmentUploadResponseSchema as y, ThreadMessageDetailResponseSchema as yt, PromptSkillSchema as z, WorkspaceTailscaleServeRequestSchema as zt };
package/dist/paths.js CHANGED
@@ -130,11 +130,13 @@ async function createTursoPairingSessionStore(path) {
130
130
  platform TEXT NOT NULL,
131
131
  turn_terminal_enabled INTEGER NOT NULL,
132
132
  action_required_enabled INTEGER NOT NULL,
133
+ include_remaining_usage_enabled INTEGER NOT NULL DEFAULT 0,
133
134
  created_at INTEGER NOT NULL,
134
135
  updated_at INTEGER NOT NULL
135
136
  );
136
137
  `);
137
138
  await ensurePairingSessionColumns();
139
+ await ensurePushNotificationSubscriptionColumns();
138
140
  async function countActive() {
139
141
  const row = await db.prepare("SELECT COUNT(DISTINCT COALESCE(client_session_id, token_hash)) AS count FROM pairing_sessions").get();
140
142
  return Number(row?.count ?? 0);
@@ -250,7 +252,8 @@ async function createTursoPairingSessionStore(path) {
250
252
  expo_push_token AS expoPushToken,
251
253
  platform,
252
254
  turn_terminal_enabled AS turnTerminal,
253
- action_required_enabled AS actionRequired
255
+ action_required_enabled AS actionRequired,
256
+ include_remaining_usage_enabled AS includeRemainingUsage
254
257
  FROM push_notification_subscriptions
255
258
  WHERE client_session_id = ?`).get(clientSessionId);
256
259
  return row ? pushNotificationSubscriptionFromRow(row) : void 0;
@@ -279,7 +282,8 @@ async function createTursoPairingSessionStore(path) {
279
282
  subscriptions.expo_push_token AS expoPushToken,
280
283
  subscriptions.platform,
281
284
  subscriptions.turn_terminal_enabled AS turnTerminal,
282
- subscriptions.action_required_enabled AS actionRequired
285
+ subscriptions.action_required_enabled AS actionRequired,
286
+ subscriptions.include_remaining_usage_enabled AS includeRemainingUsage
283
287
  FROM push_notification_subscriptions AS subscriptions
284
288
  INNER JOIN pairing_sessions AS sessions
285
289
  ON sessions.client_session_id = subscriptions.client_session_id
@@ -343,16 +347,18 @@ async function createTursoPairingSessionStore(path) {
343
347
  platform,
344
348
  turn_terminal_enabled,
345
349
  action_required_enabled,
350
+ include_remaining_usage_enabled,
346
351
  created_at,
347
352
  updated_at
348
353
  )
349
- VALUES (?, ?, ?, ?, ?, ?, ?)
354
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
350
355
  ON CONFLICT(client_session_id) DO UPDATE SET
351
356
  expo_push_token = excluded.expo_push_token,
352
357
  platform = excluded.platform,
353
358
  turn_terminal_enabled = excluded.turn_terminal_enabled,
354
359
  action_required_enabled = excluded.action_required_enabled,
355
- updated_at = excluded.updated_at`).run(subscription.clientSessionId, subscription.expoPushToken, subscription.platform, subscription.turnTerminal ? 1 : 0, subscription.actionRequired ? 1 : 0, now, now);
360
+ include_remaining_usage_enabled = excluded.include_remaining_usage_enabled,
361
+ updated_at = excluded.updated_at`).run(subscription.clientSessionId, subscription.expoPushToken, subscription.platform, subscription.turnTerminal ? 1 : 0, subscription.actionRequired ? 1 : 0, subscription.includeRemainingUsage ? 1 : 0, now, now);
356
362
  }
357
363
  };
358
364
  async function ensurePairingSessionColumns() {
@@ -369,6 +375,10 @@ async function createTursoPairingSessionStore(path) {
369
375
  const pendingRows = await db.prepare("PRAGMA table_info(pending_pairings)").all();
370
376
  if (!new Set(resultRows(pendingRows).map((row) => String(row.name))).has("client_session_id")) await db.exec("ALTER TABLE pending_pairings ADD COLUMN client_session_id TEXT");
371
377
  }
378
+ async function ensurePushNotificationSubscriptionColumns() {
379
+ const rows = await db.prepare("PRAGMA table_info(push_notification_subscriptions)").all();
380
+ if (!new Set(resultRows(rows).map((row) => String(row.name))).has("include_remaining_usage_enabled")) await db.exec("ALTER TABLE push_notification_subscriptions ADD COLUMN include_remaining_usage_enabled INTEGER NOT NULL DEFAULT 0");
381
+ }
372
382
  }
373
383
  function encodeSecureSession(session) {
374
384
  if (!session) return;
@@ -397,6 +407,7 @@ function pushNotificationSubscriptionFromRow(row) {
397
407
  actionRequired: Number(row.actionRequired) === 1,
398
408
  clientSessionId: row.clientSessionId,
399
409
  expoPushToken: row.expoPushToken,
410
+ includeRemainingUsage: Number(row.includeRemainingUsage) === 1,
400
411
  platform,
401
412
  turnTerminal: Number(row.turnTerminal) === 1
402
413
  };
package/dist/src.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as ResolveApprovalResponseSchema, A as PairRequestSchema, At as WorkspaceChangesResponseSchema, Bt as WorkspaceTailscaleServeResponseSchema, C as ListModelsResponseSchema, Ct as UpdateRuntimePreferencesRequestSchema, D as ListWorkspaceDirectoriesResponseSchema, E as ListThreadsResponseSchema, Et as VersionResponseSchema, Gt as WorkspaceTerminalSessionResponseSchema, H as PushNotificationSettingsResponseSchema, J as ReasoningEffortSchema, Jt as chatMessageDetailsFromPromptContext, Kt as WorkspaceTerminalStartRequestSchema, Mt as WorkspaceFileContentResponseSchema, O as ListWorkspaceFilesResponseSchema, Pt as WorkspaceGitActionResponseSchema, Q as ResolveApprovalRequestSchema, Qt as promptMarkdownWithSkills, S as KnownReasoningEffortSchema, St as ThreadSummarySchema, T as ListSkillsResponseSchema, Tt as UpdateWorkspaceFileContentRequestSchema, U as QueuedThreadInputActionResponseSchema, Ut as WorkspaceTerminalOutputResponseSchema, Vt as WorkspaceTerminalInputRequestSchema, Wt as WorkspaceTerminalResizeRequestSchema, X as RenameThreadRequestSchema, Y as RegisterPushNotificationRequestSchema, Yt as createOpenApiDocument, Z as RenameThreadResponseSchema, Zt as normalizePromptContext, _ as EncryptedPayloadSchema, _t as ThreadGoalStatusSchema, a as ArchiveThreadResponseSchema, at as RuntimePreferencesResponseSchema, b as InterruptThreadRunResponseSchema, ct as StatusResponseSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as SubmitThreadInputResponseSchema, et as RewindThreadRequestSchema, gt as ThreadGoalSchema, h as CreateThreadRequestSchema, ht as ThreadGoalResponseSchema, j as PairResponseSchema, l as ChatMessageSchema, lt as StreamThreadRunEventSchema, m as ContextWindowUsageSchema, mt as ThreadDetailResponseSchema, ot as RuntimePreferencesSchema, p as CommitPushWorkspaceRequestSchema, pt as ThreadContextWindowResponseSchema, q as RateLimitsResponseSchema, qt as apiPaths, rn as stripPromptSkillMentions, tt as RunThreadRequestSchema, ut as StreamThreadRunRequestSchema, vt as ThreadMessageDetailFieldSchema, w as ListQueuedThreadInputsResponseSchema, wt as UpdateThreadGoalRequestSchema, y as ImageAttachmentUploadResponseSchema, yt as ThreadMessageDetailResponseSchema, zt as WorkspaceTailscaleServeRequestSchema } from "./api-schema2.js";
1
+ import { $ as ResolveApprovalResponseSchema, $t as promptMarkdownWithSkills, A as PairRequestSchema, At as WorkspaceChangesResponseSchema, Bt as WorkspaceTailscaleServeResponseSchema, C as ListModelsResponseSchema, Ct as UpdateRuntimePreferencesRequestSchema, D as ListWorkspaceDirectoriesResponseSchema, E as ListThreadsResponseSchema, Et as VersionResponseSchema, Gt as WorkspaceTerminalSessionResponseSchema, H as PushNotificationSettingsResponseSchema, J as ReasoningEffortSchema, Jt as chatMessageDetailsFromPromptContext, Kt as WorkspaceTerminalStartRequestSchema, Mt as WorkspaceFileContentResponseSchema, O as ListWorkspaceFilesResponseSchema, Pt as WorkspaceGitActionResponseSchema, Q as ResolveApprovalRequestSchema, Qt as normalizePromptContext, S as KnownReasoningEffortSchema, St as ThreadSummarySchema, T as ListSkillsResponseSchema, Tt as UpdateWorkspaceFileContentRequestSchema, U as QueuedThreadInputActionResponseSchema, Ut as WorkspaceTerminalOutputResponseSchema, Vt as WorkspaceTerminalInputRequestSchema, Wt as WorkspaceTerminalResizeRequestSchema, X as RenameThreadRequestSchema, Y as RegisterPushNotificationRequestSchema, Yt as createOpenApiDocument, Z as RenameThreadResponseSchema, Zt as lowestRateLimitRemainingPercent, _ as EncryptedPayloadSchema, _t as ThreadGoalStatusSchema, a as ArchiveThreadResponseSchema, an as stripPromptSkillMentions, at as RuntimePreferencesResponseSchema, b as InterruptThreadRunResponseSchema, ct as StatusResponseSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as SubmitThreadInputResponseSchema, et as RewindThreadRequestSchema, gt as ThreadGoalSchema, h as CreateThreadRequestSchema, ht as ThreadGoalResponseSchema, j as PairResponseSchema, l as ChatMessageSchema, lt as StreamThreadRunEventSchema, m as ContextWindowUsageSchema, mt as ThreadDetailResponseSchema, ot as RuntimePreferencesSchema, p as CommitPushWorkspaceRequestSchema, pt as ThreadContextWindowResponseSchema, q as RateLimitsResponseSchema, qt as apiPaths, tt as RunThreadRequestSchema, ut as StreamThreadRunRequestSchema, vt as ThreadMessageDetailFieldSchema, w as ListQueuedThreadInputsResponseSchema, wt as UpdateThreadGoalRequestSchema, y as ImageAttachmentUploadResponseSchema, yt as ThreadMessageDetailResponseSchema, zt as WorkspaceTailscaleServeRequestSchema } from "./api-schema2.js";
2
2
  import { a as getConnectUrlCandidates, c as permanentClientSessionExpiresAt, i as createPairingQrPayload, o as getConnectUrlGuidance, r as legacyCodexRelayDataPath, s as createTursoPairingSessionStore, t as codexRelayDataPath } from "./paths.js";
3
3
  import { createRequire } from "node:module";
4
4
  import qrcode from "qrcode-terminal";
@@ -188,34 +188,32 @@ var CodexAppServerClient = class {
188
188
  initialize() {
189
189
  return this.ensureInitialized();
190
190
  }
191
- async listThreads(limit = 80) {
192
- const threads = [];
193
- const seenCursors = /* @__PURE__ */ new Set();
194
- let cursor;
195
- do {
196
- const response = await this.request("thread/list", {
197
- archived: false,
198
- limit,
199
- sortDirection: "desc",
200
- sortKey: "recency_at",
201
- sourceKinds: [
202
- "cli",
203
- "vscode",
204
- "exec",
205
- "appServer"
206
- ],
207
- ...cursor ? {
208
- cursor,
209
- useStateDbOnly: true
210
- } : {}
211
- });
212
- threads.push(...response.data);
213
- const nextCursor = response.nextCursor ?? void 0;
214
- if (!nextCursor || seenCursors.has(nextCursor)) break;
215
- seenCursors.add(nextCursor);
216
- cursor = nextCursor;
217
- } while (cursor);
218
- return threads;
191
+ async listThreads(limit = Number(process.env.CODEX_RELAY_THREAD_LIST_LIMIT ?? 20)) {
192
+ if (!Number.isSafeInteger(limit) || limit < 1) throw new Error("CODEX_RELAY_THREAD_LIST_LIMIT must be a positive integer.");
193
+ const startedAt = Date.now();
194
+ const params = {
195
+ archived: false,
196
+ limit,
197
+ sortDirection: "desc",
198
+ sortKey: "recency_at",
199
+ sourceKinds: [
200
+ "cli",
201
+ "vscode",
202
+ "exec",
203
+ "appServer"
204
+ ]
205
+ };
206
+ let response = await this.request("thread/list", {
207
+ ...params,
208
+ useStateDbOnly: true
209
+ });
210
+ if (response.data.length === 0) response = await this.request("thread/list", params);
211
+ relayDebugLog("thread.list.completed", {
212
+ durationMs: Date.now() - startedAt,
213
+ limit,
214
+ count: response.data.length
215
+ });
216
+ return response.data.slice(0, limit);
219
217
  }
220
218
  async readThread(threadId, options = {}) {
221
219
  return (await this.request("thread/read", {
@@ -1029,24 +1027,25 @@ function createExpoPushNotificationSender(fetchImpl = fetch) {
1029
1027
  function createPushNotificationDispatcher(input) {
1030
1028
  return { async dispatch(event) {
1031
1029
  const subscriptions = await input.sessions.listActivePushNotificationSubscriptions();
1032
- const selectedTokens = new Set(subscriptions.filter((subscription) => notificationEnabled(subscription, event.intent)).map((subscription) => subscription.expoPushToken));
1033
- if (selectedTokens.size === 0) return;
1034
- const delivery = await input.sender.send([...selectedTokens].map((expoPushToken) => notificationForEvent(expoPushToken, event)));
1030
+ const selectedSubscriptions = new Map(subscriptions.filter((subscription) => notificationEnabled(subscription, event.intent)).map((subscription) => [subscription.expoPushToken, subscription]));
1031
+ if (selectedSubscriptions.size === 0) return;
1032
+ const remainingUsagePercent = event.intent === "turn_terminal" && [...selectedSubscriptions.values()].some((subscription) => subscription.includeRemainingUsage) ? await input.readRemainingUsagePercent?.().catch(() => void 0) : void 0;
1033
+ const delivery = await input.sender.send([...selectedSubscriptions.values()].map((subscription) => notificationForEvent(subscription.expoPushToken, event, subscription.includeRemainingUsage ? remainingUsagePercent : void 0)));
1035
1034
  await Promise.all(delivery.invalidExpoPushTokens.map((expoPushToken) => input.sessions.deletePushNotificationSubscriptionsByExpoPushToken(expoPushToken)));
1036
1035
  } };
1037
1036
  }
1038
1037
  function notificationEnabled(subscription, intent) {
1039
1038
  return intent === "action_required" ? subscription.actionRequired : subscription.turnTerminal;
1040
1039
  }
1041
- function notificationForEvent(expoPushToken, event) {
1040
+ function notificationForEvent(expoPushToken, event, remainingUsagePercent) {
1042
1041
  return {
1043
- body: event.intent === "action_required" ? "Codex needs your attention." : "A Codex turn has finished.",
1042
+ body: event.intent === "action_required" ? "Codex needs your attention." : remainingUsagePercent === void 0 ? "Finished working." : `Finished working. Remaining usage: ${remainingUsagePercent}%`,
1044
1043
  data: {
1045
1044
  intent: event.intent,
1046
1045
  threadId: event.threadId,
1047
1046
  ...event.turnId ? { turnId: event.turnId } : {}
1048
1047
  },
1049
- title: "Codex Relay",
1048
+ title: event.threadTitle?.trim() || event.threadId,
1050
1049
  to: expoPushToken
1051
1050
  };
1052
1051
  }
@@ -1575,7 +1574,7 @@ function shellEnvValue(value) {
1575
1574
  //#endregion
1576
1575
  //#region src/app.ts
1577
1576
  const defaultWorkspacePath = process.cwd();
1578
- const defaultCodexModel = "gpt-5.5";
1577
+ const defaultCodexModel = "gpt-6-astra";
1579
1578
  const execFileAsync = promisify(execFile);
1580
1579
  const IMAGE_ATTACHMENT_MAX_BYTES = 8 * 1024 * 1024;
1581
1580
  const WORKSPACE_FILE_PREVIEW_MAX_BYTES = 256 * 1024;
@@ -1688,6 +1687,7 @@ function createApp(options = {}) {
1688
1687
  return result;
1689
1688
  }
1690
1689
  const pushNotificationDispatcher = options.pairing ? createPushNotificationDispatcher({
1690
+ readRemainingUsagePercent: appServer ? async () => lowestRateLimitRemainingPercent(normalizeRateLimitBuckets(await appServer.readRateLimits())) : void 0,
1691
1691
  sender: options.pushNotificationSender ?? createExpoPushNotificationSender(),
1692
1692
  sessions: options.pairing.sessions
1693
1693
  }) : void 0;
@@ -1924,6 +1924,7 @@ function createApp(options = {}) {
1924
1924
  const response = PushNotificationSettingsResponseSchema.parse({
1925
1925
  preferences: subscription ? {
1926
1926
  actionRequired: subscription.actionRequired,
1927
+ includeRemainingUsage: subscription.includeRemainingUsage,
1927
1928
  turnTerminal: subscription.turnTerminal
1928
1929
  } : defaultPushNotificationPreferences(),
1929
1930
  registered: Boolean(subscription)
@@ -1940,6 +1941,7 @@ function createApp(options = {}) {
1940
1941
  actionRequired: parsed.data.preferences.actionRequired,
1941
1942
  clientSessionId,
1942
1943
  expoPushToken: parsed.data.expoPushToken,
1944
+ includeRemainingUsage: parsed.data.preferences.includeRemainingUsage,
1943
1945
  platform: parsed.data.platform,
1944
1946
  turnTerminal: parsed.data.preferences.turnTerminal
1945
1947
  });
@@ -2173,7 +2175,11 @@ function createApp(options = {}) {
2173
2175
  app.get(apiPaths.models, async (c) => {
2174
2176
  try {
2175
2177
  const models = appServer ? await appServer.listModels() : fallbackModels();
2176
- return secureJson(c, options.pairing, secureSessionsByTokenHash, ListModelsResponseSchema.parse({ models: models.map(mapAppServerModel) }));
2178
+ const hasAstra = models.some((model) => model.model === defaultCodexModel);
2179
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, ListModelsResponseSchema.parse({ models: models.map((model) => mapAppServerModel(hasAstra ? {
2180
+ ...model,
2181
+ isDefault: model.model === defaultCodexModel
2182
+ } : model)) }));
2177
2183
  } catch {
2178
2184
  return secureJson(c, options.pairing, secureSessionsByTokenHash, ListModelsResponseSchema.parse({ models: fallbackModels().map(mapAppServerModel) }));
2179
2185
  }
@@ -2400,23 +2406,26 @@ function createApp(options = {}) {
2400
2406
  const turns = threadBeforeRewind.turns ?? [];
2401
2407
  const turnIndex = turns.findIndex((turn) => turn.id === parsed.data.turnId);
2402
2408
  if (turnIndex < 0) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", "The selected message is no longer available to rewind."), 404);
2403
- let rolledBackThread;
2404
- if (threadBeforeRewind.historyMode === "paginated" && typeof appServer.revertThread === "function") try {
2405
- rolledBackThread = await appServer.revertThread({
2406
- beforeTurnId: parsed.data.turnId,
2407
- threadId
2408
- });
2409
- } catch (error) {
2410
- if (!/method not found|unsupported|-32601/i.test(errorMessage(error))) throw error;
2411
- rolledBackThread = await appServer.rollbackThread({
2412
- threadId,
2413
- numTurns: turns.length - turnIndex
2414
- });
2415
- }
2416
- else rolledBackThread = await appServer.rollbackThread({
2409
+ const rollbackThread = () => appServer.rollbackThread({
2417
2410
  threadId,
2418
2411
  numTurns: turns.length - turnIndex
2419
2412
  });
2413
+ let rolledBackThread;
2414
+ try {
2415
+ if (threadBeforeRewind.historyMode === "paginated" && typeof appServer.revertThread === "function") try {
2416
+ rolledBackThread = await appServer.revertThread({
2417
+ beforeTurnId: parsed.data.turnId,
2418
+ threadId
2419
+ });
2420
+ } catch (error) {
2421
+ if (!isMissingAppServerMethodError(error, "thread/revert")) throw error;
2422
+ rolledBackThread = await rollbackThread();
2423
+ }
2424
+ else rolledBackThread = await rollbackThread();
2425
+ } catch (error) {
2426
+ if (!isMissingAppServerMethodError(error, "thread/rollback")) throw error;
2427
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("rewind_unsupported", "This chat uses Codex's legacy history format, which this Codex version can no longer rewind. Chats started after the update can be rewound."), 409);
2428
+ }
2420
2429
  const threadWithTurns = rolledBackThread.turns === void 0 ? await appServer.readThread(threadId, { includeTurns: true }) : rolledBackThread;
2421
2430
  const thread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: true });
2422
2431
  const messages = mapAppServerMessages(threadWithTurns);
@@ -3080,6 +3089,7 @@ async function pairedClientSessionIdForAuthorization(authorization, pairing) {
3080
3089
  function defaultPushNotificationPreferences() {
3081
3090
  return {
3082
3091
  actionRequired: false,
3092
+ includeRemainingUsage: false,
3083
3093
  turnTerminal: false
3084
3094
  };
3085
3095
  }
@@ -4685,6 +4695,7 @@ function appServerUserMessageDetails(item) {
4685
4695
  const attachments = item.content.flatMap((content) => {
4686
4696
  switch (content.type) {
4687
4697
  case "image":
4698
+ if (!content.url) return [];
4688
4699
  if (content.url.startsWith("data:image/")) {
4689
4700
  const materialized = materializeDataUriImage(content.url);
4690
4701
  return materialized ? [materialized] : [];
@@ -5328,7 +5339,7 @@ function mapAppServerThread(thread, fallbackMessageCount) {
5328
5339
  return ThreadSummarySchema.parse({
5329
5340
  id: thread.id,
5330
5341
  parentThreadId: thread.parentThreadId ?? void 0,
5331
- title: thread.name ?? preview(thread.preview || "Untitled thread"),
5342
+ title: appServerThreadTitle(thread),
5332
5343
  createdAt,
5333
5344
  updatedAt,
5334
5345
  state: mappedState,
@@ -5339,6 +5350,9 @@ function mapAppServerThread(thread, fallbackMessageCount) {
5339
5350
  lastActivityAt
5340
5351
  });
5341
5352
  }
5353
+ function appServerThreadTitle(thread) {
5354
+ return thread.name ?? preview(thread.preview || "Untitled thread");
5355
+ }
5342
5356
  function rememberAppServerThread(threads, thread, options = {}) {
5343
5357
  const existingThread = threads.get(thread.id);
5344
5358
  const mappedThread = mapAppServerThread(thread, options.authoritativeMessageCount ? void 0 : existingThread?.messageCount);
@@ -6202,26 +6216,54 @@ function objectRecord(value) {
6202
6216
  return value && typeof value === "object" ? value : void 0;
6203
6217
  }
6204
6218
  function fallbackModels() {
6205
- return [{
6206
- id: defaultCodexModel,
6207
- model: defaultCodexModel,
6208
- displayName: "GPT-5.5",
6209
- description: "Default Codex model",
6210
- isDefault: true,
6219
+ const efforts = [
6220
+ "low",
6221
+ "medium",
6222
+ "high",
6223
+ "xhigh",
6224
+ "max"
6225
+ ];
6226
+ return [
6227
+ fallbackModel({
6228
+ description: "Frontier intelligence for the most demanding work.",
6229
+ displayName: "GPT-6-Astra",
6230
+ efforts: [...efforts, "ultra"],
6231
+ fastDescription: "2x speed, increased usage",
6232
+ isDefault: true,
6233
+ model: defaultCodexModel
6234
+ }),
6235
+ fallbackModel({
6236
+ description: "Workhorse model for coding and everyday work.",
6237
+ displayName: "GPT-6-Sol",
6238
+ efforts: [...efforts, "ultra"],
6239
+ fastDescription: "1.5x speed",
6240
+ model: "gpt-6-sol"
6241
+ }),
6242
+ fallbackModel({
6243
+ description: "Fast and affordable model for easier tasks.",
6244
+ displayName: "GPT-6-Luna",
6245
+ efforts,
6246
+ fastDescription: "1.5x speed",
6247
+ model: "gpt-6-luna"
6248
+ })
6249
+ ];
6250
+ }
6251
+ function fallbackModel({ description, displayName, efforts, fastDescription, isDefault = false, model }) {
6252
+ return {
6253
+ id: model,
6254
+ model,
6255
+ displayName,
6256
+ description,
6257
+ isDefault,
6211
6258
  defaultReasoningEffort: "medium",
6212
- supportedReasoningEfforts: [
6213
- { reasoningEffort: "low" },
6214
- { reasoningEffort: "medium" },
6215
- { reasoningEffort: "high" },
6216
- { reasoningEffort: "xhigh" }
6217
- ],
6259
+ supportedReasoningEfforts: efforts.map((reasoningEffort) => ({ reasoningEffort })),
6218
6260
  additionalSpeedTiers: ["fast"],
6219
6261
  serviceTiers: [{
6220
6262
  id: "priority",
6221
6263
  name: "Fast",
6222
- description: "1.5x speed, increased usage"
6264
+ description: fastDescription
6223
6265
  }]
6224
- }];
6266
+ };
6225
6267
  }
6226
6268
  function mapAppServerThreadState(status, turns) {
6227
6269
  if (turns?.some(isAppServerTurnRunning)) return "running";
@@ -6417,7 +6459,10 @@ function observeAppServerPushNotifications(appServer, dispatcher) {
6417
6459
  if (dispatchedEventIds.has(eventId)) return;
6418
6460
  rememberEventId(eventId);
6419
6461
  appServer.readThread(event.threadId, { includeTurns: false }).then((thread) => {
6420
- if (!isSubagentThread(thread)) return dispatcher.dispatch(event);
6462
+ if (!isSubagentThread(thread)) return dispatcher.dispatch({
6463
+ ...event,
6464
+ threadTitle: appServerThreadTitle(thread)
6465
+ });
6421
6466
  }).catch((error) => {
6422
6467
  relayDebugLog("push_notification.dispatch_failed", {
6423
6468
  error: errorMessage(error),
@@ -7430,9 +7475,96 @@ function apiError(code, message, issues) {
7430
7475
  function errorMessage(error) {
7431
7476
  return error instanceof Error ? error.message : "Codex run failed.";
7432
7477
  }
7478
+ function isMissingAppServerMethodError(error, method) {
7479
+ const message = errorMessage(error);
7480
+ return /method not found|-32601/i.test(message) || message.includes(`unknown variant \`${method}\``);
7481
+ }
7482
+ //#endregion
7483
+ //#region src/terminal-pairing.ts
7484
+ function createTerminalPairingApprover(options) {
7485
+ const queue = [];
7486
+ let active;
7487
+ let reader;
7488
+ let expiry;
7489
+ let answering = false;
7490
+ let closed = false;
7491
+ function print(message) {
7492
+ options.output.write(`${message}\n`);
7493
+ }
7494
+ function close() {
7495
+ closed = true;
7496
+ queue.length = 0;
7497
+ active = void 0;
7498
+ clearTimeout(expiry);
7499
+ reader?.close();
7500
+ }
7501
+ function next() {
7502
+ if (closed || active || answering) return;
7503
+ clearTimeout(expiry);
7504
+ active = queue.shift();
7505
+ while (active && active.expiresAt <= Date.now()) active = queue.shift();
7506
+ if (!active) return;
7507
+ print(`\nPairing request from ${(active.clientName ?? "mobile device").replace(/[\p{Cc}\p{Cf}]/gu, "").slice(0, 80)}. Code: ${active.approvalCode}`);
7508
+ print("Check that this code matches your phone. Approve? [y/N] (then Enter)");
7509
+ expiry = setTimeout(() => {
7510
+ print("Pairing request expired. Open the pairing link again to retry.");
7511
+ active = void 0;
7512
+ next();
7513
+ }, Math.max(1, active.expiresAt - Date.now()));
7514
+ }
7515
+ async function answer(line) {
7516
+ if (!active || answering || closed) return;
7517
+ const pairing = active;
7518
+ active = void 0;
7519
+ answering = true;
7520
+ clearTimeout(expiry);
7521
+ try {
7522
+ if (!/^(y|yes)$/i.test(line.trim())) {
7523
+ print("Not approved. You can still use the approve command for this request.");
7524
+ return;
7525
+ }
7526
+ const approved = await options.sessions.approvePendingPairing(pairing.approvalCode, Date.now());
7527
+ if (approved) options.onApproved(approved);
7528
+ else print("This pairing request expired or was already completed. Open the pairing link again.");
7529
+ } catch {
7530
+ print("Could not approve pairing. Try the approve command or open the pairing link again.");
7531
+ } finally {
7532
+ answering = false;
7533
+ next();
7534
+ }
7535
+ }
7536
+ return {
7537
+ close,
7538
+ async request(approvalCode) {
7539
+ if (closed || !options.input.isTTY || !options.output.isTTY) return;
7540
+ try {
7541
+ const pairing = await options.sessions.getPendingPairing(approvalCode, Date.now());
7542
+ if (!pairing || pairing.approved || closed) return;
7543
+ if (active?.approvalCode === approvalCode || queue.some((item) => item.approvalCode === approvalCode)) return;
7544
+ if (queue.length >= 10) {
7545
+ print("More pairing requests are waiting. Use the approve command for this request.");
7546
+ return;
7547
+ }
7548
+ if (!reader) {
7549
+ reader = createInterface({
7550
+ input: options.input,
7551
+ terminal: false
7552
+ });
7553
+ reader.on("line", (line) => void answer(line));
7554
+ reader.on("close", close);
7555
+ }
7556
+ queue.push(pairing);
7557
+ next();
7558
+ } catch {
7559
+ print("Could not show pairing approval. Use the approve command instead.");
7560
+ }
7561
+ }
7562
+ };
7563
+ }
7433
7564
  //#endregion
7434
7565
  //#region src/index.ts
7435
7566
  const port = Number(process.env.PORT ?? 8787);
7567
+ let activePort = port;
7436
7568
  const hostname$1 = process.env.HOST ?? "0.0.0.0";
7437
7569
  const dangerouslyAutoApprove = process.env.CODEX_RELAY_DANGEROUSLY_AUTO_APPROVE === "1";
7438
7570
  const serverIdentity = await getServerIdentity();
@@ -7457,6 +7589,12 @@ const color = {
7457
7589
  };
7458
7590
  const npxCommand = "npx codex-relay@latest";
7459
7591
  const sessionStore = await createTursoPairingSessionStore(process.env.CODEX_RELAY_AUTH_DB_PATH ?? await prepareCodexRelayDataPath("auth.db", ["auth.db-shm", "auth.db-wal"]));
7592
+ const terminalPairing = createTerminalPairingApprover({
7593
+ input: process.stdin,
7594
+ output: process.stdout,
7595
+ sessions: sessionStore,
7596
+ onApproved: () => logRuntimeEvent("Approved", "Pairing approved. Waiting for your phone to finish connecting.")
7597
+ });
7460
7598
  const preferencesStore = createFileRuntimePreferencesStore(process.env.CODEX_RELAY_PREFERENCES_PATH ?? await prepareCodexRelayDataPath("preferences.json"));
7461
7599
  const appServerMode = resolveCodexAppServerMode();
7462
7600
  const relayAppServer = appServerMode.mode === "socket" ? new CodexAppServerClient({
@@ -7487,8 +7625,9 @@ serve({
7487
7625
  onPairAttempt: ({ remoteAddress }) => {
7488
7626
  logRuntimeEvent("Pairing", `Handshake received${remoteAddress ? ` from ${remoteAddress}` : ""}.`);
7489
7627
  },
7490
- onPairApprovalRequested: ({ clientName }) => {
7491
- logRuntimeEvent("Approval", `Pairing approval requested${clientName ? ` from ${clientName}` : ""}. Use the code shown in the mobile app to approve locally.`);
7628
+ onPairApprovalRequested: ({ approvalCode, clientName }) => {
7629
+ logRuntimeEvent("Approval", `Pairing approval requested${clientName ? ` from ${clientName}` : ""}. Command: ${formatApprovalCommand(approvalCode, activePort)}`);
7630
+ terminalPairing.request(approvalCode);
7492
7631
  },
7493
7632
  onPairApproved: ({ clientName }) => {
7494
7633
  logRuntimeEvent("Approved", `Pairing request approved${clientName ? ` for ${clientName}` : ""}. Waiting for secure session pickup.`);
@@ -7505,6 +7644,7 @@ serve({
7505
7644
  hostname: hostname$1,
7506
7645
  port
7507
7646
  }, (info) => {
7647
+ activePort = info.port;
7508
7648
  const listenUrl = `http://${info.address}:${info.port}`;
7509
7649
  const connectUrlCandidates = getConnectUrlCandidates({
7510
7650
  listenUrl,
@@ -7589,7 +7729,7 @@ function formatStartupInstructions(details) {
7589
7729
  ` ${color.command(`${npxCommand} approve <code>`)} Approve a device`,
7590
7730
  "",
7591
7731
  details.dangerouslyAutoApprove ? `${color.prompt("›")} Pairing requests will be auto-approved.` : `${color.prompt("›")} Waiting for pairing requests`,
7592
- details.dangerouslyAutoApprove ? `${color.prompt("›")} Disable this for normal use.` : `${color.prompt("›")} Approve a device with ${color.command(formatApprovalCommand("<code>", details.port))}`
7732
+ details.dangerouslyAutoApprove ? `${color.prompt("›")} Disable this for normal use.` : process.stdin.isTTY && process.stdout.isTTY ? `${color.prompt("›")} Approve a device here when prompted: type y and press Enter.` : `${color.prompt("›")} Approve a device with ${color.command(formatApprovalCommand("<code>", details.port))}`
7593
7733
  ],
7594
7734
  ""
7595
7735
  ].join("\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-relay",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Local Codex Relay CLI bridge for the Codex Relay mobile app.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -41,8 +41,8 @@
41
41
  "@noble/ciphers": "2.2.0",
42
42
  "@noble/curves": "2.2.0",
43
43
  "@noble/hashes": "2.2.0",
44
- "@openai/codex": "0.153.4",
45
- "@openai/codex-sdk": "0.153.4",
44
+ "@openai/codex": "0.156.1",
45
+ "@openai/codex-sdk": "0.156.1",
46
46
  "base64-js": "1.5.1",
47
47
  "commander": "^14.0.3",
48
48
  "es-git": "^0.6.0",
package/src/api-schema.ts CHANGED
@@ -99,6 +99,7 @@ export const PushNotificationIntentSchema = z.enum(["turn_terminal", "action_req
99
99
 
100
100
  export const PushNotificationPreferencesSchema = z.object({
101
101
  actionRequired: z.boolean(),
102
+ includeRemainingUsage: z.boolean().default(false),
102
103
  turnTerminal: z.boolean(),
103
104
  });
104
105
 
@@ -179,6 +180,19 @@ export const RateLimitsResponseSchema = z.object({
179
180
  buckets: z.array(RateLimitBucketSchema),
180
181
  });
181
182
 
183
+ export function rateLimitRemainingPercent(usedPercent: number) {
184
+ return 100 - Math.max(0, Math.min(100, usedPercent));
185
+ }
186
+
187
+ export function lowestRateLimitRemainingPercent(buckets: readonly RateLimitBucket[]) {
188
+ const remaining = buckets.flatMap((bucket) =>
189
+ [bucket.primary, bucket.secondary].flatMap((window) =>
190
+ window ? [rateLimitRemainingPercent(window.usedPercent)] : [],
191
+ ),
192
+ );
193
+ return remaining.length > 0 ? Math.min(...remaining) : undefined;
194
+ }
195
+
182
196
  export const ThreadContextWindowResponseSchema = z.object({
183
197
  threadId: z.string().min(1),
184
198
  usage: ContextWindowUsageSchema.nullable(),
@@ -1636,6 +1650,7 @@ export function createOpenApiDocument() {
1636
1650
  required: ["actionRequired", "turnTerminal"],
1637
1651
  properties: {
1638
1652
  actionRequired: { type: "boolean" },
1653
+ includeRemainingUsage: { type: "boolean", default: false },
1639
1654
  turnTerminal: { type: "boolean" },
1640
1655
  },
1641
1656
  },