codex-relay 1.4.4 → 1.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- import { $ as RunThreadResponseSchema, $t as promptSkillMentionTextCandidates, A as PairRequestSchema, At as WorkspaceFileMentionSchema, B as PushNotificationIntentSchema, Bt as WorkspaceTerminalOutputResponseSchema, C as ListModelsResponseSchema, Ct as VersionResponseSchema, D as ListWorkspaceDirectoriesResponseSchema, Dt as WorkspaceChangesResponseSchema, E as ListThreadsResponseSchema, Et as WebPreviewTargetSchema, F as PromptAttachmentSchema, Ft as WorkspaceSelectionRequestSchema, G as RateLimitBucketSchema, Gt as chatMessageDetailsFromPromptContext, H as PushNotificationSettingsResponseSchema, Ht as WorkspaceTerminalSessionResponseSchema, I as PromptAttachmentSummarySchema, It as WorkspaceTailscaleServeRequestSchema, J as ReasoningEffortSchema, Jt as normalizePromptContext, K as RateLimitWindowSchema, Kt as createOpenApiDocument, L as PromptContextInputSchema, Lt as WorkspaceTailscaleServeResponseSchema, M as PendingInputRequestOptionSchema, Mt as WorkspaceMarkdownPreviewTargetSchema, N as PendingInputRequestQuestionSchema, Nt as WorkspacePreviewNavigationRequestSchema, O as ListWorkspaceFilesResponseSchema, Ot as WorkspaceDirectoryEntrySchema, P as PendingInputRequestSchema, Pt as WorkspacePreviewTabSchema, Q as RunThreadRequestSchema, Qt as promptSkillMentionMarkdown, R as PromptContextSchema, Rt as WorkspaceTerminalInputRequestSchema, S as KnownReasoningEffortSchema, St as UpdateWorkspaceFileContentRequestSchema, T as ListSkillsResponseSchema, Tt as WORKSPACE_PREVIEW_TAB_VALUES, U as QueuedThreadInputActionResponseSchema, Ut as WorkspaceTerminalStartRequestSchema, V as PushNotificationPreferencesSchema, Vt as WorkspaceTerminalResizeRequestSchema, W as QueuedThreadInputSchema, Wt as apiPaths, X as ResolveApprovalRequestSchema, Xt as promptSkillDisplayName, Y as RegisterPushNotificationRequestSchema, Yt as promptMarkdownWithSkills, Z as ResolveApprovalResponseSchema, Zt as promptSkillMentionLabel, _ as EncryptedPayloadSchema, _t as ThreadRunOptionsSchema, a as ArchiveThreadResponseSchema, at as StatusResponseSchema, b as InterruptThreadRunResponseSchema, bt as UpdateRuntimePreferencesRequestSchema, c as ChatMessageRoleSchema, ct as SubmitThreadInputResponseSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as ThreadDetailResponseSchema, en as stripPromptSkillMentions, et as RuntimeModeSchema, f as CodexModelSchema, ft as ThreadGoalResponseSchema, g as CreateThreadResponseSchema, gt as ThreadMessageDetailResponseSchema, h as CreateThreadRequestSchema, ht as ThreadMessageDetailFieldSchema, i as ApprovalModeSchema, it as SandboxModeSchema, j as PairResponseSchema, jt as WorkspaceGitActionResponseSchema, k as PairEncryptedPayloadSchema, kt as WorkspaceFileContentResponseSchema, l as ChatMessageSchema, lt as ThreadCollaborationModeSchema, m as ContextWindowUsageSchema, mt as ThreadGoalStatusSchema, n as AgentSkillSourceSchema, nt as RuntimePreferencesResponseSchema, o as ChatMessageKindSchema, ot as StreamThreadRunEventSchema, p as CommitPushWorkspaceRequestSchema, pt as ThreadGoalSchema, q as RateLimitsResponseSchema, qt as isPromptSkillMarkdownMention, r as ApprovalDecisionSchema, rt as RuntimePreferencesSchema, s as ChatMessagePromptDetailsSchema, st as StreamThreadRunRequestSchema, t as AgentSkillSchema, tt as RuntimePreferencesByWorkspacePathSchema, u as ChatMessageStateSchema, ut as ThreadContextWindowResponseSchema, v as ErrorResponseSchema, vt as ThreadStateSchema, w as ListQueuedThreadInputsResponseSchema, wt as WORKSPACE_PREVIEW_OPEN_PROTOCOL, x as IsoDateTimeSchema, xt as UpdateThreadGoalRequestSchema, y as ImageAttachmentUploadResponseSchema, yt as ThreadSummarySchema, z as PromptSkillSchema, zt as WorkspaceTerminalOutputChunkSchema } 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, ResolveApprovalRequestSchema, ResolveApprovalResponseSchema, 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 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 };
@@ -553,6 +553,9 @@ const ArchiveThreadResponseSchema = z.object({
553
553
  threads: z.array(ThreadSummarySchema),
554
554
  source: z.enum(["app-server", "memory"]).default("memory")
555
555
  });
556
+ const RenameThreadRequestSchema = z.object({ title: z.string().trim().min(1).max(120) });
557
+ const RenameThreadResponseSchema = z.object({ thread: ThreadSummarySchema });
558
+ const RewindThreadRequestSchema = z.object({ turnId: z.string().trim().min(1) });
556
559
  const ListModelsResponseSchema = z.object({ models: z.array(CodexModelSchema) });
557
560
  const ListSkillsResponseSchema = z.object({ skills: z.array(AgentSkillSchema) });
558
561
  const ThreadDetailResponseSchema = z.object({
@@ -758,6 +761,8 @@ const apiPaths = {
758
761
  threads: "/v1/threads",
759
762
  thread: (threadId) => `/v1/threads/${encodeURIComponent(threadId)}`,
760
763
  threadArchive: (threadId) => `/v1/threads/${encodeURIComponent(threadId)}`,
764
+ threadName: (threadId) => `/v1/threads/${encodeURIComponent(threadId)}/name`,
765
+ threadRollback: (threadId) => `/v1/threads/${encodeURIComponent(threadId)}/rollback`,
761
766
  threadContextWindow: (threadId) => `/v1/threads/${encodeURIComponent(threadId)}/context-window`,
762
767
  threadGoal: (threadId) => `/v1/threads/${encodeURIComponent(threadId)}/goal`,
763
768
  threadMessageDetail: (threadId, messageId, field) => `/v1/threads/${encodeURIComponent(threadId)}/messages/${encodeURIComponent(messageId)}/details/${encodeURIComponent(field)}`,
@@ -840,6 +845,10 @@ function createOpenApiDocument() {
840
845
  in: "path",
841
846
  required: true,
842
847
  schema: { type: "string" }
848
+ }, {
849
+ name: "refresh",
850
+ in: "query",
851
+ schema: { type: "boolean" }
843
852
  }],
844
853
  responses: {
845
854
  "200": jsonResponse("ThreadDetailResponse"),
@@ -861,6 +870,40 @@ function createOpenApiDocument() {
861
870
  }
862
871
  }
863
872
  },
873
+ "/v1/threads/{threadId}/name": { post: {
874
+ summary: "Rename a Codex app-server thread",
875
+ parameters: [{
876
+ name: "threadId",
877
+ in: "path",
878
+ required: true,
879
+ schema: { type: "string" }
880
+ }],
881
+ requestBody: jsonRequest("RenameThreadRequest"),
882
+ responses: {
883
+ "200": jsonResponse("RenameThreadResponse"),
884
+ "400": jsonResponse("ErrorResponse"),
885
+ "404": jsonResponse("ErrorResponse"),
886
+ "409": jsonResponse("ErrorResponse"),
887
+ "502": jsonResponse("ErrorResponse")
888
+ }
889
+ } },
890
+ "/v1/threads/{threadId}/rollback": { post: {
891
+ summary: "Rewind a Codex app-server thread to before a selected turn",
892
+ parameters: [{
893
+ name: "threadId",
894
+ in: "path",
895
+ required: true,
896
+ schema: { type: "string" }
897
+ }],
898
+ requestBody: jsonRequest("RewindThreadRequest"),
899
+ responses: {
900
+ "200": jsonResponse("ThreadDetailResponse"),
901
+ "400": jsonResponse("ErrorResponse"),
902
+ "404": jsonResponse("ErrorResponse"),
903
+ "409": jsonResponse("ErrorResponse"),
904
+ "502": jsonResponse("ErrorResponse")
905
+ }
906
+ } },
864
907
  "/v1/workspace-directories": { get: {
865
908
  summary: "List directories under the configured workspace",
866
909
  responses: {
@@ -1160,6 +1203,28 @@ function createOpenApiDocument() {
1160
1203
  }, { type: "null" }] }
1161
1204
  }
1162
1205
  },
