@henryqw/pi-multi-codex 0.3.9 → 0.3.11

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
@@ -1,6 +1,6 @@
1
1
  # `@henryqw/pi-multi-codex`
2
2
 
3
- Use multiple ChatGPT Codex OAuth accounts in Pi. Native `/login` still owns credentials.
3
+ Use multiple ChatGPT Codex OAuth accounts in Pi.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,16 +8,23 @@ Use multiple ChatGPT Codex OAuth accounts in Pi. Native `/login` still owns cred
8
8
  pi install npm:@henryqw/pi-multi-codex
9
9
  ```
10
10
 
11
- ## Use
11
+ ## With
12
12
 
13
- | Surface | Purpose |
13
+ | Package | Why |
14
14
  | --- | --- |
15
- | `/login` | Authenticate slot 1 as OpenAI Codex. |
16
- | `/codex-add` | Create the next numbered slot, then `/login` and pick that slot. |
17
- | `/codex-status` | Show shared seven-day quota snapshots. Never waits on network. |
18
- | `/codex-switch` | Pick an authenticated slot. |
15
+ | `@henryqw/pi-model-thinking` | Improves. Thinking level follows the model across slots. |
16
+ | `@henryqw/pi-task-models` | Improves. Numbered slots share one profile route. |
17
+ | `@henryqw/pi-subagent` | Improves. Isolated children keep Main's active Codex slot. |
18
+
19
+ ## Use
20
+
21
+ | Surface | Type | Purpose |
22
+ | --- | --- | --- |
23
+ | `/codex-add` | command | Create the next numbered slot, then authenticate that slot. |
24
+ | `/codex-status` | command | Show shared seven-day quota snapshots. Never waits on network. |
25
+ | `/codex-switch` | command | Pick an authenticated slot. |
19
26
 
20
- Extension reads native `auth.json` and never writes or refreshes credentials. Before the first agent start, a fresh snapshot routes a managed Codex model to the slot with the most seven-day quota. Routing preserves model ID and never changes in-progress work. Footer shows the active slot and fresh seven-day quota. Scoped sessions can switch only to exact scoped aliases; restart or update model scope after adding a slot.
27
+ Extension reads `auth.json` and never writes or refreshes credentials. Before the first agent start, a fresh snapshot routes a managed Codex model to the slot with the most seven-day quota. Routing preserves model ID and never changes in-progress work. Footer shows the active slot and fresh seven-day quota. Scoped sessions can switch only to exact scoped aliases; restart or update model scope after adding a slot.
21
28
 
22
29
  Quota snapshots live in `~/.pi/agent/config/pi-multi-codex/usage.json`. They are credential-free cache, not user-edited config.
23
30
 
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
+ import { setTimeout as delay } from "node:timers/promises";
5
6
  import { lock } from "proper-lockfile";
6
7
  import {
7
8
  createAssistantMessageEventStream,
@@ -259,32 +260,6 @@ function errorCode(error: unknown): string | undefined {
259
260
  return error && typeof error === "object" && "code" in error ? String(error.code) : undefined;
260
261
  }
261
262
 
262
- function sleepUnref(ms: number, signal?: AbortSignal): Promise<void> {
263
- return new Promise((resolve, reject) => {
264
- if (signal?.aborted) return reject(signal.reason);
265
- let abort: () => void;
266
- const cleanup = () => signal?.removeEventListener("abort", abort);
267
- const timer = setTimeout(() => {
268
- cleanup();
269
- resolve();
270
- }, ms);
271
- timer.unref?.();
272
- abort = () => {
273
- clearTimeout(timer);
274
- cleanup();
275
- reject(signal?.reason);
276
- };
277
- signal?.addEventListener("abort", abort, { once: true });
278
- if (signal?.aborted) abort();
279
- });
280
- }
281
-
282
- function cleanupSignal(): AbortSignal {
283
- const controller = new AbortController();
284
- void sleepUnref(CLEANUP_TIMEOUT_MS).then(() => controller.abort());
285
- return controller.signal;
286
- }
287
-
288
263
  async function withCacheMutex<T>(operation: (signal: AbortSignal) => Promise<T>, signal?: AbortSignal): Promise<T> {
289
264
  signal?.throwIfAborted();
290
265
  await mkdir(join(getAgentDir(), "config", "pi-multi-codex"), { recursive: true, mode: 0o700 });
@@ -310,7 +285,7 @@ async function withCacheMutex<T>(operation: (signal: AbortSignal) => Promise<T>,
310
285
  }
311
286
  } catch (error) {
312
287
  if (errorCode(error) !== "ELOCKED") throw error;
313
- await sleepUnref(CACHE_MUTEX_RETRY_MS, signal);
288
+ await delay(CACHE_MUTEX_RETRY_MS, undefined, { signal, ref: false });
314
289
  }
315
290
  }
316
291
  throw new Error("Cache mutex acquisition ended unexpectedly.");
@@ -694,7 +669,7 @@ class CodexQuotaStatus {
694
669
  } finally {
695
670
  if (owner) {
696
671
  this.stopHeartbeat(slot, owner);
697
- await this.release(slot, owner, session.signal.aborted ? cleanupSignal() : session.signal).catch(() => undefined);
672
+ await this.release(slot, owner, session.signal.aborted ? AbortSignal.timeout(CLEANUP_TIMEOUT_MS) : session.signal).catch(() => undefined);
698
673
  }
699
674
  }
700
675
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-multi-codex",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "Use multiple ChatGPT Codex OAuth accounts in Pi.",
5
5
  "keywords": [
6
6
  "pi-package",