remote-codex 0.11.32 → 0.11.34

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.
@@ -10,16 +10,16 @@
10
10
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
11
11
  <link rel="manifest" href="/site.webmanifest" />
12
12
  <title>Remote Codex</title>
13
- <script type="module" crossorigin src="/assets/index-CGHHTNkM.js"></script>
13
+ <script type="module" crossorigin src="/assets/index-PHqiYl42.js"></script>
14
14
  <link rel="modulepreload" crossorigin href="/assets/react-vendor-Dfg_6BLf.js">
15
15
  <link rel="modulepreload" crossorigin href="/assets/ui-vendor-CuR8GHb0.js">
16
16
  <link rel="modulepreload" crossorigin href="/assets/graph-vendor-DVQUpZ8C.js">
17
17
  <link rel="modulepreload" crossorigin href="/assets/terminal-vendor-C5bTa-Ka.js">
18
18
  <link rel="modulepreload" crossorigin href="/assets/markdown-vendor-RZk8L7-L.js">
19
- <link rel="modulepreload" crossorigin href="/assets/thread-ui-B9eC2H4u.js">
19
+ <link rel="modulepreload" crossorigin href="/assets/thread-ui-DoKAPILv.js">
20
20
  <link rel="stylesheet" crossorigin href="/assets/graph-vendor-C5ap-Sga.css">
21
21
  <link rel="stylesheet" crossorigin href="/assets/terminal-vendor-Beg8tuEN.css">
22
- <link rel="stylesheet" crossorigin href="/assets/index-CdjTdnJt.css">
22
+ <link rel="stylesheet" crossorigin href="/assets/index-BTG-ELMo.css">
23
23
  </head>
24
24
  <body class="bg-stone-950">
25
25
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remote-codex",
3
- "version": "0.11.32",
3
+ "version": "0.11.34",
4
4
  "description": "Local web supervisor for Codex workspaces and threads.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -15,6 +15,7 @@ interface Query extends AsyncIterable<SDKMessage> {
15
15
  interrupt(): Promise<void>;
16
16
  supportedModels(): Promise<any[]>;
17
17
  mcpServerStatus(): Promise<any[]>;
18
+ usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET(): Promise<any>;
18
19
  }
19
20
 
