@pasko70/pibo 1.7.10 → 1.7.12

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.
Files changed (41) hide show
  1. package/dist/apps/chat/chat-request-normalizers.js +1 -1
  2. package/dist/apps/chat/project-workflow-sessions.js +1 -1
  3. package/dist/apps/chat/ralph-api.js +1 -1
  4. package/dist/apps/chat-ui/assets/{dist-D6ImmtZ3.js → dist--lraqdDn.js} +1 -1
  5. package/dist/apps/chat-ui/assets/{dist-CCOZbHCt.js → dist-6pcjNbPQ.js} +1 -1
  6. package/dist/apps/chat-ui/assets/{dist-Daom6uMW.js → dist-CMFjl7MX.js} +1 -1
  7. package/dist/apps/chat-ui/assets/{dist-DTJJ5-iA.js → dist-COlFTw2x.js} +1 -1
  8. package/dist/apps/chat-ui/assets/{dist-DDQWCHTh.js → dist-CZSEM5So.js} +1 -1
  9. package/dist/apps/chat-ui/assets/{dist-Dgs6LwMW.js → dist-CajLLpaw.js} +1 -1
  10. package/dist/apps/chat-ui/assets/{dist-Bg2k47fY.js → dist-CoCvyd4f.js} +1 -1
  11. package/dist/apps/chat-ui/assets/{dist-BLYDOjGT.js → dist-Dgz3iWay.js} +1 -1
  12. package/dist/apps/chat-ui/assets/{dist-BLQTYmgi.js → dist-IlQbTNzw.js} +1 -1
  13. package/dist/apps/chat-ui/assets/{dist-Bv912fTl.js → dist-YdEosvsr.js} +1 -1
  14. package/dist/apps/chat-ui/assets/{dist-LRUZKp77.js → dist-bLd-NaVv.js} +1 -1
  15. package/dist/apps/chat-ui/assets/{index-BnZ0V5cJ.js → index-HPWlrJwv.js} +3 -3
  16. package/dist/apps/chat-ui/index.html +1 -1
  17. package/dist/apps/chat-vscode-web/assets/{index-CiQZTRZH.js → index-Dge6XEYB.js} +1 -1
  18. package/dist/apps/chat-vscode-web/index.html +1 -1
  19. package/dist/apps/cli-ui/InkSessionApp.js +3 -3
  20. package/dist/apps/cli-ui/cliSessionsCommand.js +1 -1
  21. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  22. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.7.12.vsix +0 -0
  23. package/dist/cli-session/fakeSessionSource.js +2 -2
  24. package/dist/cli.js +1 -1
  25. package/dist/core/routed-session.js +7 -0
  26. package/dist/core/runtime-telemetry.js +5 -5
  27. package/dist/core/runtime.js +22 -0
  28. package/dist/core/session-errors.js +16 -0
  29. package/dist/core/session-router.js +9 -0
  30. package/dist/core/thinking.js +1 -1
  31. package/dist/gateway/cli.js +35 -16
  32. package/dist/gateway/pidfile.js +90 -44
  33. package/dist/gateway/server.js +22 -16
  34. package/dist/gateway/web.js +29 -23
  35. package/dist/providers/openai-gpt56.js +2 -2
  36. package/dist/ralph/cli.js +2 -2
  37. package/dist/ralph/service.js +60 -20
  38. package/dist/ralph/store.js +2 -2
  39. package/dist/session-ui/commandCatalog.js +1 -1
  40. package/dist/tools/guides.js +1 -1
  41. package/package.json +1 -1
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="theme-color" content="#101d22" />
7
7
  <title>Pibo</title>
8
- <script type="module" crossorigin src="/apps/chat-vscode/assets/index-CiQZTRZH.js"></script>
8
+ <script type="module" crossorigin src="/apps/chat-vscode/assets/index-Dge6XEYB.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-B5QK07zO.css">
10
10
  </head>
11
11
  <body>
@@ -699,8 +699,8 @@ async function executeSharedSlashCommand(command, source, state, setState, openS
699
699
  setState((current) => applyCommandResultToState(current, command, result.descriptor, status));
700
700
  return true;
701
701
  }