1206
+ RenameThreadRequest: {
1207
+ type: "object",
1208
+ required: ["title"],
1209
+ properties: { title: {
1210
+ type: "string",
1211
+ minLength: 1,
1212
+ maxLength: 120
1213
+ } }
1214
+ },
1215
+ RenameThreadResponse: {
1216
+ type: "object",
1217
+ required: ["thread"],
1218
+ properties: { thread: { $ref: "#/components/schemas/ThreadSummary" } }
1219
+ },
1220
+ RewindThreadRequest: {
1221
+ type: "object",
1222
+ required: ["turnId"],
1223
+ properties: { turnId: {
1224
+ type: "string",
1225
+ minLength: 1
1226
+ } }
1227
+ },
1163
1228
  ThreadGoalResponse: {
1164
1229
  type: "object",
1165
1230
  required: ["goal", "thread"],
@@ -1801,4 +1866,4 @@ function jsonResponse(schemaName) {
1801
1866
  };
1802
1867
  }
1803
1868
  //#endregion
1804
- export { RunThreadResponseSchema as $, promptSkillMentionTextCandidates as $t, PairRequestSchema as A, WorkspaceFileMentionSchema as At, PushNotificationIntentSchema as B, WorkspaceTerminalOutputResponseSchema as Bt, ListModelsResponseSchema as C, VersionResponseSchema as Ct, ListWorkspaceDirectoriesResponseSchema as D, WorkspaceChangesResponseSchema as Dt, ListThreadsResponseSchema as E, WebPreviewTargetSchema as Et, PromptAttachmentSchema as F, WorkspaceSelectionRequestSchema as Ft, RateLimitBucketSchema as G, chatMessageDetailsFromPromptContext as Gt, PushNotificationSettingsResponseSchema as H, WorkspaceTerminalSessionResponseSchema as Ht, PromptAttachmentSummarySchema as I, WorkspaceTailscaleServeRequestSchema as It, ReasoningEffortSchema as J, normalizePromptContext as Jt, RateLimitWindowSchema as K, createOpenApiDocument as Kt, PromptContextInputSchema as L, WorkspaceTailscaleServeResponseSchema as Lt, PendingInputRequestOptionSchema as M, WorkspaceMarkdownPreviewTargetSchema as Mt, PendingInputRequestQuestionSchema as N, WorkspacePreviewNavigationRequestSchema as Nt, ListWorkspaceFilesResponseSchema as O, WorkspaceDirectoryEntrySchema as Ot, PendingInputRequestSchema as P, WorkspacePreviewTabSchema as Pt, RunThreadRequestSchema as Q, promptSkillMentionMarkdown as Qt, PromptContextSchema as R, WorkspaceTerminalInputRequestSchema as Rt, KnownReasoningEffortSchema as S, UpdateWorkspaceFileContentRequestSchema as St, ListSkillsResponseSchema as T, WORKSPACE_PREVIEW_TAB_VALUES as Tt, QueuedThreadInputActionResponseSchema as U, WorkspaceTerminalStartRequestSchema as Ut, PushNotificationPreferencesSchema as V, WorkspaceTerminalResizeRequestSchema as Vt, QueuedThreadInputSchema as W, apiPaths as Wt, ResolveApprovalRequestSchema as X, promptSkillDisplayName as Xt, RegisterPushNotificationRequestSchema as Y, promptMarkdownWithSkills as Yt, ResolveApprovalResponseSchema as Z, promptSkillMentionLabel as Zt, EncryptedPayloadSchema as _, ThreadRunOptionsSchema as _t, ArchiveThreadResponseSchema as a, StatusResponseSchema as at, InterruptThreadRunResponseSchema as b, UpdateRuntimePreferencesRequestSchema as bt, ChatMessageRoleSchema as c, SubmitThreadInputResponseSchema as ct, CheckoutWorkspaceBranchRequestSchema as d, ThreadDetailResponseSchema as dt, stripPromptSkillMentions as en, RuntimeModeSchema as et, CodexModelSchema as f, ThreadGoalResponseSchema as ft, CreateThreadResponseSchema as g, ThreadMessageDetailResponseSchema as gt, CreateThreadRequestSchema as h, ThreadMessageDetailFieldSchema as ht, ApprovalModeSchema as i, SandboxModeSchema as it, PairResponseSchema as j, WorkspaceGitActionResponseSchema as jt, PairEncryptedPayloadSchema as k, WorkspaceFileContentResponseSchema as kt, ChatMessageSchema as l, ThreadCollaborationModeSchema as lt, ContextWindowUsageSchema as m, ThreadGoalStatusSchema as mt, AgentSkillSourceSchema as n, RuntimePreferencesResponseSchema as nt, ChatMessageKindSchema as o, StreamThreadRunEventSchema as ot, CommitPushWorkspaceRequestSchema as p, ThreadGoalSchema as pt, RateLimitsResponseSchema as q, isPromptSkillMarkdownMention as qt, ApprovalDecisionSchema as r, RuntimePreferencesSchema as rt, ChatMessagePromptDetailsSchema as s, StreamThreadRunRequestSchema as st, AgentSkillSchema as t, RuntimePreferencesByWorkspacePathSchema as tt, ChatMessageStateSchema as u, ThreadContextWindowResponseSchema as ut, ErrorResponseSchema as v, ThreadStateSchema as vt, ListQueuedThreadInputsResponseSchema as w, WORKSPACE_PREVIEW_OPEN_PROTOCOL as wt, IsoDateTimeSchema as x, UpdateThreadGoalRequestSchema as xt, ImageAttachmentUploadResponseSchema as y, ThreadSummarySchema as yt, PromptSkillSchema as z, WorkspaceTerminalOutputChunkSchema as zt };
1869
+ 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 };
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { Wt as apiPaths } from "./api-schema2.js";
2
+ import { qt as apiPaths } from "./api-schema2.js";
3
3
  import { n as codexRelayHome, o as getConnectUrlGuidance, r as legacyCodexRelayDataPath, s as createTursoPairingSessionStore, t as codexRelayDataPath } from "./paths.js";
4
4
  import { Command } from "@commander-js/extra-typings";
5
5
  import qrcode from "qrcode-terminal";
package/dist/paths.js CHANGED
@@ -92,6 +92,7 @@ function createQueryableDatabase(executor) {
92
92
  }
93
93
  //#endregion
94
94
  //#region src/pairing-store.ts
95
+ const permanentClientSessionExpiresAt = Date.parse("9999-12-31T23:59:59.999Z");
95
96
  async function createTursoPairingSessionStore(path) {
96
97
  if (path !== ":memory:") await mkdir(dirname(path), { recursive: true });
97
98
  const db = await connect(path);
@@ -134,8 +135,8 @@ async function createTursoPairingSessionStore(path) {
134
135
  );
135
136
  `);
136
137
  await ensurePairingSessionColumns();
137
- async function countActive(now) {
138
- const row = await db.prepare("SELECT COUNT(DISTINCT COALESCE(client_session_id, token_hash)) AS count FROM pairing_sessions WHERE expires_at > ?").get(now);
138
+ async function countActive() {
139
+ const row = await db.prepare("SELECT COUNT(DISTINCT COALESCE(client_session_id, token_hash)) AS count FROM pairing_sessions").get();
139
140
  return Number(row?.count ?? 0);
140
141
  }
141
142
  async function deleteSession(tokenHash) {
@@ -238,8 +239,8 @@ async function createTursoPairingSessionStore(path) {
238
239
  created_at,
239
240
  updated_at
240
241
  )
241
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(tokenHash, session.clientSessionId ?? null, session.clientName ?? null, session.expiresAt, secure?.keyEpoch ?? null, secure?.mobileToServerKey ?? null, secure?.serverToMobileKey ?? null, secure?.lastMobileCounter ?? null, secure?.nextServerCounter ?? null, now, now);
242
- return countActive(now);
242
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(tokenHash, session.clientSessionId ?? null, session.clientName ?? null, session.expiresAt ?? permanentClientSessionExpiresAt, secure?.keyEpoch ?? null, secure?.mobileToServerKey ?? null, secure?.serverToMobileKey ?? null, secure?.lastMobileCounter ?? null, secure?.nextServerCounter ?? null, now, now);
243
+ return countActive();
243
244
  },
244
245
  deleteSession,
245
246
  deletePendingPairing,
@@ -254,7 +255,7 @@ async function createTursoPairingSessionStore(path) {
254
255
  WHERE client_session_id = ?`).get(clientSessionId);
255
256
  return row ? pushNotificationSubscriptionFromRow(row) : void 0;
256
257
  },