20
21
  function wait(ms = 0) {
@@ -29,7 +30,7 @@ class FakeQuery implements Query {
29
30
 
30
31
  constructor(
31
32
  private readonly messages: SDKMessage[],
32
- private readonly options: { holdOpen?: boolean } = {},
33
+ private readonly options: { holdOpen?: boolean; usage?: Record<string, any> } = {},
33
34
  ) {}
34
35
 
35
36
  [Symbol.asyncIterator]() {
@@ -104,6 +105,12 @@ class FakeQuery implements Query {
104
105
  async getContextUsage(): Promise<any> {
105
106
  return {};
106
107
  }
108
+ async usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET(): Promise<any> {
109
+ if (!this.options.usage) {
110
+ throw new Error('Structured usage unavailable in this fixture.');
111
+ }
112
+ return this.options.usage;
113
+ }
107
114
  async readFile(): Promise<any> {
108
115
  return {};
109
116
  }
@@ -208,6 +215,55 @@ function makeAdapter(
208
215
  }
209
216
 
210
217
  describe('ClaudeRuntimeAdapter', () => {
218
+ it('actively reads Claude subscription windows from the structured usage API', async () => {
219
+ const adapter = makeAdapter(() => new FakeQuery(
220
+ [systemInit(), result()],
221
+ {
222
+ usage: {
223
+ subscription_type: 'max',
224
+ rate_limits_available: true,
225
+ rate_limits: {
226
+ five_hour: {
227
+ utilization: 23,
228
+ resets_at: '2027-01-15T08:00:00.000Z',
229
+ },
230
+ seven_day: {
231
+ utilization: 84,
232
+ resets_at: '2027-01-20T08:00:00.000Z',
233
+ },
234
+ },
235
+ },
236
+ },
237
+ ));
238
+
239
+ await adapter.start();
240
+ await adapter.startSession({
241
+ cwd: '/tmp/workspace',
242
+ model: 'sonnet',
243
+ approvalMode: 'guarded',
244
+ sandboxMode: 'workspace-write',
245
+ });
246
+
247
+ await expect(adapter.getSubscriptionUsage()).resolves.toMatchObject({
248
+ provider: 'claude',
249
+ authKind: 'subscription',
250
+ windows: [
251
+ {
252
+ id: 'five_hour',
253
+ label: '5h',
254
+ usedPercent: 23,
255
+ resetsAt: '2027-01-15T08:00:00.000Z',
256
+ },
257
+ {
258
+ id: 'seven_day',
259
+ label: '7d',
260
+ usedPercent: 84,
261
+ resetsAt: '2027-01-20T08:00:00.000Z',
262
+ },
263
+ ],
264
+ });
265
+ });
266
+
211
267
  it('captures Claude subscription rate-limit windows from SDK events', async () => {
212
268
  const adapter = makeAdapter(() => [
213
269
  systemInit(),
@@ -544,6 +600,13 @@ describe('ClaudeRuntimeAdapter', () => {
544
600
  }),
545
601
  ],
546
602
  });
603
+
604
+ await adapter.interruptTurn({
605
+ providerSessionId: 'claude-session-1',
606
+ providerTurnId: started.providerTurnId,
607
+ });
608
+ const reloadedSession = await adapter.readSession('claude-session-1');
609
+ expect(reloadedSession.turns[0]?.providerTurnId).toBe(started.providerTurnId);
547
610
  });
548
611
 
549
612
  it('keeps image blocks visible when reading Claude session history', async () => {
@@ -99,6 +99,19 @@ interface Query extends AsyncIterable<SDKMessage> {
99
99
  interrupt(): Promise<void>;
100
100
  supportedModels(): Promise<ModelInfo[]>;
101
101
  mcpServerStatus(): Promise<McpServerStatus[]>;
102
+ usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET?(): Promise<SDKUsageResponse>;
103
+ }
104
+ interface SDKUsageWindow {
105
+ utilization: number | null;
106
+ resets_at: string | null;
107
+ }
108
+ interface SDKUsageResponse {
109
+ subscription_type: string | null;
110
+ rate_limits_available: boolean;
111
+ rate_limits: {
112
+ five_hour?: SDKUsageWindow | null;
113
+ seven_day?: SDKUsageWindow | null;
114
+ } | null;
102
115
  }
103
116
  interface SDKMessage {
104
117
  type: string;
@@ -1239,7 +1252,9 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1239
1252
  'five_hour' | 'seven_day',
1240
1253
  { usedPercent: number; resetsAt: string | null }
1241
1254
  >();
1255
+ private subscriptionAuthKind: 'subscription' | 'apiKey' | 'unknown' = 'unknown';
1242
1256
  private subscriptionUsageObservedAt: string | null = null;
1257
+ private readonly historicalTurnIdAliases = new Map<string, Map<string, string>>();
1243
1258
  private readonly clientApp: string;
1244
1259
  private sdkLoadError: string | null = null;
1245
1260
 
@@ -1271,7 +1286,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1271
1286
  }));
1272
1287
  return {
1273
1288
  provider: 'claude' as const,
1274
- authKind: windows.length > 0 ? 'subscription' as const : 'unknown' as const,
1289
+ authKind: this.subscriptionAuthKind,
1275
1290
  observedAt,
1276
1291
  stale: false,
1277
1292
  windows,
@@ -1297,9 +1312,56 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1297
1312
  ? new Date(info.resetsAt * 1000).toISOString()
1298
1313
  : null,
1299
1314
  });
1315
+ this.subscriptionAuthKind = 'subscription';
1300
1316
  this.subscriptionUsageObservedAt = new Date().toISOString();
1301
1317
  }
1302
1318
 
1319
+ private async captureStructuredSubscriptionUsage(query: Query) {
1320
+ const getUsage = query.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET;
1321
+ if (!getUsage) {
1322
+ return;
1323
+ }
1324
+
1325
+ try {
1326
+ const usage = await getUsage.call(query);
1327
+ const nextWindows = new Map<
1328
+ 'five_hour' | 'seven_day',
1329
+ { usedPercent: number; resetsAt: string | null }
1330
+ >();
1331
+ const addWindow = (
1332
+ id: 'five_hour' | 'seven_day',
1333
+ window: SDKUsageWindow | null | undefined,
1334
+ ) => {
1335
+ if (
1336
+ typeof window?.utilization !== 'number'
1337
+ || !Number.isFinite(window.utilization)
1338
+ ) {
1339
+ return;
1340
+ }
1341
+ nextWindows.set(id, {
1342
+ usedPercent: Math.max(0, Math.min(100, window.utilization)),
1343
+ resetsAt: typeof window.resets_at === 'string' ? window.resets_at : null,
1344
+ });
1345
+ };
1346
+ addWindow('five_hour', usage.rate_limits?.five_hour);
1347
+ addWindow('seven_day', usage.rate_limits?.seven_day);
1348
+
1349
+ this.subscriptionUsageWindows.clear();
1350
+ for (const [id, window] of nextWindows) {
1351
+ this.subscriptionUsageWindows.set(id, window);
1352
+ }
1353
+ this.subscriptionAuthKind = usage.subscription_type
1354
+ ? 'subscription'
1355
+ : usage.rate_limits_available
1356
+ ? 'subscription'
1357
+ : 'apiKey';
1358
+ this.subscriptionUsageObservedAt = new Date().toISOString();
1359
+ } catch {
1360
+ // This SDK method is experimental. Rate-limit events remain the
1361
+ // compatibility fallback for Claude versions that do not expose it.
1362
+ }
1363
+ }
1364
+
1303
1365
  private updateToolboxItemsFromSystemInit(message: SDKMessage) {
1304
1366
  this.managementSchema.toolboxItems = buildClaudeToolboxItems(
1305
1367
  normalizeClaudeSlashCommands(message.slash_commands),
@@ -1430,7 +1492,10 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1430
1492
  workspacePath: options.workspacePath || cwd,
1431
1493
  ...(options.localThreadId ? { localThreadId: options.localThreadId } : {}),
1432
1494
  };
1433
- const turns = await this.sessionMessagesToTurns(messages, historyAssetContext);
1495
+ const turns = this.applyHistoricalTurnIdAliases(
1496
+ providerSessionId,
1497
+ await this.sessionMessagesToTurns(messages, historyAssetContext),
1498
+ );
1434
1499
  const activeTurn = [...this.activeTurns.values()].find(
1435
1500
  (turn) => turn.providerSessionId === providerSessionId,
1436
1501
  );
@@ -1463,9 +1528,14 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1463
1528
  let providerSessionId: string | null = null;
1464
1529
  let model: string | null = input.model;
1465
1530
  const rawMessages: SDKMessage[] = [];
1531
+ let capturedStructuredUsage = false;
1466
1532
  try {
1467
1533
  for await (const message of query) {
1468
1534
  rawMessages.push(message);
1535
+ if (!capturedStructuredUsage) {
1536
+ capturedStructuredUsage = true;
1537
+ await this.captureStructuredSubscriptionUsage(query);
1538
+ }
1469
1539
  this.captureRateLimit(message);
1470
1540
  if (message.type === 'system' && message.subtype === 'init') {
1471
1541
  this.updateToolboxItemsFromSystemInit(message);
@@ -1701,6 +1771,16 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1701
1771
  return turns;
1702
1772
  }
1703
1773
 
1774
+ const transcriptTurn = turns[transcriptTurnIndex];
1775
+ if (transcriptTurn && transcriptTurn.providerTurnId !== activeTurn.providerTurnId) {
1776
+ let aliases = this.historicalTurnIdAliases.get(providerSessionId);
1777
+ if (!aliases) {
1778
+ aliases = new Map();
1779
+ this.historicalTurnIdAliases.set(providerSessionId, aliases);
1780
+ }
1781
+ aliases.set(transcriptTurn.providerTurnId, activeTurn.providerTurnId);
1782
+ }
1783
+
1704
1784
  return turns.map((turn, index) => {
1705
1785
  if (index !== transcriptTurnIndex) {
1706
1786
  return turn;
@@ -1718,6 +1798,20 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1718
1798
  });
1719
1799
  }
1720
1800
 
1801
+ private applyHistoricalTurnIdAliases(
1802
+ providerSessionId: string,
1803
+ turns: AgentTurn[],
1804
+ ) {
1805
+ const aliases = this.historicalTurnIdAliases.get(providerSessionId);
1806
+ if (!aliases || aliases.size === 0) {
1807
+ return turns;
1808
+ }
1809
+ return turns.map((turn) => {
1810
+ const providerTurnId = aliases.get(turn.providerTurnId);
1811
+ return providerTurnId ? { ...turn, providerTurnId } : turn;
1812
+ });
1813
+ }
1814
+
1721
1815
  async listMcpServers(): Promise<AgentMcpServer[]> {
1722
1816
  const active = [...this.activeTurns.values()][0];
1723
1817
  if (!active) {
@@ -1755,9 +1849,14 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1755
1849
  const rawMessages: SDKMessage[] = [];
1756
1850
  let terminalStatus: AgentTurn['status'] | null = null;
1757
1851
  let terminalError: string | null = null;
1852
+ let capturedStructuredUsage = false;
1758
1853
  try {
1759
1854
  for await (const message of state.query) {
1760
1855
  rawMessages.push(message);
1856
+ if (!capturedStructuredUsage) {
1857
+ capturedStructuredUsage = true;
1858
+ await this.captureStructuredSubscriptionUsage(state.query);
1859
+ }
1761
1860
  this.captureRateLimit(message);
1762
1861
  this.consumeMessage(state, message);
1763
1862
  const status = queryResultStatus(message);