702
- const THINKING_LEVELS = ["current/default", "off", "minimal", "low", "medium", "high", "xhigh"];
703
- const APPLY_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh"]);
702
+ const THINKING_LEVELS = ["current/default", "off", "minimal", "low", "medium", "high", "xhigh", "max"];
703
+ const APPLY_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
704
704
  async function selectCommandMenuItem(picker, item, source, state, setState, openSession) {
705
705
  if (item.disabled) {
706
706
  setState((current) => ({ ...current, message: item.description ? `${item.label}: ${item.description}` : `${item.label} is unavailable.`, error: undefined }));
@@ -787,7 +787,7 @@ function thinkingPickerState() {
787
787
  function validateThinkingLevel(value) {
788
788
  const level = value.trim().toLowerCase();
789
789
  if (!APPLY_THINKING_LEVELS.has(level))
790
- throw new Error(`Unsupported thinking level "${value}". Use off, minimal, low, medium, high, or xhigh.`);
790
+ throw new Error(`Unsupported thinking level "${value}". Use off, minimal, low, medium, high, xhigh, or max.`);
791
791
  }
792
792
  function modelProviderPickerState(providers) {
793
793
  return {
@@ -148,7 +148,7 @@ class DebugMockCliSessionRouter {
148
148
  const level = typeof params.level === "string" ? params.level : undefined;
149
149
  if (level)
150
150
  return { message: `Thinking level set to ${level}`, level, supported: true, changed: true };
151
- return { action: "show_thinking_menu", items: ["off", "minimal", "low", "medium", "high", "xhigh"].map((value) => ({ id: value, label: value })) };
151
+ return { action: "show_thinking_menu", items: ["off", "minimal", "low", "medium", "high", "xhigh", "max"].map((value) => ({ id: value, label: value })) };
152
152
  }
153
153
  if (event.action === "model") {
154
154
  const model = typeof params.model === "string" ? params.model : undefined;
@@ -343,9 +343,9 @@ export class FakeCliSessionSource {
343
343
  if (!level)
344
344
  return {
345
345
  action: "show_thinking_menu",
346
- items: ["off", "minimal", "low", "medium", "high", "xhigh"].map((value) => ({ id: value, label: value })),
346
+ items: ["off", "minimal", "low", "medium", "high", "xhigh", "max"].map((value) => ({ id: value, label: value })),
347
347
  };
348
- if (!["off", "minimal", "low", "medium", "high", "xhigh"].includes(level))
348
+ if (!["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(level))
349
349
  return {
350
350
  supported: false,
351
351
  unsupportedReason: `Unsupported thinking level ${level}.`,
package/dist/cli.js CHANGED
@@ -338,7 +338,7 @@ export async function runPiboCli(argv = process.argv) {
338
338
  .command("tui:routed")
339
339
  .helpOption("-h, --help", "Display help for command")
340
340
  .option("--show-thinking", "Show routed thinking deltas in the local TUI")
341
- .option("--thinking <level>", "Set routed thinking level: off, minimal, low, medium, high, xhigh", parsePiboThinkingLevel)
341
+ .option("--thinking <level>", "Set routed thinking level: off, minimal, low, medium, high, xhigh, max", parsePiboThinkingLevel)
342
342
  .argument("[profile]")
343
343
  .description("Start the local routed Pibo TUI")
344
344
  .action(async (profile, options) => {
@@ -565,6 +565,7 @@ export class RoutedSession {
565
565
  getStatus() {
566
566
  const enabledTools = this.runtime.session.getActiveToolNames();
567
567
  const thinkingLevel = this.runtime.session.thinkingLevel;
568
+ const settingsManager = this.runtime.session.settingsManager;
568
569
  return {
569
570
  piboSessionId: this.piboSessionId,
570
571
  queuedMessages: this.queue.length,
@@ -576,6 +577,12 @@ export class RoutedSession {
576
577
  disposed: this.disposed,
577
578
  thinkingLevel,
578
579
  fastMode: this.getFastModeResult().mode === "fast",
580
+ ...(settingsManager ? {
581
+ retry: {
582
+ ...settingsManager.getRetrySettings(),
583
+ provider: settingsManager.getProviderRetrySettings(),
584
+ },
585
+ } : {}),
579
586
  };
580
587
  }
581
588
  getContextUsage() {
@@ -68,7 +68,7 @@ export class PiboRuntimeTelemetryRecorder {
68
68
  this.recordToolExecutionFinished(event, context);
69
69
  return;
70
70
  case "session_error":
71
- this.recordTurnTerminal(event, context, "error", "error", event.error);
71
+ this.recordTurnTerminal(event, context, "error", "error", event.error, event.errorDetails?.category ?? event.errorDetails?.errorClass);
72
72
  return;
73
73
  case "execution_result":
74
74
  this.recordExecutionResult(event, context);
@@ -368,13 +368,13 @@ export class PiboRuntimeTelemetryRecorder {
368
368
  return;
369
369
  this.startOrProgressPhase(turn, phaseName, new Date().toISOString(), summary, { updateTurn: true });
370
370
  }
371
- recordTurnTerminal(event, context, status, phaseName, summary) {
371
+ recordTurnTerminal(event, context, status, phaseName, summary, errorCategory) {
372
372
  const turn = this.turnContextForEvent(event.piboSessionId, event.eventId, undefined, context) ?? this.activeTurnContext(event.piboSessionId, context);
373
373
  if (!turn)
374
374
  return;
375
375
  const now = new Date().toISOString();
376
376
  this.finishOpenPhases(turn.turnId, terminalPhaseStatus(status), now);
377
- this.finishActiveProviderRequests(turn.turnId, providerStatusForTurnStatus(status), now, summary);
377
+ this.finishActiveProviderRequests(turn.turnId, providerStatusForTurnStatus(status), now, summary, errorCategory);
378
378
  this.finishActiveToolCalls(turn.turnId, status, now, summary);
379
379
  this.telemetry.upsertPhase({
380
380
  phaseId: phaseId(turn.turnId, phaseName),
@@ -486,7 +486,7 @@ export class PiboRuntimeTelemetryRecorder {
486
486
  normalizedEventCount: request.normalizedEventCount + 1,
487
487
  });
488
488
  }
489
- finishActiveProviderRequests(turnId, status, now, summary) {
489
+ finishActiveProviderRequests(turnId, status, now, summary, errorCategory) {
490
490
  const timeline = this.store?.getTurnTimeline(turnId, { limit: 100 });
491
491
  for (const request of timeline?.providerRequests ?? []) {
492
492
  if (isTerminalProviderStatus(request.status))
@@ -494,7 +494,7 @@ export class PiboRuntimeTelemetryRecorder {
494
494
  this.upsertProviderRequestFromExisting(request, {
495
495
  status,
496
496
  completedAt: now,
497
- errorCategory: status === "error" ? "runtime_error" : undefined,
497
+ errorCategory: status === "error" ? errorCategory ?? "runtime_error" : undefined,
498
498
  errorMessage: status === "error" ? safeSummary(summary) : undefined,
499
499
  });
500
500
  }
@@ -25,6 +25,27 @@ import { PIBO_APP_CONTEXT } from "../app-context.js";
25
25
  import { createRuntimeToolDefinition } from "../tools/runtime/tool.js";
26
26
  import { RuntimeSessionRegistry } from "../tools/runtime/registry.js";
27
27
  import { compactValidationToolResultForContext } from "./test-output-compaction.js";
28
+ function hasOwnRetrySetting(settings, key) {
29
+ return settings !== undefined && settings !== null && Object.prototype.hasOwnProperty.call(settings, key);
30
+ }
31
+ export function applyPiboRuntimeRetryDefaults(settingsManager, defaults) {
32
+ if (!defaults)
33
+ return;
34
+ const globalRetry = settingsManager.getGlobalSettings().retry;
35
+ const projectRetry = settingsManager.getProjectSettings().retry;
36
+ const overrides = {};
37
+ if (!hasOwnRetrySetting(globalRetry, "enabled") && !hasOwnRetrySetting(projectRetry, "enabled") && defaults.enabled !== undefined) {
38
+ overrides.enabled = defaults.enabled;
39
+ }
40
+ if (!hasOwnRetrySetting(globalRetry, "maxRetries") && !hasOwnRetrySetting(projectRetry, "maxRetries") && defaults.maxRetries !== undefined) {
41
+ overrides.maxRetries = defaults.maxRetries;
42
+ }
43
+ if (!hasOwnRetrySetting(globalRetry, "baseDelayMs") && !hasOwnRetrySetting(projectRetry, "baseDelayMs") && defaults.baseDelayMs !== undefined) {
44
+ overrides.baseDelayMs = defaults.baseDelayMs;
45
+ }
46
+ if (Object.keys(overrides).length > 0)
47
+ settingsManager.applyOverrides({ retry: overrides });
48
+ }
28
49
  function resolveProfilePath(cwd, path) {
29
50
  return isAbsolute(path) ? path : resolve(cwd, path);
30
51
  }
@@ -243,6 +264,7 @@ export async function createPiboRuntime(options = {}) {
243
264
  }),
244
265
  },
245
266
  });
267
+ applyPiboRuntimeRetryDefaults(services.settingsManager, options.retryDefaults);
246
268
  registerOpenAiGpt56Models(services.modelRegistry);
247
269
  registerMiniMaxProvider(services.modelRegistry);
248
270
  registerGlmProvider(services.modelRegistry);
@@ -1,3 +1,16 @@
1
+ const PROVIDER_NETWORK_ERROR_MARKERS = [
2
+ "fetch failed",
3
+ "network error",
4
+ "connection error",
5
+ "connection refused",
6
+ "connection lost",
7
+ "connection reset",
8
+ "other side closed",
9
+ "upstream connect",
10
+ "reset before headers",
11
+ "socket hang up",
12
+ "socket connection was closed",
13
+ ];
1
14
  export function classifySessionErrorMessage(message, options = {}) {
2
15
  const normalized = message.toLowerCase();
3
16
  if (normalized.includes("context_length_exceeded") || normalized.includes("context window")) {
@@ -18,6 +31,9 @@ export function classifySessionErrorMessage(message, options = {}) {
18
31
  if (normalized.includes("timeout") || normalized.includes("timed out")) {
19
32
  return { category: "provider_transport", errorClass: "provider_transport", code: "timeout", origin: "provider", retryable: true, userMessage: "The provider request timed out." };
20
33
  }
34
+ if (PROVIDER_NETWORK_ERROR_MARKERS.some((marker) => normalized.includes(marker))) {
35
+ return { category: "provider_transport", errorClass: "provider_transport", code: "network_error", origin: "provider", retryable: true, userMessage: "The provider network connection failed." };
36
+ }
21
37
  if (/\b5\d\d\b/.test(normalized)) {
22
38
  return { category: "provider_server", errorClass: "provider_server", code: "provider_server_error", origin: "provider", retryable: true, userMessage: "The provider returned a server error." };
23
39
  }
@@ -20,6 +20,14 @@ import { withWorkflowSessionKind } from "../sessions/workflow-session-kind.js";
20
20
  import { PiboRuntimeTelemetryRecorder } from "./runtime-telemetry.js";
21
21
  import { createPiboProviderTelemetryExtension } from "./provider-telemetry.js";
22
22
  const DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS = 10 * 60 * 1000;
23
+ export const RALPH_RUNTIME_RETRY_DEFAULTS = {
24
+ enabled: true,
25
+ maxRetries: 7,
26
+ baseDelayMs: 2_000,
27
+ };
28
+ export function resolvePiboSessionRetryDefaults(kind, configured) {
29
+ return configured ?? (kind === "ralph" ? RALPH_RUNTIME_RETRY_DEFAULTS : undefined);
30
+ }
23
31
  export function resolvePiboSessionInitialThinkingLevel(session) {
24
32
  const value = session.metadata?.initialThinkingLevel;
25
33
  return typeof value === "string" && isPiboThinkingLevel(value) ? value : undefined;
@@ -360,6 +368,7 @@ export class PiboSessionRouter {
360
368
  cwd: piboSession.workspace ?? this.options.cwd,
361
369
  persistSession: this.options.persistSession,
362
370
  thinkingLevel: initialThinkingLevel ?? this.options.thinkingLevel,
371
+ retryDefaults: resolvePiboSessionRetryDefaults(piboSession.kind, this.options.retryDefaults),
363
372
  profile: profileForSession(profile, piboSession.piSessionId, parentPiSessionId),
364
373
  extensionFactories: [
365
374
  ...(telemetryExtension ? [telemetryExtension] : []),
@@ -1,4 +1,4 @@
1
- export const PIBO_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
1
+ export const PIBO_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
2
2
  export function isPiboThinkingLevel(value) {
3
3
  return PIBO_THINKING_LEVELS.includes(value);
4
4
  }
@@ -1,5 +1,5 @@
1
1
  import { spawn, execFile } from "node:child_process";
2
- import { existsSync, mkdirSync, openSync, readFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, openSync, readFileSync, readdirSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { promisify } from "node:util";
@@ -87,28 +87,40 @@ function resolveGatewayWebCommand(argv, target) {
87
87
  ];
88
88
  return { command: process.execPath, args };
89
89
  }
90
- function managedGatewayPidPath(target) {
91
- return join(managedGatewayHome(target), "gateway.pid");
90
+ function managedGatewayPidPaths(target) {
91
+ const home = managedGatewayHome(target);
92
+ try {
93
+ const legacy = readdirSync(home)
94
+ .filter((name) => /^gateway-\d+\.pid$/.test(name))
95
+ .map((name) => join(home, name));
96
+ return [join(home, "gateway.pid"), ...legacy];
97
+ }
98
+ catch {
99
+ return [join(home, "gateway.pid")];
100
+ }
92
101
  }
93
102
  function readManagedGatewayPid(target) {
94
- try {
95
- const path = managedGatewayPidPath(target);
96
- if (!existsSync(path))
97
- return undefined;
98
- const pid = Number(readFileSync(path, "utf-8").trim());
99
- if (!Number.isInteger(pid) || pid <= 0)
100
- return undefined;
103
+ for (const path of managedGatewayPidPaths(target)) {
101
104
  try {
102
- process.kill(pid, 0);
103
- return pid;
105
+ if (!existsSync(path))
106
+ continue;
107
+ const pid = Number(readFileSync(path, "utf-8").trim());
108
+ if (!Number.isInteger(pid) || pid <= 0)
109
+ continue;
110
+ try {
111
+ process.kill(pid, 0);
112
+ return pid;
113
+ }
114
+ catch (error) {
115
+ if (error.code === "EPERM")
116
+ return pid;
117
+ }
104
118
  }
105
119
  catch {
106
- return undefined;
120
+ // Try the next current or legacy PID file.
107
121
  }
108
122
  }
109
- catch {
110
- return undefined;
111
- }
123
+ return undefined;
112
124
  }
113
125
  async function waitForTargetGatewayDown(target, maxRetries = 40, intervalMs = 250) {
114
126
  const port = targetPort(target);
@@ -322,6 +334,13 @@ async function runManagedGatewayCommand(target, command, args, argv = process.ar
322
334
  process.exitCode = 1;
323
335
  return true;
324
336
  }
337
+ const existingPid = readManagedGatewayPid(target);
338
+ if (existingPid !== undefined) {
339
+ console.error(`Start blocked: ${managedGatewayHome(target)} is already owned by gateway PID ${existingPid}.`);
340
+ console.error(`The configured status port ${targetPort(target)} is not reachable; check the gateway port configuration instead of starting a second gateway with the same PIBO_HOME.`);
341
+ process.exitCode = 1;
342
+ return true;
343
+ }
325
344
  console.error(`Starting ${target === "web" ? "production" : "dev"} gateway...`);
326
345
  try {
327
346
  await runGatewayManager("start", target, argv);
@@ -1,84 +1,130 @@
1
- import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
- import { piboHomePath } from "../core/pibo-home.js";
3
- function gatewayPidPath(port) {
4
- return piboHomePath(port === undefined ? "gateway.pid" : `gateway-${port}.pid`);
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { getPiboHome, piboHomePath } from "../core/pibo-home.js";
4
+ function gatewayPidPath() {
5
+ return piboHomePath("gateway.pid");
5
6
  }
6
7
  function fallbackGatewayPidPath() {
7
8
  return piboHomePath("gateway-fallback.pid");
8
9
  }
9
- export function readPidFile(port) {
10
+ function legacyGatewayPidPaths() {
11
+ try {
12
+ const home = getPiboHome();
13
+ return readdirSync(home)
14
+ .filter((name) => /^gateway-\d+\.pid$/.test(name))
15
+ .map((name) => join(home, name));
16
+ }
17
+ catch {
18
+ return [];
19
+ }
20
+ }
21
+ function storedPid(path) {
10
22
  try {
11
- const path = gatewayPidPath(port);
12
23
  if (!existsSync(path))
13
24
  return undefined;
14
25
  const pid = parseInt(readFileSync(path, "utf-8").trim(), 10);
15
- if (Number.isNaN(pid))
16
- return undefined;
17
- try {
18
- process.kill(pid, 0);
26
+ return Number.isNaN(pid) ? undefined : pid;
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ function livePid(path) {
33
+ const pid = storedPid(path);
34
+ if (pid === undefined)
35
+ return undefined;
36
+ try {
37
+ process.kill(pid, 0);
38
+ return pid;
39
+ }
40
+ catch (error) {
41
+ if (error.code === "EPERM")
19
42
  return pid;
43
+ return undefined;
44
+ }
45
+ }
46
+ function claimPidFile(path, label) {
47
+ mkdirSync(dirname(path), { recursive: true });
48
+ for (let attempt = 0; attempt < 3; attempt += 1) {
49
+ try {
50
+ writeFileSync(path, String(process.pid), { encoding: "utf-8", flag: "wx" });
51
+ return;
20
52
  }
21
- catch {
22
- return undefined;
53
+ catch (error) {
54
+ if (error.code !== "EEXIST")
55
+ throw error;
56
+ const existingPid = livePid(path);
57
+ if (existingPid === process.pid)
58
+ return;
59
+ if (existingPid !== undefined)
60
+ throw new Error(`${label} already running (PID ${existingPid})`);
61
+ try {
62
+ unlinkSync(path);
63
+ }
64
+ catch (unlinkError) {
65
+ if (unlinkError.code !== "ENOENT")
66
+ throw unlinkError;
67
+ }
23
68
  }
24
69
  }
70
+ throw new Error(`Unable to claim ${label.toLowerCase()} PID file`);
71
+ }
72
+ function clearPidFileIfOwned(path) {
73
+ try {
74
+ if (storedPid(path) === process.pid)
75
+ unlinkSync(path);
76
+ }
25
77
  catch {
26
- return undefined;
78
+ // ignore
27
79
  }
28
80
  }
29
- export function clearPidFile(port) {
81
+ export function readPidFile() {
82
+ return livePid(gatewayPidPath()) ?? legacyGatewayPidPaths().map(livePid).find((pid) => pid !== undefined);
83
+ }
84
+ export function clearPidFile() {
30
85
  try {
31
- const path = gatewayPidPath(port);
32
- if (existsSync(path)) {
86
+ const path = gatewayPidPath();
87
+ if (existsSync(path))
33
88
  unlinkSync(path);
34
- }
35
89
  }
36
90
  catch {
37
91
  // ignore
38
92
  }
39
93
  }
40
- export function writeGatewayPid(port) {
41
- const existingPid = readPidFile(port);
94
+ export function releaseGatewayPid() {
95
+ clearPidFileIfOwned(gatewayPidPath());
96
+ }
97
+ export function writeGatewayPid() {
98
+ const existingPid = readPidFile();
42
99
  if (existingPid !== undefined && existingPid !== process.pid) {
43
100
  throw new Error(`Gateway already running (PID ${existingPid})`);
44
101
  }
45
- writeFileSync(gatewayPidPath(port), String(process.pid), "utf-8");
46
- }
47
- export function readFallbackPidFile() {
48
- try {
49
- const path = fallbackGatewayPidPath();
50
- if (!existsSync(path))
51
- return undefined;
52
- const pid = parseInt(readFileSync(path, "utf-8").trim(), 10);
53
- if (Number.isNaN(pid))
54
- return undefined;
102
+ for (const path of legacyGatewayPidPaths()) {
103
+ if (livePid(path) !== undefined)
104
+ continue;
55
105
  try {
56
- process.kill(pid, 0);
57
- return pid;
58
- }
59
- catch {
60
- return undefined;
106
+ unlinkSync(path);
61
107
  }
108
+ catch { }
62
109
  }
63
- catch {
64
- return undefined;
65
- }
110
+ claimPidFile(gatewayPidPath(), "Gateway");
111
+ }
112
+ export function readFallbackPidFile() {
113
+ return livePid(fallbackGatewayPidPath());
66
114
  }
67
115
  export function clearFallbackPidFile() {
68
116
  try {
69
117
  const path = fallbackGatewayPidPath();
70
- if (existsSync(path)) {
118
+ if (existsSync(path))
71
119
  unlinkSync(path);
72
- }
73
120
  }
74
121
  catch {
75
122
  // ignore
76
123
  }
77
124
  }
125
+ export function releaseFallbackGatewayPid() {
126
+ clearPidFileIfOwned(fallbackGatewayPidPath());
127
+ }
78
128
  export function writeFallbackGatewayPid() {
79
- const existingPid = readFallbackPidFile();
80
- if (existingPid !== undefined && existingPid !== process.pid) {
81
- throw new Error(`Fallback gateway already running (PID ${existingPid})`);
82
- }
83
- writeFileSync(fallbackGatewayPidPath(), String(process.pid), "utf-8");
129
+ claimPidFile(fallbackGatewayPidPath(), "Fallback gateway");
84
130
  }
@@ -3,7 +3,7 @@ import { createDefaultPiboPluginRegistry, createPiboProfileFromRegistryOrDefault
3
3
  import { PiboSessionRouter } from "../core/session-router.js";
4
4
  import { loadPiboModelDefaults, selectRequestedModelProfile } from "../core/model-defaults.js";
5
5
  import { DEFAULT_GATEWAY_HOST, DEFAULT_GATEWAY_PORT, encodeFrame, errorResponse, isGatewayRequestFrame, isGatewaySubscribeFrame, } from "./protocol.js";
6
- import { clearFallbackPidFile, clearPidFile, writeFallbackGatewayPid, writeGatewayPid } from "./pidfile.js";
6
+ import { releaseFallbackGatewayPid, releaseGatewayPid, writeFallbackGatewayPid, writeGatewayPid } from "./pidfile.js";
7
7
  const DEFAULT_MAX_BACKPRESSURE_FRAMES = 1_000;
8
8
  const DEFAULT_MAX_BACKPRESSURE_BYTES = 4 * 1024 * 1024;
9
9
  function parseJsonLine(line) {
@@ -342,31 +342,37 @@ export class PiboGatewayServer {
342
342
  }
343
343
  }
344
344
  export async function runGatewayServer(options = {}) {
345
- const server = new PiboGatewayServer(options);
346
- await server.start();
345
+ const fallbackMode = process.env.PIBO_FALLBACK_MODE === "1";
347
346
  try {
348
- if (process.env.PIBO_FALLBACK_MODE === "1") {
347
+ if (fallbackMode)
349
348
  writeFallbackGatewayPid();
350
- }
351
- else {
349
+ else
352
350
  writeGatewayPid();
353
- }
354
351
  }
355
- catch (err) {
356
- console.error(err instanceof Error ? err.message : String(err));
357
- await server.stop();
358
- process.exit(1);
352
+ catch (error) {
353
+ console.error(error instanceof Error ? error.message : String(error));
354
+ process.exitCode = 1;
355
+ return;
356
+ }
357
+ const releasePid = fallbackMode ? releaseFallbackGatewayPid : releaseGatewayPid;
358
+ let server;
359
+ try {
360
+ server = new PiboGatewayServer(options);
361
+ await server.start();
362
+ }
363
+ catch (error) {
364
+ releasePid();
365
+ throw error;
359
366
  }
360
367
  const host = options.host ?? DEFAULT_GATEWAY_HOST;
361
368
  const port = options.port ?? DEFAULT_GATEWAY_PORT;
362
369
  console.error(`pibo gateway listening on ${host}:${port}`);
363
370
  const stop = async () => {
364
- await server.stop();
365
- if (process.env.PIBO_FALLBACK_MODE === "1") {
366
- clearFallbackPidFile();
371
+ try {
372
+ await server.stop();
367
373
  }
368
- else {
369
- clearPidFile();
374
+ finally {
375
+ releasePid();
370
376
  }
371
377
  };
372
378
  process.once("SIGINT", () => {
@@ -13,7 +13,7 @@ import { createPiboWebHostPlugin } from "../plugins/web.js";
13
13
  import { DEFAULT_WEB_CHANNEL_HOST, DEFAULT_WEB_CHANNEL_PORT } from "../web/channel.js";
14
14
  import { loadPiboConfig } from "../config/config.js";
15
15
  import { PiboGatewayServer } from "./server.js";
16
- import { clearFallbackPidFile, clearPidFile, writeFallbackGatewayPid, writeGatewayPid } from "./pidfile.js";
16
+ import { releaseFallbackGatewayPid, releaseGatewayPid, writeFallbackGatewayPid, writeGatewayPid } from "./pidfile.js";
17
17
  const PUBLIC_WEB_CHANNEL_HOST = "0.0.0.0";
18
18
  const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
19
19
  function isComputeWorkerRuntime() {
@@ -175,37 +175,43 @@ function createChatAppURL(options, host, port) {
175
175
  return `http://${host}:${port}/apps/chat`;
176
176
  }
177
177
  export async function runWebGatewayServer(options = {}) {
178
- const resolvedOptions = resolveWebGatewayServerOptions(options);
179
- const pluginRegistry = resolvedOptions.pluginRegistry ?? createWebPiboPluginRegistry(resolvedOptions);
180
- const pidFilePort = resolvedOptions.port;
181
- const server = new PiboGatewayServer({
182
- ...resolvedOptions,
183
- pluginRegistry,
184
- });
185
- await server.start();
178
+ const fallbackMode = process.env.PIBO_FALLBACK_MODE === "1";
186
179
  try {
187
- if (process.env.PIBO_FALLBACK_MODE === "1") {
180
+ if (fallbackMode)
188
181
  writeFallbackGatewayPid();
189
- }
190
- else {
191
- writeGatewayPid(pidFilePort);
192
- }
182
+ else
183
+ writeGatewayPid();
193
184
  }
194
- catch (err) {
195
- console.error(err instanceof Error ? err.message : String(err));
196
- await server.stop();
197
- process.exit(1);
185
+ catch (error) {
186
+ console.error(error instanceof Error ? error.message : String(error));
187
+ process.exitCode = 1;
188
+ return;
189
+ }
190
+ const releasePid = fallbackMode ? releaseFallbackGatewayPid : releaseGatewayPid;
191
+ let resolvedOptions;
192
+ let server;
193
+ try {
194
+ resolvedOptions = resolveWebGatewayServerOptions(options);
195
+ const pluginRegistry = resolvedOptions.pluginRegistry ?? createWebPiboPluginRegistry(resolvedOptions);
196
+ server = new PiboGatewayServer({
197
+ ...resolvedOptions,
198
+ pluginRegistry,
199
+ });
200
+ await server.start();
201
+ }
202
+ catch (error) {
203
+ releasePid();
204
+ throw error;
198
205
  }
199
206
  const host = resolvedOptions.web?.host ?? DEFAULT_WEB_CHANNEL_HOST;
200
207
  const port = resolvedOptions.web?.port ?? DEFAULT_WEB_CHANNEL_PORT;
201
208
  console.error(`pibo chat app available at ${createChatAppURL(resolvedOptions, host, port)}`);
202
209
  const stop = async () => {
203
- await server.stop();
204
- if (process.env.PIBO_FALLBACK_MODE === "1") {
205
- clearFallbackPidFile();
210
+ try {
211
+ await server.stop();
206
212
  }
207
- else {
208
- clearPidFile(pidFilePort);
213
+ finally {
214
+ releasePid();
209
215
  }
210
216
  };
211
217
  process.once("SIGINT", () => {
@@ -45,7 +45,7 @@ export function buildOpenAiGpt56Models(baseModels = getBuiltInOpenAiModels()) {
45
45
  api: OPENAI_RESPONSES_API,
46
46
  baseUrl: OPENAI_BASE_URL,
47
47
  contextWindow: OPENAI_GPT_56_CONTEXT_WINDOW,
48
- thinkingLevelMap: { off: null, xhigh: "xhigh" },
48
+ thinkingLevelMap: { off: null, xhigh: "xhigh", max: "max" },
49
49
  baseModels,
50
50
  modelCost: (model) => model.cost,
51
51
  });
@@ -56,7 +56,7 @@ export function buildOpenAiCodexGpt56Models(baseModels = getBuiltInOpenAiCodexMo
56
56
  api: OPENAI_CODEX_RESPONSES_API,
57
57
  baseUrl: OPENAI_CODEX_BASE_URL,
58
58
  contextWindow: OPENAI_CODEX_GPT_56_CONTEXT_WINDOW,
59
- thinkingLevelMap: { xhigh: "xhigh", minimal: "low" },
59
+ thinkingLevelMap: { xhigh: "xhigh", max: "max", minimal: "low" },
60
60
  baseModels,
61
61
  modelCost: (model) => ({ ...model.cost, cacheWrite: 0 }),
62
62
  });