257
- async getValidSession(tokenHash, now) {
258
+ async getValidSession(tokenHash) {
258
259
  const row = await db.prepare(`SELECT client_name AS clientName,
259
260
  client_session_id AS clientSessionId,
260
261
  expires_at AS expiresAt,
@@ -266,19 +267,14 @@ async function createTursoPairingSessionStore(path) {
266
267
  FROM pairing_sessions
267
268
  WHERE token_hash = ?`).get(tokenHash);
268
269
  if (!row) return;
269
- const expiresAt = Number(row.expiresAt);
270
- if (now > expiresAt) {
271
- await deleteSession(tokenHash);
272
- return;
273
- }
274
270
  return {
275
271
  clientSessionId: typeof row.clientSessionId === "string" ? row.clientSessionId : void 0,
276
272
  clientName: typeof row.clientName === "string" ? row.clientName : void 0,
277
- expiresAt,
273
+ expiresAt: Number(row.expiresAt),
278
274
  secureSession: decodeSecureSession(row)
279
275
  };
280
276
  },
281
- async listActivePushNotificationSubscriptions(now) {
277
+ async listActivePushNotificationSubscriptions() {
282
278
  return resultRows(await db.prepare(`SELECT subscriptions.client_session_id AS clientSessionId,
283
279
  subscriptions.expo_push_token AS expoPushToken,
284
280
  subscriptions.platform,
@@ -287,14 +283,12 @@ async function createTursoPairingSessionStore(path) {
287
283
  FROM push_notification_subscriptions AS subscriptions
288
284
  INNER JOIN pairing_sessions AS sessions
289
285
  ON sessions.client_session_id = subscriptions.client_session_id
290
- WHERE sessions.expires_at > ?
291
- GROUP BY subscriptions.client_session_id`).all(now)).flatMap((row) => {
286
+ GROUP BY subscriptions.client_session_id`).all()).flatMap((row) => {
292
287
  const subscription = pushNotificationSubscriptionFromRow(row);
293
288
  return subscription ? [subscription] : [];
294
289
  });
295
290
  },
296
- async pruneExpired(now) {
297
- await db.prepare("DELETE FROM pairing_sessions WHERE expires_at <= ?").run(now);
291
+ async pruneExpiredPendingPairings(now) {
298
292
  await db.prepare("DELETE FROM pending_pairings WHERE expires_at <= ?").run(now);
299
293
  await db.prepare(`DELETE FROM push_notification_subscriptions
300
294
  WHERE NOT EXISTS (
@@ -325,9 +319,9 @@ async function createTursoPairingSessionStore(path) {
325
319
  created_at,
326
320
  updated_at
327
321
  )
328
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(newTokenHash, session.clientSessionId ?? null, session.clientName ?? null, session.expiresAt, secure?.keyEpoch ?? null, secure?.mobileToServerKey ?? null, secure?.serverToMobileKey ?? null, secure?.lastMobileCounter ?? null, secure?.nextServerCounter ?? null, now, now);
322
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(newTokenHash, session.clientSessionId ?? null, session.clientName ?? null, session.expiresAt ?? permanentClientSessionExpiresAt, secure?.keyEpoch ?? null, secure?.mobileToServerKey ?? null, secure?.serverToMobileKey ?? null, secure?.lastMobileCounter ?? null, secure?.nextServerCounter ?? null, now, now);
329
323
  })();
330
- return countActive(now);
324
+ return countActive();
331
325
  },
332
326
  async updateSecureSession(tokenHash, secureSession) {
333
327
  const secure = encodeSecureSession(secureSession);
@@ -601,4 +595,4 @@ function defaultCodexRelayHome() {
601
595
  }
602
596
  }
603
597
  //#endregion
604
- export { getConnectUrlCandidates as a, createPairingQrPayload as i, codexRelayHome as n, getConnectUrlGuidance as o, legacyCodexRelayDataPath as r, createTursoPairingSessionStore as s, codexRelayDataPath as t };
598
+ export { getConnectUrlCandidates as a, permanentClientSessionExpiresAt as c, createPairingQrPayload as i, codexRelayHome as n, getConnectUrlGuidance as o, legacyCodexRelayDataPath as r, createTursoPairingSessionStore as s, codexRelayDataPath as t };
package/dist/src.js CHANGED
@@ -1,5 +1,5 @@
1
- import { A as PairRequestSchema, Bt as WorkspaceTerminalOutputResponseSchema, C as ListModelsResponseSchema, Ct as VersionResponseSchema, D as ListWorkspaceDirectoriesResponseSchema, Dt as WorkspaceChangesResponseSchema, E as ListThreadsResponseSchema, Gt as chatMessageDetailsFromPromptContext, H as PushNotificationSettingsResponseSchema, Ht as WorkspaceTerminalSessionResponseSchema, It as WorkspaceTailscaleServeRequestSchema, J as ReasoningEffortSchema, Jt as normalizePromptContext, Kt as createOpenApiDocument, Lt as WorkspaceTailscaleServeResponseSchema, O as ListWorkspaceFilesResponseSchema, Q as RunThreadRequestSchema, Rt as WorkspaceTerminalInputRequestSchema, S as KnownReasoningEffortSchema, St as UpdateWorkspaceFileContentRequestSchema, T as ListSkillsResponseSchema, U as QueuedThreadInputActionResponseSchema, Ut as WorkspaceTerminalStartRequestSchema, Vt as WorkspaceTerminalResizeRequestSchema, Wt as apiPaths, X as ResolveApprovalRequestSchema, Y as RegisterPushNotificationRequestSchema, Yt as promptMarkdownWithSkills, Z as ResolveApprovalResponseSchema, _ as EncryptedPayloadSchema, a as ArchiveThreadResponseSchema, at as StatusResponseSchema, b as InterruptThreadRunResponseSchema, bt as UpdateRuntimePreferencesRequestSchema, ct as SubmitThreadInputResponseSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as ThreadDetailResponseSchema, en as stripPromptSkillMentions, ft as ThreadGoalResponseSchema, gt as ThreadMessageDetailResponseSchema, h as CreateThreadRequestSchema, ht as ThreadMessageDetailFieldSchema, j as PairResponseSchema, jt as WorkspaceGitActionResponseSchema, kt as WorkspaceFileContentResponseSchema, l as ChatMessageSchema, m as ContextWindowUsageSchema, mt as ThreadGoalStatusSchema, nt as RuntimePreferencesResponseSchema, ot as StreamThreadRunEventSchema, p as CommitPushWorkspaceRequestSchema, pt as ThreadGoalSchema, q as RateLimitsResponseSchema, rt as RuntimePreferencesSchema, st as StreamThreadRunRequestSchema, ut as ThreadContextWindowResponseSchema, w as ListQueuedThreadInputsResponseSchema, xt as UpdateThreadGoalRequestSchema, y as ImageAttachmentUploadResponseSchema, yt as ThreadSummarySchema } from "./api-schema2.js";
2
- import { a as getConnectUrlCandidates, i as createPairingQrPayload, o as getConnectUrlGuidance, r as legacyCodexRelayDataPath, s as createTursoPairingSessionStore, t as codexRelayDataPath } from "./paths.js";
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";
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";
5
5
  import { execFile, spawn } from "node:child_process";
@@ -209,6 +209,12 @@ var CodexAppServerClient = class {
209
209
  async archiveThread(params) {
210
210
  await this.request("thread/archive", params);
211
211
  }
212
+ async setThreadName(params) {
213
+ await this.request("thread/name/set", params);
214
+ }
215
+ async rollbackThread(params) {
216
+ return (await this.request("thread/rollback", params)).thread;
217
+ }
212
218
  async getThreadGoal(params) {
213
219
  return (await this.request("thread/goal/get", params)).goal;
214
220
  }
@@ -866,7 +872,7 @@ function createExpoPushNotificationSender(fetchImpl = fetch) {
866
872
  }
867
873
  function createPushNotificationDispatcher(input) {
868
874
  return { async dispatch(event) {
869
- const subscriptions = await input.sessions.listActivePushNotificationSubscriptions(Date.now());
875
+ const subscriptions = await input.sessions.listActivePushNotificationSubscriptions();
870
876
  const selectedTokens = new Set(subscriptions.filter((subscription) => notificationEnabled(subscription, event.intent)).map((subscription) => subscription.expoPushToken));
871
877
  if (selectedTokens.size === 0) return;
872
878
  const delivery = await input.sender.send([...selectedTokens].map((expoPushToken) => notificationForEvent(expoPushToken, event)));
@@ -1463,6 +1469,7 @@ function createApp(options = {}) {
1463
1469
  const resolvedApprovals = /* @__PURE__ */ new Map();
1464
1470
  const queuedInputsByThreadId = /* @__PURE__ */ new Map();
1465
1471
  const threadOperationTails = /* @__PURE__ */ new Map();
1472
+ const appServerMutationTails = /* @__PURE__ */ new Map();
1466
1473
  const workspaceTerminalSessions = /* @__PURE__ */ new Map();
1467
1474
  const activeAppServerTurnIdsByThreadId = /* @__PURE__ */ new Map();
1468
1475
  const appServerHistoryLoadsByThreadId = /* @__PURE__ */ new Map();
@@ -1479,6 +1486,15 @@ function createApp(options = {}) {
1479
1486
  });
1480
1487
  return result;
1481
1488
  }
1489
+ function runAppServerMutation(threadId, operation) {
1490
+ const result = (appServerMutationTails.get(threadId) ?? Promise.resolve()).then(operation, operation);
1491
+ const tail = result.then(() => void 0, () => void 0);
1492
+ appServerMutationTails.set(threadId, tail);
1493
+ tail.finally(() => {
1494
+ if (appServerMutationTails.get(threadId) === tail) appServerMutationTails.delete(threadId);
1495
+ });
1496
+ return result;
1497
+ }
1482
1498
  const pushNotificationDispatcher = options.pairing ? createPushNotificationDispatcher({
1483
1499
  sender: options.pushNotificationSender ?? createExpoPushNotificationSender(),
1484
1500
  sessions: options.pairing.sessions
@@ -1492,7 +1508,7 @@ function createApp(options = {}) {
1492
1508
  threadId
1493
1509
  });
1494
1510
  const load = appServer.readThread(threadId, { includeTurns: true }).then((threadWithTurns) => {
1495
- const mappedThread = rememberAppServerThread(threads, threadWithTurns);
1511
+ const mappedThread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: true });
1496
1512
  const messages = mergeAppServerMessagesWithLocalStatus(mapAppServerMessages(threadWithTurns), messagesByThreadId.get(threadId) ?? cachedMessages);
1497
1513
  messagesByThreadId.set(threadId, messages);
1498
1514
  relayDebugLog("thread.detail.history.background_completed", {
@@ -1520,7 +1536,7 @@ function createApp(options = {}) {
1520
1536
  }
1521
1537
  const token = parseBearerToken(c.req.header("authorization"));
1522
1538
  const tokenHash = token ? options.pairing.hashClientToken(token) : void 0;
1523
- const validSession = tokenHash ? await options.pairing.sessions.getValidSession(tokenHash, Date.now()) : void 0;
1539
+ const validSession = tokenHash ? await options.pairing.sessions.getValidSession(tokenHash) : void 0;
1524
1540
  if (!tokenHash || !validSession) return c.json(apiError("unauthorized", "Pair this device with the Codex Relay server."), 401);
1525
1541
  if (options.pairing.serverIdentity && !secureSessionsByTokenHash.has(tokenHash)) if (validSession.secureSession) secureSessionsByTokenHash.set(tokenHash, validSession.secureSession);
1526
1542
  else return c.json(apiError("secure_session_required", "Secure session expired. Pair this device again."), 401);
@@ -1541,7 +1557,7 @@ function createApp(options = {}) {
1541
1557
  const parsed = await parsePlainJson(c.req.raw, PairRequestSchema);
1542
1558
  if (!parsed.success) return c.json(validationError(parsed.error), 400);
1543
1559
  if (!parsed.data.secure || !options.pairing.serverIdentity) return c.json(apiError("secure_pairing_required", "Pairing requires the secure QR approval flow."), 400);
1544
- await options.pairing.sessions.pruneExpired(Date.now());
1560
+ await options.pairing.sessions.pruneExpiredPendingPairings(Date.now());
1545
1561
  const approvalCode = await createApprovalCode(options.pairing.sessions);
1546
1562
  const expiresAt = Date.now() + (options.pairing.approvalTtlMs ?? 300 * 1e3);
1547
1563
  const requestOrigin = externalRequestOrigin(c.req.url, (name) => c.req.header(name));
@@ -1579,11 +1595,10 @@ function createApp(options = {}) {
1579
1595
  approvalCode,
1580
1596
  approvalExpiresAt: new Date(pending.expiresAt).toISOString()
1581
1597
  }), 202);
1582
- await options.pairing.sessions.pruneExpired(Date.now());
1598
+ await options.pairing.sessions.pruneExpiredPendingPairings(Date.now());
1583
1599
  const clientToken = options.pairing.createClientToken();
1584
- const expiresAt = Date.now() + options.pairing.tokenTtlMs;
1585
1600
  const tokenHash = options.pairing.hashClientToken(clientToken);
1586
- const clientTokenExpiresAt = new Date(expiresAt).toISOString();
1601
+ const clientTokenExpiresAt = new Date(permanentClientSessionExpiresAt).toISOString();
1587
1602
  const pairing = createSecurePairing({
1588
1603
  approvalCode,
1589
1604
  clientEphemeralPublicKey: pending.clientEphemeralPublicKey,
@@ -1597,7 +1612,6 @@ function createApp(options = {}) {
1597
1612
  const tokenCount = await options.pairing.sessions.createSession(tokenHash, {
1598
1613
  clientSessionId: pending.clientSessionId,
1599
1614
  clientName: pending.clientName,
1600
- expiresAt,
1601
1615
  secureSession: pairing.session
1602
1616
  });
1603
1617
  await options.pairing.sessions.deletePendingPairing(approvalCode);
@@ -1640,14 +1654,12 @@ function createApp(options = {}) {
1640
1654
  const oldSession = oldToken ? await getValidClientSession(options.pairing, oldToken) : void 0;
1641
1655
  if (!oldToken || !oldSession) return c.json(apiError("unauthorized", "Pair this device with the Codex Relay server."), 401);
1642
1656
  const clientToken = options.pairing.createClientToken();
1643
- const expiresAt = Date.now() + options.pairing.tokenTtlMs;
1644
1657
  const oldTokenHash = options.pairing.hashClientToken(oldToken);
1645
1658
  const newTokenHash = options.pairing.hashClientToken(clientToken);
1646
1659
  const clientSessionId = normalizeClientSessionId(c.req.header("x-codex-relay-client-session-id")) ?? oldSession.clientSessionId;
1647
1660
  const tokenCount = await options.pairing.sessions.rotateSession(oldTokenHash, newTokenHash, {
1648
1661
  clientSessionId,
1649
1662
  clientName: oldSession.clientName,
1650
- expiresAt,
1651
1663
  secureSession: secureSessionsByTokenHash.get(oldTokenHash) ?? oldSession.secureSession
1652
1664
  });
1653
1665
  const secureSession = secureSessionsByTokenHash.get(oldTokenHash) ?? oldSession.secureSession;
@@ -1658,7 +1670,7 @@ function createApp(options = {}) {
1658
1670
  });
1659
1671
  const response = PairResponseSchema.parse({
1660
1672
  clientToken,
1661
- clientTokenExpiresAt: new Date(expiresAt).toISOString()
1673
+ clientTokenExpiresAt: new Date(permanentClientSessionExpiresAt).toISOString()
1662
1674
  });
1663
1675
  const jsonResponse = await secureJson(c, options.pairing, secureSessionsByTokenHash, response, 201);
1664
1676
  if (secureSession) {
@@ -2151,8 +2163,73 @@ function createApp(options = {}) {
2151
2163
  });
2152
2164
  return secureJson(c, options.pairing, secureSessionsByTokenHash, response);
2153
2165
  });
2166
+ app.post("/v1/threads/:threadId/name", async (c) => {
2167
+ const threadId = c.req.param("threadId");
2168
+ if (!appServer) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("unsupported", "Renaming threads requires the Codex app-server."), 409);
2169
+ if (!await ensureKnownThread({
2170
+ appServer,
2171
+ threadId,
2172
+ messagesByThreadId,
2173
+ threads
2174
+ })) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", `Thread ${threadId} is not known to this server.`), 404);
2175
+ const parsed = await parseRequestJson(c, options.pairing, secureSessionsByTokenHash, RenameThreadRequestSchema);
2176
+ if (!parsed.success) return secureJson(c, options.pairing, secureSessionsByTokenHash, validationError(parsed.error), 400);
2177
+ try {
2178
+ const body = parsed.data;
2179
+ const thread = await runAppServerMutation(threadId, async () => {
2180
+ await appServer.setThreadName({
2181
+ name: body.title,
2182
+ threadId
2183
+ });
2184
+ return rememberAppServerThread(threads, await appServer.readThread(threadId, { includeTurns: false }));
2185
+ });
2186
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, RenameThreadResponseSchema.parse({ thread }));
2187
+ } catch (error) {
2188
+ const message = errorMessage(error);
2189
+ const status = /not found|no rollout found/i.test(message) ? 404 : 502;
2190
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError(status === 404 ? "not_found" : "rename_unavailable", message), status);
2191
+ }
2192
+ });
2193
+ app.post("/v1/threads/:threadId/rollback", async (c) => {
2194
+ const threadId = c.req.param("threadId");
2195
+ if (!appServer) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("unsupported", "Rewinding threads requires the Codex app-server."), 409);
2196
+ const parsed = await parseRequestJson(c, options.pairing, secureSessionsByTokenHash, RewindThreadRequestSchema);
2197
+ if (!parsed.success) return secureJson(c, options.pairing, secureSessionsByTokenHash, validationError(parsed.error), 400);
2198
+ return runThreadOperation(threadId, () => runAppServerMutation(threadId, async () => {
2199
+ try {
2200
+ const threadBeforeRewind = await appServer.readThread(threadId, { includeTurns: true });
2201
+ if (isSubagentThread(threadBeforeRewind)) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", `Thread ${threadId} is not known to this server.`), 404);
2202
+ if (mapAppServerThreadState(threadBeforeRewind.status, threadBeforeRewind.turns) === "running" || activeAppServerTurnIdsByThreadId.has(threadId)) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("thread_running", "Stop the current turn before rewinding this chat."), 409);
2203
+ const turns = threadBeforeRewind.turns ?? [];
2204
+ const turnIndex = turns.findIndex((turn) => turn.id === parsed.data.turnId);
2205
+ if (turnIndex < 0) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", "The selected message is no longer available to rewind."), 404);
2206
+ const rolledBackThread = await appServer.rollbackThread({
2207
+ threadId,
2208
+ numTurns: turns.length - turnIndex
2209
+ });
2210
+ const threadWithTurns = rolledBackThread.turns === void 0 ? await appServer.readThread(threadId, { includeTurns: true }) : rolledBackThread;
2211
+ const thread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: true });
2212
+ const messages = mapAppServerMessages(threadWithTurns);
2213
+ messagesByThreadId.set(threadId, messages);
2214
+ liveThreads.delete(threadId);
2215
+ activeAppServerTurnIdsByThreadId.delete(threadId);
2216
+ queuedInputsByThreadId.delete(threadId);
2217
+ steeringThreads.delete(threadId);
2218
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, threadDetailResponse({
2219
+ thread,
2220
+ messages,
2221
+ pendingInputRequests: []
2222
+ }));
2223
+ } catch (error) {
2224
+ const message = errorMessage(error);
2225
+ const status = /not found|no rollout found/i.test(message) ? 404 : 502;
2226
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError(status === 404 ? "not_found" : "rewind_unavailable", message), status);
2227
+ }
2228
+ }));
2229
+ });
2154
2230
  app.get("/v1/threads/:threadId", async (c) => {
2155
2231
  const threadId = c.req.param("threadId");
2232
+ const forceRefresh = c.req.query("refresh") === "true";
2156
2233
  const detailStartedAt = Date.now();
2157
2234
  relayDebugLog("thread.detail.requested", { threadId });
2158
2235
  const knownThread = threads.get(threadId);
@@ -2166,21 +2243,29 @@ function createApp(options = {}) {
2166
2243
  let loadedMessages = false;
2167
2244
  let messages = cachedMessages;
2168
2245
  let responseThread = preserveKnownRunningThreadState(mappedThread, wasKnownRunning);
2169
- const rolloutHistory = readRolloutThreadMessages(threadId, workspacePath);
2170
- if (rolloutHistory.messages.length > 0) {
2171
- messages = mergeThreadMessagePages(rolloutHistory.messages, cachedMessages);
2172
- responseThread = rememberRolloutThreadMessages(threads, responseThread, messages, rolloutHistory.messageCountLowerBound);
2173
- responseThread = preserveKnownRunningThreadState(responseThread, wasKnownRunning);
2246
+ if (forceRefresh && responseThread.state !== "running") {
2247
+ const threadWithTurns = await appServer.readThread(threadId, { includeTurns: true });
2248
+ responseThread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: true });
2249
+ messages = mapAppServerMessages(threadWithTurns);
2174
2250
  messagesByThreadId.set(threadId, messages);
2175
2251
  loadedMessages = true;
2176
- } else if (cachedMessages.length > 0) {
2177
- messages = dedupeThreadMessages(cachedMessages);
2178
- if (messages.length !== cachedMessages.length) messagesByThreadId.set(threadId, messages);
2179
- loadedMessages = true;
2252
+ } else {
2253
+ const rolloutHistory = readRolloutThreadMessages(threadId, workspacePath);
2254
+ if (rolloutHistory.messages.length > 0) {
2255
+ messages = mergeThreadMessagePages(rolloutHistory.messages, cachedMessages);
2256
+ responseThread = rememberRolloutThreadMessages(threads, responseThread, messages, rolloutHistory.messageCountLowerBound);
2257
+ responseThread = preserveKnownRunningThreadState(responseThread, wasKnownRunning);
2258
+ messagesByThreadId.set(threadId, messages);
2259
+ loadedMessages = true;
2260
+ } else if (cachedMessages.length > 0) {
2261
+ messages = dedupeThreadMessages(cachedMessages);
2262
+ if (messages.length !== cachedMessages.length) messagesByThreadId.set(threadId, messages);
2263
+ loadedMessages = true;
2264
+ }
2180
2265
  }
2181
2266
  if (!loadedMessages && responseThread.state !== "running") {
2182
2267
  const threadWithTurns = await appServer.readThread(threadId, { includeTurns: true });
2183
- responseThread = rememberAppServerThread(threads, threadWithTurns);
2268
+ responseThread = rememberAppServerThread(threads, threadWithTurns, { authoritativeMessageCount: true });
2184
2269
  messages = mergeAppServerMessagesWithLocalStatus(mapAppServerMessages(threadWithTurns), cachedMessages);
2185
2270
  messagesByThreadId.set(threadId, messages);
2186
2271
  loadedMessages = true;
@@ -2507,30 +2592,32 @@ function createApp(options = {}) {
2507
2592
  app.post("/v1/threads/:threadId/input/:inputId/steer", async (c) => {
2508
2593
  const threadId = c.req.param("threadId");
2509
2594
  const inputId = c.req.param("inputId");
2510
- const knownThread = await ensureKnownThread({
2511
- appServer,
2512
- threadId,
2513
- messagesByThreadId,
2514
- threads
2515
- });
2516
- if (!knownThread) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", `Thread ${threadId} is not known to this server.`), 404);
2517
- if (!appServer) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("unsupported", "Running-thread input requires the Codex app-server."), 409);
2518
- if (knownThread.state !== "running") return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("thread_not_running", `Thread ${threadId} is not currently running.`), 409);
2519
- const queuedInput = removeQueuedInput(queuedInputsByThreadId, threadId, inputId);
2520
- if (!queuedInput) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", `Queued input ${inputId} is not known to this thread.`), 404);
2521
- steeringThreads.add(threadId);
2522
- await startAppServerTurn(appServer, threadId, queuedInput);
2523
- const thread = updateThread(threads, messagesByThreadId, threadId, {
2524
- state: "running",
2525
- lastPrompt: promptWithAttachmentReferences(queuedInput.prompt, queuedInput.attachments),
2526
- lastError: void 0
2527
- });
2528
- const response = QueuedThreadInputActionResponseSchema.parse({
2529
- input: queuedThreadInputSummary(queuedInput),
2530
- queueLength: queuedInputsByThreadId.get(threadId)?.length ?? 0,
2531
- thread
2595
+ return runThreadOperation(threadId, async () => {
2596
+ const knownThread = await ensureKnownThread({
2597
+ appServer,
2598
+ threadId,
2599
+ messagesByThreadId,
2600
+ threads
2601
+ });
2602
+ if (!knownThread) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", `Thread ${threadId} is not known to this server.`), 404);
2603
+ if (!appServer) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("unsupported", "Running-thread input requires the Codex app-server."), 409);
2604
+ if (knownThread.state !== "running") return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("thread_not_running", `Thread ${threadId} is not currently running.`), 409);
2605
+ const queuedInput = removeQueuedInput(queuedInputsByThreadId, threadId, inputId);
2606
+ if (!queuedInput) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("not_found", `Queued input ${inputId} is not known to this thread.`), 404);
2607
+ steeringThreads.add(threadId);
2608
+ await runAppServerMutation(threadId, () => startAppServerTurn(appServer, threadId, queuedInput));
2609
+ const thread = updateThread(threads, messagesByThreadId, threadId, {
2610
+ state: "running",
2611
+ lastPrompt: promptWithAttachmentReferences(queuedInput.prompt, queuedInput.attachments),
2612
+ lastError: void 0
2613
+ });
2614
+ const response = QueuedThreadInputActionResponseSchema.parse({
2615
+ input: queuedThreadInputSummary(queuedInput),
2616
+ queueLength: queuedInputsByThreadId.get(threadId)?.length ?? 0,
2617
+ thread
2618
+ });
2619
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, response, 202);
2532
2620
  });
2533
- return secureJson(c, options.pairing, secureSessionsByTokenHash, response, 202);
2534
2621
  });
