@centerforagenticai/pi-multi-account 0.1.6 → 0.1.7

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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.6",
2
+ "version": "0.1.7",
3
3
  "description": "Global Anthropic and OpenAI Codex multi-account OAuth routing for Pi.",
4
4
  "type": "module",
5
5
  "bin": {
@@ -59,11 +59,11 @@
59
59
  "undici": "^8.10.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@earendil-works/pi-ai": "^1.0.2",
63
- "@earendil-works/pi-coding-agent": "^1.0.2",
64
- "@earendil-works/pi-tui": "^1.0.2",
62
+ "@earendil-works/pi-ai": "^1.0.4",
63
+ "@earendil-works/pi-coding-agent": "^1.0.4",
64
+ "@earendil-works/pi-tui": "^1.0.4",
65
65
  "@types/node": "^24.0.0",
66
- "typebox": "1.3.34",
66
+ "typebox": "1.3.35",
67
67
  "typescript": "^5.9.0",
68
68
  "vitest": "^4.1.11"
69
69
  },
@@ -456,6 +456,14 @@ const VISIBLE_FAILURE_CAUSES: Readonly<Record<LogicalFailureEvidence["category"]
456
456
  export const HOST_STALE_INSTALL_MESSAGE =
457
457
  "pi's installed files changed while this session was running; restart the pi session to load the current install.";
458
458
 
459
+ /**
460
+ * Public text when the missing module is a bare package rather than one of
461
+ * pi's own bundle files: the dependency is absent from the install, so a
462
+ * restart loads the same broken tree and only a reinstall helps.
463
+ */
464
+ export const HOST_MISSING_DEPENDENCY_MESSAGE =
465
+ "pi could not load a package it depends on; reinstall pi (or the extension that needs the package), then restart the pi session.";
466
+
459
467
  /**
460
468
  * Node's own phrasing, anchored at the start: pi-ai `lazyStream` keeps
461
469
  * `error.message` unprefixed. An unanchored match would misread a provider or
@@ -491,7 +499,7 @@ function hasStructuredFailureEvidence(failure: ProviderFailureSignal | undefined
491
499
  * Bounded diagnostic cause for a stale install: the Node error code and the
492
500
  * missing file's base name. Directories are dropped so no local path is kept.
493
501
  */