2535
2622
  app.post("/v1/threads/:threadId/runs/interrupt", async (c) => {
2536
2623
  const threadId = c.req.param("threadId");
@@ -2717,6 +2804,7 @@ function createApp(options = {}) {
2717
2804
  messagesByThreadId,
2718
2805
  pendingApprovals,
2719
2806
  queuedInputsByThreadId,
2807
+ runAppServerMutation,
2720
2808
  runThreadOperation,
2721
2809
  prompt: rawPrompt,
2722
2810
  attachments: runOptions.attachments ?? [],
@@ -2763,7 +2851,7 @@ async function pairedClientSessionIdForAuthorization(authorization, pairing) {
2763
2851
  if (!pairing) return;
2764
2852
  const token = parseBearerToken(authorization);
2765
2853
  if (!token) return;
2766
- return (await pairing.sessions.getValidSession(pairing.hashClientToken(token), Date.now()))?.clientSessionId;
2854
+ return (await pairing.sessions.getValidSession(pairing.hashClientToken(token)))?.clientSessionId;
2767
2855
  }
2768
2856
  function defaultPushNotificationPreferences() {
2769
2857
  return {
@@ -2805,7 +2893,7 @@ async function createApprovalCode(sessions) {
2805
2893
  throw new Error("Unable to allocate a pairing approval code.");
2806
2894
  }
2807
2895
  async function getValidClientSession(pairing, token) {
2808
- return pairing.sessions.getValidSession(pairing.hashClientToken(token), Date.now());
2896
+ return pairing.sessions.getValidSession(pairing.hashClientToken(token));
2809
2897
  }
2810
2898
  function externalRequestOrigin(requestUrl, header) {
2811
2899
  const request = new URL(requestUrl);
@@ -2929,6 +3017,7 @@ async function runPromptStreamed(input) {
2929
3017
  messagesByThreadId: input.messagesByThreadId,
2930
3018
  pendingApprovals: input.pendingApprovals,
2931
3019
  queuedInputsByThreadId: input.queuedInputsByThreadId,
3020
+ runAppServerMutation: input.runAppServerMutation,
2932
3021
  runThreadOperation: input.runThreadOperation,
2933
3022
  prompt: input.prompt,
2934
3023
  runOptions: input.runOptions,
@@ -3549,7 +3638,7 @@ async function runAppServerPromptStreamed(input) {
3549
3638
  observedInputRequest = false;
3550
3639
  producedTurnOutput = false;
3551
3640
  }
3552
- function finishTerminalTurn(options) {
3641
+ async function finishTerminalTurn(options) {
3553
3642
  const terminalTurnId = options.turnId ?? activeTurnId;
3554
3643
  if (terminalTurnId && finalizedTurnIds.has(terminalTurnId)) return;
3555
3644
  if (terminalTurnId) {
@@ -3627,25 +3716,36 @@ async function runAppServerPromptStreamed(input) {
3627
3716
  handedOffToQueuedTurn = true;
3628
3717
  resetTurnTracking();
3629
3718
  input.steeringThreads.add(activeThreadId);
3630
- startAppServerTurn(input.appServer, activeThreadId, nextQueuedInput).then(processReturnedTurn).catch((error) => {
3631
- cleanupNotificationHandler();
3632
- rejectCompleted(error);
3633
- });
3719
+ await processReturnedTurn(await startAppServerTurn(input.appServer, activeThreadId, nextQueuedInput));
3634
3720
  return;
3635
3721
  }
3636
3722
  input.steeringThreads.delete(activeThreadId);
3637
3723
  cleanupNotificationHandler();
3638
3724
  resolveCompleted();
3639
3725
  }
3640
- function processReturnedTurn(turn) {
3726
+ async function processReturnedTurn(turn) {
3641
3727
  if (finalizedTurnIds.has(turn.id)) return;
3642
3728
  activeTurnId = turn.id;
3643
3729
  input.activeAppServerTurnIdsByThreadId.set(activeThreadId, turn.id);
3644
3730
  const turnIsRunning = isAppServerTurnRunning(turn);
3645
3731
  for (const item of turn.items) {
3732
+ const previousUserMessage = userMessage;
3646
3733
  const canonicalUserMessage = replaceDuplicateInitialUserMessage(input.messagesByThreadId, activeThreadId, turn.id, item, userMessage.id, displayPrompt);
3647
3734
  if (canonicalUserMessage) {
3648
3735
  userMessage = canonicalUserMessage;
3736
+ if (canonicalUserMessage.id !== previousUserMessage.id || canonicalUserMessage.turnId !== previousUserMessage.turnId) {
3737
+ relayDebugLog("app_server.user_message.canonicalized", {
3738
+ messageId: canonicalUserMessage.id,
3739
+ previousMessageId: previousUserMessage.id,
3740
+ threadId: activeThreadId,
3741
+ turnId: canonicalUserMessage.turnId
3742
+ });
3743
+ sendSse(input.controller, input.encoder, input.secureSession, {
3744
+ type: "thread.message.created",
3745
+ thread: threadSummary,
3746
+ message: canonicalUserMessage
3747
+ });
3748
+ }
3649
3749
  continue;
3650
3750
  }
3651
3751
  const message = upsertAppServerItemMessage(input.messagesByThreadId, activeThreadId, turn.id, item);
@@ -3665,13 +3765,20 @@ async function runAppServerPromptStreamed(input) {
3665
3765
  if (turnIsRunning) return;
3666
3766
  const params = { turn };
3667
3767
  const state = terminalTurnState("turn/completed", params);
3668
- finishTerminalTurn({
3768
+ await finishTerminalTurn({
3669
3769
  lastError: state === "failed" ? turnErrorMessage(params) : void 0,
3670
3770
  method: "turn/returned",
3671
3771
  state,
3672
3772
  turnId: turn.id
3673
3773
  });
3674
3774
  }
3775
+ function startAndProcessAppServerTurn(threadId, queuedInput) {
3776
+ return input.runAppServerMutation(threadId, async () => {
3777
+ const turn = await startAppServerTurn(input.appServer, threadId, queuedInput);
3778
+ await processReturnedTurn(turn);
3779
+ return turn;
3780
+ });
3781
+ }
3675
3782
  const completed = new Promise((resolve, reject) => {
3676
3783
  resolveCompleted = resolve;
3677
3784
  rejectCompleted = reject;
@@ -3711,9 +3818,23 @@ async function runAppServerPromptStreamed(input) {
3711
3818
  case "item/completed": {
3712
3819
  const item = params?.item;
3713
3820
  if (!item || typeof item !== "object") return;
3821
+ const previousUserMessage = userMessage;
3714
3822
  const canonicalUserMessage = replaceDuplicateInitialUserMessage(input.messagesByThreadId, activeThreadId, firstString(params, ["turnId"]) ?? activeTurnId, item, userMessage.id, displayPrompt);
3715
3823
  if (canonicalUserMessage) {
3716
3824
  userMessage = canonicalUserMessage;
3825
+ if (canonicalUserMessage.id !== previousUserMessage.id || canonicalUserMessage.turnId !== previousUserMessage.turnId) {
3826
+ relayDebugLog("app_server.user_message.canonicalized", {
3827
+ messageId: canonicalUserMessage.id,
3828
+ previousMessageId: previousUserMessage.id,
3829
+ threadId: activeThreadId,
3830
+ turnId: canonicalUserMessage.turnId
3831
+ });
3832
+ sendSse(input.controller, input.encoder, input.secureSession, {
3833
+ type: "thread.message.created",
3834
+ thread: threadSummary,
3835
+ message: canonicalUserMessage
3836
+ });
3837
+ }
3717
3838
  return;
3718
3839
  }
3719
3840
  const turnId = firstString(params, ["turnId"]) ?? activeTurnId;
@@ -3784,12 +3905,12 @@ async function runAppServerPromptStreamed(input) {
3784
3905
  const state = terminalTurnState(notification.method, params);
3785
3906
  const terminalTurnId = firstString(params, ["turnId"]) ?? turnIdFromParams(params) ?? activeTurnId;
3786
3907
  input.runThreadOperation(activeThreadId, async () => {
3787
- finishTerminalTurn({
3908
+ await input.runAppServerMutation(activeThreadId, () => finishTerminalTurn({
3788
3909
  lastError: state === "failed" ? turnErrorMessage(params) : void 0,
3789
3910
  method: notification.method,
3790
3911
  state,
3791
3912
  turnId: terminalTurnId
3792
- });
3913
+ }));
3793
3914
  }).catch((error) => {
3794
3915
  cleanupNotificationHandler();
3795
3916
  rejectCompleted(error);
@@ -3850,7 +3971,7 @@ async function runAppServerPromptStreamed(input) {
3850
3971
  debugStream("start turn begin", activeThreadId);
3851
3972
  let turn;
3852
3973
  try {
3853
- turn = await startAppServerTurn(input.appServer, activeThreadId, {
3974
+ turn = await startAndProcessAppServerTurn(activeThreadId, {
3854
3975
  attachments: input.attachments,
3855
3976
  id: randomUUID(),
3856
3977
  prompt,
@@ -3884,7 +4005,7 @@ async function runAppServerPromptStreamed(input) {
3884
4005
  thread: threadSummary,
3885
4006
  message: userMessage
3886
4007
  });
3887
- turn = await startAppServerTurn(input.appServer, activeThreadId, {
4008
+ turn = await startAndProcessAppServerTurn(activeThreadId, {
3888
4009
  attachments: input.attachments,
3889
4010
  id: randomUUID(),
3890
4011
  prompt,
@@ -3894,7 +4015,6 @@ async function runAppServerPromptStreamed(input) {
3894
4015
  });
3895
4016
  }
3896
4017
  debugStream("start turn complete", activeThreadId, turn.id);
3897
- processReturnedTurn(turn);
3898
4018
  await completed;
3899
4019
  } catch (error) {
3900
4020
  debugStream(`failed ${errorMessage(error)}`, activeThreadId, activeTurnId);
@@ -4862,9 +4982,9 @@ function mapAppServerThread(thread, fallbackMessageCount) {
4862
4982
  lastActivityAt: updatedAt
4863
4983
  });
4864
4984
  }
4865
- function rememberAppServerThread(threads, thread) {
4985
+ function rememberAppServerThread(threads, thread, options = {}) {
4866
4986
  const existingThread = threads.get(thread.id);
4867
- const mappedThread = mapAppServerThread(thread, existingThread?.messageCount);
4987
+ const mappedThread = mapAppServerThread(thread, options.authoritativeMessageCount ? void 0 : existingThread?.messageCount);
4868
4988
  const threadWithLocalRuntime = ThreadSummarySchema.parse({
4869
4989
  ...mappedThread,
4870
4990
  goal: existingThread?.goal ?? mappedThread.goal,
@@ -5053,6 +5173,7 @@ function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePat
5053
5173
  const applyPatchInputs = /* @__PURE__ */ new Map();
5054
5174
  const handledApplyPatchCallIds = /* @__PURE__ */ new Set();
5055
5175
  const pendingApplyPatchChanges = [];
5176
+ let activeTurnId;
5056
5177
  const lines = readFileSync(rolloutPath, "utf8").split("\n");
5057
5178
  for (let index = 0; index < lines.length; index += 1) {
5058
5179
  const line = lines[index];
@@ -5061,14 +5182,17 @@ function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePat
5061
5182
  if (!isRolloutMessageLine(line)) continue;
5062
5183
  try {
5063
5184
  const record = JSON.parse(line);
5185
+ activeTurnId = firstString(record.payload, ["turn_id", "turnId"]) ?? activeTurnId;
5064
5186
  rememberRolloutApplyPatchInput(record, applyPatchInputs);
5065
5187
  collectRolloutApplyPatchOutput(record, workspacePath, applyPatchInputs, handledApplyPatchCallIds, pendingApplyPatchChanges);
5066
5188
  if (isRolloutTaskComplete(record) && pendingApplyPatchChanges.length > 0) {
5067
5189
  collected.push(rolloutApplyPatchSummaryMessage(threadId, record, `rollout:${lineNumber}:apply_patch`, pendingApplyPatchChanges));
5068
5190
  pendingApplyPatchChanges.length = 0;
5191
+ activeTurnId = void 0;
5069
5192
  continue;
5070
5193
  }
5071
- const message = rolloutRecordMessage(threadId, record, `rollout:${lineNumber}`, workspacePath);
5194
+ const message = rolloutRecordMessage(threadId, record, `rollout:${lineNumber}`, workspacePath, activeTurnId);
5195
+ if (isRolloutTaskComplete(record)) activeTurnId = void 0;
5072
5196
  if (!message) continue;
5073
5197
  collected.push(message);
5074
5198
  const patchApplyEndCallId = rolloutPatchApplyEndCallId(record);
@@ -5103,7 +5227,7 @@ function findRolloutFileForThread(threadId) {
5103
5227
  function isRolloutTaskComplete(record) {
5104
5228
  return record.type === "event_msg" && record.payload?.type === "task_complete";
5105
5229
  }
5106
- function rolloutRecordMessage(threadId, record, messageKey, workspacePath = defaultWorkspacePath) {
5230
+ function rolloutRecordMessage(threadId, record, messageKey, workspacePath = defaultWorkspacePath, turnId) {
5107
5231
  const timestamp = typeof record.timestamp === "string" ? record.timestamp : (/* @__PURE__ */ new Date()).toISOString();
5108
5232
  const payload = record.payload;
5109
5233
  if (!payload) return;
@@ -5117,7 +5241,8 @@ function rolloutRecordMessage(threadId, record, messageKey, workspacePath = defa
5117
5241
  content,
5118
5242
  details: rolloutUserMessageDetails(payload),
5119
5243
  createdAt: timestamp,
5120
- state: "completed"
5244
+ state: "completed",
5245
+ turnId
5121
5246
  });
5122
5247
  }
5123
5248
  if (record.type === "event_msg" && payload.type === "agent_message") {
@@ -5131,7 +5256,8 @@ function rolloutRecordMessage(threadId, record, messageKey, workspacePath = defa
5131
5256
  content: messageParts.content,
5132
5257
  details: messageParts.details,
5133
5258
  createdAt: timestamp,
5134
- state: "completed"
5259
+ state: "completed",
5260
+ turnId
5135
5261
  });
5136
5262
  }
5137
5263
  if (record.type === "event_msg" && payload.type === "patch_apply_end") {
@@ -5339,7 +5465,7 @@ function fileUrlPath(value) {
5339
5465
  }
5340
5466
  }
5341
5467
  function isRolloutMessageLine(line) {
5342
- return line.includes("\"type\":\"event_msg\"") && (line.includes("\"type\":\"user_message\"") || line.includes("\"type\":\"agent_message\"") || line.includes("\"type\":\"patch_apply_end\"") || line.includes("\"type\":\"task_complete\"") || line.includes("\"type\":\"exec_command_end\"") || line.includes("\"type\":\"mcp_tool_call_end\"")) || line.includes("\"type\":\"response_item\"") && (line.includes("\"type\":\"custom_tool_call\"") || line.includes("\"type\":\"custom_tool_call_output\""));
5468
+ return line.includes("\"type\":\"event_msg\"") && (line.includes("\"type\":\"user_message\"") || line.includes("\"type\":\"agent_message\"") || line.includes("\"type\":\"patch_apply_end\"") || line.includes("\"type\":\"task_started\"") || line.includes("\"type\":\"task_complete\"") || line.includes("\"type\":\"exec_command_end\"") || line.includes("\"type\":\"mcp_tool_call_end\"")) || line.includes("\"type\":\"response_item\"") && (line.includes("\"type\":\"custom_tool_call\"") || line.includes("\"type\":\"custom_tool_call_output\""));
5343
5469
  }
5344
5470
  function threadDetailResponse(input) {
5345
5471
  return ThreadDetailResponseSchema.parse({
@@ -5836,7 +5962,6 @@ function isApprovalServerRequest(method) {
5836
5962
  }
5837
5963
  function observeAppServerPushNotifications(appServer, dispatcher) {
5838
5964
  const dispatchedEventIds = /* @__PURE__ */ new Set();
5839
- const subagentThreadIds = /* @__PURE__ */ new Set();
5840
5965
  const rememberEventId = (eventId) => {
5841
5966
  if (dispatchedEventIds.size >= 1e3) dispatchedEventIds.clear();
5842
5967
  dispatchedEventIds.add(eventId);
@@ -5844,7 +5969,9 @@ function observeAppServerPushNotifications(appServer, dispatcher) {
5844
5969
  const dispatch = (event, eventId) => {
5845
5970
  if (dispatchedEventIds.has(eventId)) return;
5846
5971
  rememberEventId(eventId);
5847
- dispatcher.dispatch(event).catch((error) => {
5972
+ appServer.readThread(event.threadId, { includeTurns: false }).then((thread) => {
5973
+ if (!isSubagentThread(thread)) return dispatcher.dispatch(event);
5974
+ }).catch((error) => {
5848
5975
  relayDebugLog("push_notification.dispatch_failed", {
5849
5976
  error: errorMessage(error),
5850
5977
  intent: event.intent,
@@ -5854,36 +5981,16 @@ function observeAppServerPushNotifications(appServer, dispatcher) {
5854
5981
  });
5855
5982
  };
5856
5983
  appServer.onNotification((notification) => {
5857
- rememberSubagentThreadIds(notification, subagentThreadIds);
5858
5984
  const event = pushNotificationEventFromTerminalNotification(notification);
5859
5985
  if (!event) return;
5860
- const eventId = `${event.intent}:${event.threadId}:${event.turnId ?? ""}`;
5861
- if (subagentThreadIds.delete(event.threadId)) {
5862
- rememberEventId(eventId);
5863
- return;
5864
- }
5865
- dispatch(event, eventId);
5986
+ dispatch(event, `${event.intent}:${event.threadId}:${event.turnId ?? ""}`);
5866
5987
  });
5867
5988
  appServer.onRequest((request) => {
5868
5989
  const event = pushNotificationEventFromApprovalRequest(request);
5869
5990
  if (!event) return;
5870
- const eventId = `${event.intent}:${event.threadId}:${event.turnId ?? ""}:${request.id}`;
5871
- if (subagentThreadIds.has(event.threadId)) {
5872
- rememberEventId(eventId);
5873
- return;
5874
- }
5875
- dispatch(event, eventId);
5991
+ dispatch(event, `${event.intent}:${event.threadId}:${event.turnId ?? ""}:${request.id}`);
5876
5992
  });
5877
5993
  }
5878
- function rememberSubagentThreadIds(notification, subagentThreadIds) {
5879
- const item = objectRecord(recordParams(notification)?.item);
5880
- if (item?.type !== "collabAgentToolCall" || ![
5881
- "spawnAgent",
5882
- "sendInput",
5883
- "resumeAgent"
5884
- ].includes(String(item.tool))) return;
5885
- for (const threadId of stringArray(item.receiverThreadIds)) subagentThreadIds.add(threadId);
5886
- }
5887
5994
  function pushNotificationEventFromTerminalNotification(notification) {
5888
5995
  if (notification.method !== "turn/completed" && notification.method !== "turn/failed") return;
5889
5996
  const params = recordParams(notification);
@@ -6859,7 +6966,6 @@ function errorMessage(error) {
6859
6966
  //#region src/index.ts
6860
6967
  const port = Number(process.env.PORT ?? 8787);
6861
6968
  const hostname$1 = process.env.HOST ?? "0.0.0.0";
6862
- const clientTokenTtlMs = 10080 * 60 * 1e3;
6863
6969
  const dangerouslyAutoApprove = process.env.CODEX_RELAY_DANGEROUSLY_AUTO_APPROVE === "1";
6864
6970
  const serverIdentity = await getServerIdentity();
6865
6971
  const approvalSecret = await getApprovalSecret();
@@ -6907,7 +7013,6 @@ serve({
6907
7013
  createClientToken: () => randomBytes(32).toString("base64url"),
6908
7014
  hashClientToken,
6909
7015
  sessions: sessionStore,
6910
- tokenTtlMs: clientTokenTtlMs,
6911
7016
  onPaired: ({ clientName, tokenCount }) => {
6912
7017
  logRuntimeEvent("Paired", `Mobile client connected${clientName ? ` from ${clientName}` : ""}; ${formatClientCount(tokenCount)} active.`);
6913
7018
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-relay",
3
- "version": "1.4.4",
3
+ "version": "1.4.6",
4
4
  "description": "Local Codex Relay CLI bridge for the Codex Relay mobile app.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/api-schema.ts CHANGED
@@ -681,6 +681,18 @@ export const ArchiveThreadResponseSchema = z.object({
681
681
  source: z.enum(["app-server", "memory"]).default("memory"),
682
682
  });
683
683
 
684
+ export const RenameThreadRequestSchema = z.object({
685
+ title: z.string().trim().min(1).max(120),
686
+ });
687
+
688
+ export const RenameThreadResponseSchema = z.object({
689
+ thread: ThreadSummarySchema,
690
+ });
691
+
692
+ export const RewindThreadRequestSchema = z.object({
693
+ turnId: z.string().trim().min(1),
694
+ });
695
+
684
696
  export const ListModelsResponseSchema = z.object({
685
697
  models: z.array(CodexModelSchema),
686
698
  });
@@ -844,6 +856,9 @@ export type UpdateRuntimePreferencesRequest = z.infer<typeof UpdateRuntimePrefer
844
856
  export type VersionResponse = z.infer<typeof VersionResponseSchema>;
845
857
  export type ListThreadsResponse = z.infer<typeof ListThreadsResponseSchema>;
846
858
  export type ArchiveThreadResponse = z.infer<typeof ArchiveThreadResponseSchema>;
859
+ export type RenameThreadRequest = z.infer<typeof RenameThreadRequestSchema>;
860
+ export type RenameThreadResponse = z.infer<typeof RenameThreadResponseSchema>;
861
+ export type RewindThreadRequest = z.infer<typeof RewindThreadRequestSchema>;
847
862
  export type ListModelsResponse = z.infer<typeof ListModelsResponseSchema>;
848
863
  export type ListSkillsResponse = z.infer<typeof ListSkillsResponseSchema>;
849
864
  export type ListWorkspaceFilesResponse = z.infer<typeof ListWorkspaceFilesResponseSchema>;
@@ -1089,6 +1104,8 @@ export const apiPaths = {
1089
1104
  threads: "/v1/threads",
1090
1105
  thread: (threadId: string) => `/v1/threads/${encodeURIComponent(threadId)}`,
1091
1106
  threadArchive: (threadId: string) => `/v1/threads/${encodeURIComponent(threadId)}`,
1107
+ threadName: (threadId: string) => `/v1/threads/${encodeURIComponent(threadId)}/name`,
1108
+ threadRollback: (threadId: string) => `/v1/threads/${encodeURIComponent(threadId)}/rollback`,
1092
1109
  threadContextWindow: (threadId: string) =>
1093
1110
  `/v1/threads/${encodeURIComponent(threadId)}/context-window`,
1094
1111
  threadGoal: (threadId: string) => `/v1/threads/${encodeURIComponent(threadId)}/goal`,
@@ -1191,6 +1208,11 @@ export function createOpenApiDocument() {
1191
1208
  required: true,
1192
1209
  schema: { type: "string" },
1193
1210
  },
1211
+ {
1212
+ name: "refresh",
1213
+ in: "query",
1214
+ schema: { type: "boolean" },
1215
+ },
1194
1216
  ],
1195
1217
  responses: {
1196
1218
  "200": jsonResponse("ThreadDetailResponse"),
@@ -1214,6 +1236,48 @@ export function createOpenApiDocument() {
1214
1236
  },
1215
1237
  },
1216
1238
  },
1239
+ "/v1/threads/{threadId}/name": {
1240
+ post: {
1241
+ summary: "Rename a Codex app-server thread",
1242
+ parameters: [
1243
+ {
1244
+ name: "threadId",
1245
+ in: "path",
1246
+ required: true,
1247
+ schema: { type: "string" },
1248
+ },
1249
+ ],
1250
+ requestBody: jsonRequest("RenameThreadRequest"),
1251
+ responses: {
1252
+ "200": jsonResponse("RenameThreadResponse"),
1253
+ "400": jsonResponse("ErrorResponse"),
1254
+ "404": jsonResponse("ErrorResponse"),
1255
+ "409": jsonResponse("ErrorResponse"),
1256
+ "502": jsonResponse("ErrorResponse"),
1257
+ },
1258
+ },
1259
+ },
1260
+ "/v1/threads/{threadId}/rollback": {
1261
+ post: {
1262
+ summary: "Rewind a Codex app-server thread to before a selected turn",
1263
+ parameters: [
1264
+ {
1265
+ name: "threadId",
1266
+ in: "path",
1267
+ required: true,
1268
+ schema: { type: "string" },
1269
+ },
1270
+ ],
1271
+ requestBody: jsonRequest("RewindThreadRequest"),
1272
+ responses: {
1273
+ "200": jsonResponse("ThreadDetailResponse"),
1274
+ "400": jsonResponse("ErrorResponse"),
1275
+ "404": jsonResponse("ErrorResponse"),
1276
+ "409": jsonResponse("ErrorResponse"),
1277
+ "502": jsonResponse("ErrorResponse"),
1278
+ },
1279
+ },
1280
+ },
1217
1281
  "/v1/workspace-directories": {
1218
1282
  get: {
1219
1283
  summary: "List directories under the configured workspace",
@@ -1452,6 +1516,27 @@ export function createOpenApiDocument() {
1452
1516
  tokenBudget: { anyOf: [{ type: "integer", minimum: 1 }, { type: "null" }] },
1453
1517
  },
1454
1518
  },
1519
+ RenameThreadRequest: {
1520
+ type: "object",
1521
+ required: ["title"],
1522
+ properties: {
1523
+ title: { type: "string", minLength: 1, maxLength: 120 },
1524
+ },
1525
+ },
1526
+ RenameThreadResponse: {
1527
+ type: "object",
1528
+ required: ["thread"],
1529
+ properties: {
1530
+ thread: { $ref: "#/components/schemas/ThreadSummary" },
1531
+ },
1532
+ },
1533
+ RewindThreadRequest: {
1534
+ type: "object",
1535
+ required: ["turnId"],
1536
+ properties: {
1537
+ turnId: { type: "string", minLength: 1 },
1538
+ },
1539
+ },
1455
1540
  ThreadGoalResponse: {
1456
1541
  type: "object",
1457
1542
  required: ["goal", "thread"],