494
- function hostStaleInstallCause(error: unknown): string {
502
+ export function hostLoadFault(error: unknown): { cause: string; missingDependency: boolean } {
495
503
  let text = "";
496
504
  let code: unknown;
497
505
  try {
@@ -504,9 +512,25 @@ function hostStaleInstallCause(error: unknown): string {
504
512
  } catch {
505
513
  // Fall through with whatever was read.
506
514
  }
507
- const file = /['"]?([^'"\s]*\.(?:m?js|cjs|json|node))['"]?/.exec(text)?.[1]?.split(/[\\/]/).at(-1);
508
515
  const kind = code === "ERR_MODULE_NOT_FOUND" || /ERR_MODULE_NOT_FOUND|Cannot find/.test(text) ? "ERR_MODULE_NOT_FOUND" : "dynamic-import-failed";
509
- return file === undefined || file.length === 0 ? kind : `${kind} ${file.slice(0, 120)}`;
516
+ // Node names the missing specifier first ("Cannot find package 'x' imported
517
+ // from /dir/importer.js"); the importer is not what is missing.
518
+ // Node does not escape quotes, so a path may contain one: read up to the
519
+ // quote that ends the specifier (before " imported from", a CJS
520
+ // ". Please verify…"/"Require stack" line, or the end of the text).
521
+ const specifier = /Cannot find (?:module|package) '(.+?)'(?=$| imported from |\.\s|\r?\n)/.exec(text)?.[1];
522
+ if (specifier !== undefined && specifier.length > 0) {
523
+ if (!/^(?:\.{1,2}[\\/]|[\\/]|file:|[A-Za-z]:[\\/])/.test(specifier)) {
524
+ // A bare specifier names a package (keep "@scope/name" or "name").
525
+ const parts = specifier.split("/");
526
+ const name = (specifier.startsWith("@") ? parts.slice(0, 2) : parts.slice(0, 1)).join("/");
527
+ return { cause: `${kind} package ${name.slice(0, 120)}`, missingDependency: true };
528
+ }
529
+ const base = specifier.split(/[\\/]/).at(-1) ?? "";
530
+ return { cause: base.length === 0 ? kind : `${kind} ${base.slice(0, 120)}`, missingDependency: false };
531
+ }
532
+ const file = /['"]?([^'"\s]*\.(?:m?js|cjs|json|node))['"]?/.exec(text)?.[1]?.split(/[\\/]/).at(-1);
533
+ return { cause: file === undefined || file.length === 0 ? kind : `${kind} ${file.slice(0, 120)}`, missingDependency: false };
510
534
  }
511
535
 
512
536
  type SetupFailureDisposition = "context-overflow" | "retryable" | "host-final";
@@ -1167,13 +1191,15 @@ export function createLogicalProvider(
1167
1191
  account: LogicalPhysicalAccount,
1168
1192
  cause: unknown,
1169
1193
  ): void => {
1194
+ const fault = hostLoadFault(cause);
1170
1195
  box.hostStaleInstall = true;
1196
+ box.hostMissingDependency = fault.missingDependency;
1171
1197
  box.overflow = false;
1172
1198
  box.preStartRetryable = false;
1173
1199
  try {
1174
1200
  deps.onDiagnostic?.(
1175
1201
  `logical dispatch for ${account.providerId} failed loading host code (host_stale_install: ` +
1176
- `${hostStaleInstallCause(cause)}); restart the pi session`,
1202
+ `${fault.cause}); ${fault.missingDependency ? "reinstall the missing package" : "restart the pi session"}`,
1177
1203
  );
1178
1204
  } catch {
1179
1205
  // A diagnostic sink failure cannot replace a provider result.
@@ -1247,6 +1273,8 @@ export function createLogicalProvider(
1247
1273
  preStartRetryable: boolean;
1248
1274
  /** Setup failed because this pi process could not load its own code. */
1249
1275
  hostStaleInstall?: boolean;
1276
+ /** The missing module is a bare package, so a reinstall, not a restart, helps. */
1277
+ hostMissingDependency?: boolean;
1250
1278
  /** Whether the caller or provider shutdown aborted this physical request. */
1251
1279
  cancelled?: () => boolean;
1252
1280
  /**
@@ -1283,7 +1311,10 @@ export function createLogicalProvider(
1283
1311
  let sawTerminal = false;
1284
1312
  // Whether any event other than a leading `start` arrived. A `start`
1285
1313
  // only reports that response headers arrived, so a failure after it is
1286
- // still the stream's first real event.
1314
+ // still the stream's first real event. Every other non-terminal event
1315
+ // is output (it also sets `box.sawOutput`), and a terminal ends the
1316
+ // loop, so on the thrown path `!sawEvent` means "no content yet": this
1317
+ // is the signal the setup-only classifications key on.
1287
1318
  let sawEvent = false;
1288
1319
  const recordFailureOnce = (error: unknown): HostRetryCooldownReceipt => {
1289
1320
  if (box.hostStaleInstall === true) box.receipt ??= hostFaultReceipt();
@@ -1779,7 +1810,7 @@ export function createLogicalProvider(
1779
1810
  return {
1780
1811
  ...syntheticErrorMessage(
1781
1812
  modelId,
1782
- `${stale ? HOST_STALE_INSTALL_MESSAGE : errorMessage} ${evidence}`,
1813
+ `${stale ? (box?.hostMissingDependency === true ? HOST_MISSING_DEPENDENCY_MESSAGE : HOST_STALE_INSTALL_MESSAGE) : errorMessage} ${evidence}`,
1783
1814
  physical === undefined ? undefined : projectTerminalUsage(physical),
1784
1815
  physical === undefined ? undefined : finiteNonNegative(physical.timestamp),
1785
1816
  ),