@pasko70/pibo 3.2.3 → 3.2.5

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 (70) hide show
  1. package/dist/agent-runtime/auth.js +3 -5
  2. package/dist/agent-runtime/portable-history.js +1 -1
  3. package/dist/agent-runtimes/codex-native/client.js +2 -5
  4. package/dist/agent-runtimes/codex-native/redaction.js +3 -27
  5. package/dist/agent-runtimes/omp/client.js +2 -4
  6. package/dist/agent-runtimes/pi/adapter.js +1 -0
  7. package/dist/agent-runtimes/pi/runtime.js +1 -1
  8. package/dist/apps/chat/agent-store.js +1 -1
  9. package/dist/apps/chat/chat-files.js +3 -1
  10. package/dist/apps/chat/chat-request-normalizers.js +1 -1
  11. package/dist/apps/chat/chat-user-skill-routes.js +7 -0
  12. package/dist/apps/chat/data/project-service.js +49 -1
  13. package/dist/apps/chat/data/session-query-service.js +46 -6
  14. package/dist/apps/chat/loop-api.js +10 -1
  15. package/dist/apps/chat/ralph-api.js +10 -1
  16. package/dist/apps/chat/web-app.js +22 -5
  17. package/dist/apps/chat-ui/assets/{dist-CTsyojIh.js → dist-CTC-_3l0.js} +1 -1
  18. package/dist/apps/chat-ui/assets/{dist-BHio-Mzx.js → dist-CftLEFd5.js} +1 -1
  19. package/dist/apps/chat-ui/assets/{dist-DJ6Bhcuk.js → dist-Dq27Wt2q.js} +1 -1
  20. package/dist/apps/chat-ui/assets/{dist-CPKEoSPK.js → dist-Y4fHV5-Z.js} +1 -1
  21. package/dist/apps/chat-ui/assets/{dist-DNHFRMCk.js → dist-dGF-PDGY.js} +1 -1
  22. package/dist/apps/chat-ui/assets/index-C_P0gXvJ.js +228 -0
  23. package/dist/apps/chat-ui/assets/{index-Dc-6Xupt.css → index-DUGVw259.css} +1 -1
  24. package/dist/apps/chat-ui/index.html +2 -2
  25. package/dist/apps/chat-vscode-web/assets/{index-B1TuQNY5.js → index-B5X0NaRo.js} +4 -4
  26. package/dist/apps/chat-vscode-web/index.html +1 -1
  27. package/dist/apps/cli-ui/InkSessionApp.js +38 -28
  28. package/dist/auth/cli.js +4 -6
  29. package/dist/auth/machine-keys.js +20 -3
  30. package/dist/cli-session/localSessionSource.js +2 -4
  31. package/dist/compute/cli.js +16 -13
  32. package/dist/compute/docker.js +96 -48
  33. package/dist/core/base-prompt.js +27 -5
  34. package/dist/core/context-build.js +5 -15
  35. package/dist/core/sensitive-data-redaction.js +2 -2
  36. package/dist/core/session-router.js +15 -4
  37. package/dist/cron/cli.js +42 -10
  38. package/dist/cron/store.js +1 -1
  39. package/dist/data/cli.js +15 -7
  40. package/dist/data/payload-store.js +55 -7
  41. package/dist/data/schema.js +65 -23
  42. package/dist/debug/index.js +5 -0
  43. package/dist/debug/pty.js +45 -10
  44. package/dist/gateway/cli.js +18 -2
  45. package/dist/gateway/server.js +1 -0
  46. package/dist/loops/service.js +32 -16
  47. package/dist/mcp/runtime-session.js +2 -1
  48. package/dist/pi-packages/cli.js +5 -0
  49. package/dist/previews/cli.js +2 -6
  50. package/dist/previews/config.js +10 -0
  51. package/dist/previews/manager.js +2 -9
  52. package/dist/ralph/service.js +21 -10
  53. package/dist/session-ui/statusViewModel.js +2 -4
  54. package/dist/sessions/pibo-data-store.js +18 -0
  55. package/dist/sessions/store.js +9 -0
  56. package/dist/setup/cli.js +14 -7
  57. package/dist/setup/installation-profiles.js +20 -7
  58. package/dist/shared/trace-event-projection.js +32 -1
  59. package/dist/subagents/tool.js +3 -3
  60. package/dist/tools/browser-use-leases.js +11 -3
  61. package/dist/tools/index.js +0 -1
  62. package/dist/tools/mcp-bridge.js +1 -1
  63. package/dist/tools/runtime/node-worker-source.js +39 -5
  64. package/dist/tools/runtime/python-backend.js +6 -2
  65. package/dist/tools/runtime/registry.js +38 -6
  66. package/dist/vscode/cli.js +1 -2
  67. package/dist/vscode/install.js +21 -14
  68. package/npm-shrinkwrap.json +2 -2
  69. package/package.json +3 -1
  70. package/dist/apps/chat-ui/assets/index-Y6jYzsaC.js +0 -228
@@ -20,6 +20,9 @@ class LoopRunTimeoutError extends Error {
20
20
  this.name = 'LoopRunTimeoutError';
21
21
  }
22
22
  }
23
+ export class PiboLoopCapacityError extends Error {
24
+ constructor() { super('Loop run capacity is currently full'); this.name = 'PiboLoopCapacityError'; }
25
+ }
23
26
  class LoopContinuationInvalidatedError extends Error {
24
27
  constructor(message) { super(message); this.name = 'LoopContinuationInvalidatedError'; }
25
28
  }
@@ -84,6 +87,7 @@ export class PiboLoopService {
84
87
  random;
85
88
  timer;
86
89
  activeRuns = 0;
90
+ admissionTail = Promise.resolve();
87
91
  activeRunWork = new Set();
88
92
  pendingRunStarts = 0;
89
93
  schedulerWork;
@@ -143,14 +147,21 @@ export class PiboLoopService {
143
147
  return undefined;
144
148
  this.pendingRunStarts += 1;
145
149
  try {
146
- const job = this.store.updateJob(id, { enabled: true });
147
- if (!job)
148
- return undefined;
149
- const reserved = await this.reserveAfterBeforeRunEvaluation(job);
150
- if (!reserved)
151
- return undefined;
152
- this.launchReservedRun(reserved.job, reserved.run);
153
- return reserved.run;
150
+ return await this.withAdmissionLock(async () => {
151
+ const existing = this.store.getJob(id);
152
+ if (!existing || existing.state.runningAt)
153
+ return undefined;
154
+ if (this.activeRuns >= this.maxConcurrentRuns)
155
+ throw new PiboLoopCapacityError();
156
+ const job = this.store.updateJob(id, { enabled: true });
157
+ if (!job)
158
+ return undefined;
159
+ const reserved = await this.reserveAfterBeforeRunEvaluation(job);
160
+ if (!reserved)
161
+ return undefined;
162
+ this.launchReservedRun(reserved.job, reserved.run);
163
+ return reserved.run;
164
+ });
154
165
  }
155
166
  finally {
156
167
  this.pendingRunStarts -= 1;
@@ -303,14 +314,13 @@ export class PiboLoopService {
303
314
  async tick() { if (this.stopped)
304
315
  return; try {
305
316
  await this.abortCancelRequestedJobs();
306
- const capacity = this.maxConcurrentRuns - this.activeRuns;
307
- if (capacity > 0) {
308
- const jobs = this.store.listJobs({ includeDisabled: false }).filter((job) => !job.state.runningAt).slice(0, capacity);
309
- for (const job of jobs) {
310
- const reserved = await this.reserveAfterBeforeRunEvaluation(job);
311
- if (reserved)
312
- this.launchReservedRun(reserved.job, reserved.run);
313
- }
317
+ const jobs = this.store.listJobs({ includeDisabled: false }).filter((job) => !job.state.runningAt);
318
+ for (const job of jobs) {
319
+ const admitted = await this.withAdmissionLock(async () => { if (this.activeRuns >= this.maxConcurrentRuns)
320
+ return false; const reserved = await this.reserveAfterBeforeRunEvaluation(job); if (!reserved)
321
+ return true; this.launchReservedRun(reserved.job, reserved.run); return true; });
322
+ if (!admitted)
323
+ break;
314
324
  }
315
325
  }
316
326
  catch (error) {
@@ -319,6 +329,12 @@ export class PiboLoopService {
319
329
  finally {
320
330
  this.arm();
321
331
  } }
332
+ async withAdmissionLock(action) { const previous = this.admissionTail; let release; this.admissionTail = new Promise((resolve) => { release = resolve; }); await previous; try {
333
+ return await action();
334
+ }
335
+ finally {
336
+ release();
337
+ } }
322
338
  async reserveAfterBeforeRunEvaluation(job) {
323
339
  const fresh = this.store.getJob(job.id) ?? job;
324
340
  const now = this.now();
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
+ import { redactSensitiveText } from "../core/sensitive-data-redaction.js";
3
4
  import { getDefaultEnvironment, StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4
5
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
6
  import { filterTools, isHttpServer, } from "./config.js";
@@ -94,7 +95,7 @@ export function redactMcpRuntimeError(error, secretValues = []) {
94
95
  if (value)
95
96
  message = message.split(value).join("[REDACTED]");
96
97
  }
97
- return message.replace(/Bearer\s+\S+/gi, "Bearer [REDACTED]");
98
+ return redactSensitiveText(message).replaceAll("[redacted]", "[REDACTED]");
98
99
  }
99
100
  export async function verifyPiboMcpServer(serverName, config, options) {
100
101
  const client = new Client({ name: "pibo-runtime-resource-verifier", version: "1.0.0" }, { capabilities: {} });
@@ -51,6 +51,7 @@ export async function runPiPackagesCli(argv = process.argv) {
51
51
  console.log("No Pi packages registered.");
52
52
  return;
53
53
  }
54
+ let hasErrors = false;
54
55
  for (const pkg of packages) {
55
56
  const diagnostics = [];
56
57
  try {
@@ -68,11 +69,15 @@ export async function runPiPackagesCli(argv = process.argv) {
68
69
  }
69
70
  }
70
71
  const status = diagnostics.length || pkg.installStatus === "error" || pkg.installStatus === "missing" ? "error" : "ok";
72
+ if (status === "error")
73
+ hasErrors = true;
71
74
  console.log(`${pkg.name}\t${status}\t${pkg.installSpec}`);
72
75
  for (const diagnostic of [...pkg.diagnostics.map((item) => item.message), ...diagnostics]) {
73
76
  console.log(` ${diagnostic}`);
74
77
  }
75
78
  }
79
+ if (hasErrors)
80
+ process.exitCode = 1;
76
81
  });
77
82
  await program.parseAsync(argv);
78
83
  }
@@ -1,9 +1,8 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { resolve } from "node:path";
3
3
  import { Command } from "commander";
4
- import { sanitizePreviewServerSettings } from "../core/preview-server-settings.js";
5
4
  import { createDefaultPiboDataSessionStore } from "../sessions/pibo-data-store.js";
6
- import { DEFAULT_PREVIEW_TTL_MINUTES, loadPreviewConfig, previewPublicURL, requirePreviewBaseURL } from "./config.js";
5
+ import { DEFAULT_PREVIEW_TTL_MINUTES, loadEffectivePreviewServerSettings, loadPreviewConfig, previewPublicURL, requirePreviewBaseURL } from "./config.js";
7
6
  import { reconcileManagedPreviews, startManagedPreview, stopManagedPreview, validatePreviewStartCommand, } from "./manager.js";
8
7
  import { findPreviewTargetProcess, isPiboControlProcess, isPiboYieldedProcess, isPreviewTargetProcessCurrent, probePreviewTarget, validatePreviewPort, } from "./network.js";
9
8
  import { createDefaultPreviewStore, previewExposureState } from "./store.js";
@@ -264,10 +263,7 @@ export async function runPreviewCli(argv = process.argv) {
264
263
  const baseURL = requirePreviewBaseURL();
265
264
  if (!id) {
266
265
  const config = loadPreviewConfig();
267
- const serverLimits = sanitizePreviewServerSettings({
268
- maxRunningServers: config.maxRunningServers,
269
- autoStopMinutes: config.autoStopMinutes,
270
- });
266
+ const serverLimits = loadEffectivePreviewServerSettings();
271
267
  const store = createDefaultPreviewStore();
272
268
  let diagnostics;
273
269
  try {
@@ -1,4 +1,6 @@
1
1
  import { loadPiboConfig } from "../config/config.js";
2
+ import { sanitizePreviewServerSettings } from "../core/preview-server-settings.js";
3
+ import { loadPiboUserSettings } from "../core/user-settings.js";
2
4
  import { parsePreviewBaseURL } from "./base-url.js";
3
5
  export const DEFAULT_PREVIEW_TTL_MINUTES = 8 * 60;
4
6
  export const DEFAULT_PREVIEW_TICKET_TTL_SECONDS = 60;
@@ -6,6 +8,14 @@ export const DEFAULT_PREVIEW_SESSION_TTL_MINUTES = 8 * 60;
6
8
  export function loadPreviewConfig() {
7
9
  return loadPiboConfig().preview ?? {};
8
10
  }
11
+ export function loadEffectivePreviewServerSettings() {
12
+ const config = loadPreviewConfig();
13
+ const userSettings = loadPiboUserSettings().previewServers;
14
+ return sanitizePreviewServerSettings({
15
+ maxRunningServers: config.maxRunningServers ?? userSettings.maxRunningServers,
16
+ autoStopMinutes: config.autoStopMinutes ?? userSettings.autoStopMinutes,
17
+ });
18
+ }
9
19
  export function requirePreviewBaseURL(value = loadPreviewConfig().baseURL) {
10
20
  if (!value)
11
21
  throw new Error("preview.baseURL is required. Set it with `pibo config set preview.baseURL https://preview.example.com`.");
@@ -4,8 +4,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { basename } from "node:path";
6
6
  import { promisify } from "node:util";
7
- import { sanitizePreviewServerSettings } from "../core/preview-server-settings.js";
8
- import { loadPreviewConfig } from "./config.js";
7
+ import { loadEffectivePreviewServerSettings } from "./config.js";
9
8
  import { findPreviewTargetProcess, isPreviewTargetProcessCurrent, previewProcessStartTicks, probePreviewTarget, } from "./network.js";
10
9
  import { previewExposureState } from "./store.js";
11
10
  const execFileAsync = promisify(execFile);
@@ -25,13 +24,7 @@ export function validatePreviewStartCommand(value) {
25
24
  }
26
25
  export async function startManagedPreview(store, id, options = {}) {
27
26
  const controller = options.controller ?? createDefaultPreviewProcessController();
28
- const settings = options.settings ?? (() => {
29
- const previewConfig = loadPreviewConfig();
30
- return sanitizePreviewServerSettings({
31
- maxRunningServers: previewConfig.maxRunningServers,
32
- autoStopMinutes: previewConfig.autoStopMinutes,
33
- });
34
- })();
27
+ const settings = options.settings ?? loadEffectivePreviewServerSettings();
35
28
  const now = options.now?.() ?? new Date();
36
29
  await reconcileManagedPreviews(store, { ...options, controller, settings, now: () => now });
37
30
  let current = store.requireExposure(id);
@@ -10,6 +10,9 @@ import { createDefaultPiboRalphStore } from './store.js';
10
10
  import { createBuiltInRalphStopConditions, evaluateRalphStopPolicy } from './stopping.js';
11
11
  const CHAT_WEB_CHANNEL = 'pibo.chat-web';
12
12
  function errorMessage(error) { return error instanceof Error ? error.message : String(error); }
13
+ export class PiboRalphCapacityError extends Error {
14
+ constructor() { super('Ralph run capacity is currently full'); this.name = 'PiboRalphCapacityError'; }
15
+ }
13
16
  class RalphRunTimeoutError extends Error {
14
17
  abortFailed;
15
18
  constructor(message, abortFailed = false) {
@@ -46,6 +49,7 @@ export class PiboRalphService {
46
49
  runTimeoutMs;
47
50
  timer;
48
51
  activeRuns = 0;
52
+ admissionTail = Promise.resolve();
49
53
  stopped = true;
50
54
  cancelledRuns = new Set();
51
55
  unsubscribeProductEvents;
@@ -63,9 +67,11 @@ export class PiboRalphService {
63
67
  stop() { this.stopped = true; if (this.timer)
64
68
  clearTimeout(this.timer); this.timer = undefined; this.unsubscribeProductEvents?.(); this.unsubscribeProductEvents = undefined; this.dataStore.close(); this.store.close(); }
65
69
  status() { return { enabled: !this.stopped, ...this.store.status() }; }
66
- async startJob(id) { const job = this.store.updateJob(id, { enabled: true }); if (!job)
70
+ async startJob(id) { return await this.withAdmissionLock(async () => { const existing = this.store.getJob(id); if (!existing || existing.state.runningAt)
71
+ return undefined; if (this.activeRuns >= this.maxConcurrentRuns)
72
+ throw new PiboRalphCapacityError(); const job = this.store.updateJob(id, { enabled: true }); if (!job)
67
73
  return undefined; const reserved = await this.reserveAfterBeforeRunEvaluation(job); if (!reserved)
68
- return undefined; void this.executeReserved(reserved.job, reserved.run).finally(() => this.armSoon()); return reserved.run; }
74
+ return undefined; void this.executeReserved(reserved.job, reserved.run).finally(() => this.armSoon()); return reserved.run; }); }
69
75
  stopJob(id) { const job = this.store.requestStop(id); this.armSoon(); return job; }
70
76
  async cancelJob(id) {
71
77
  const job = this.store.requestCancel(id);
@@ -82,14 +88,13 @@ export class PiboRalphService {
82
88
  async tick() { if (this.stopped)
83
89
  return; try {
84
90
  await this.abortCancelRequestedJobs();
85
- const capacity = this.maxConcurrentRuns - this.activeRuns;
86
- if (capacity > 0) {
87
- const jobs = this.store.listJobs({ includeDisabled: false }).filter((job) => !job.state.runningAt).slice(0, capacity);
88
- for (const job of jobs) {
89
- const reserved = await this.reserveAfterBeforeRunEvaluation(job);
90
- if (reserved)
91
- void this.executeReserved(reserved.job, reserved.run).finally(() => this.armSoon());
92
- }
91
+ const jobs = this.store.listJobs({ includeDisabled: false }).filter((job) => !job.state.runningAt);
92
+ for (const job of jobs) {
93
+ const admitted = await this.withAdmissionLock(async () => { if (this.activeRuns >= this.maxConcurrentRuns)
94
+ return false; const reserved = await this.reserveAfterBeforeRunEvaluation(job); if (!reserved)
95
+ return true; void this.executeReserved(reserved.job, reserved.run).finally(() => this.armSoon()); return true; });
96
+ if (!admitted)
97
+ break;
93
98
  }
94
99
  }
95
100
  catch (error) {
@@ -98,6 +103,12 @@ export class PiboRalphService {
98
103
  finally {
99
104
  this.arm();
100
105
  } }
106
+ async withAdmissionLock(action) { const previous = this.admissionTail; let release; this.admissionTail = new Promise((resolve) => { release = resolve; }); await previous; try {
107
+ return await action();
108
+ }
109
+ finally {
110
+ release();
111
+ } }
101
112
  async reserveAfterBeforeRunEvaluation(job) {
102
113
  const fresh = this.store.getJob(job.id) ?? job;
103
114
  if (!fresh.enabled || fresh.state.runningAt)
@@ -1,3 +1,4 @@
1
+ import { redactSensitiveText } from "../core/sensitive-data-redaction.js";
1
2
  export function buildTerminalStatusViewModel(input = {}) {
2
3
  const fields = [];
3
4
  const progress = [];
@@ -56,10 +57,7 @@ export function progressBarText(descriptor, width = 20) {
56
57
  return `${"█".repeat(filled)}${"░".repeat(boundedWidth - filled)} ${descriptor.percent.toFixed(1)}%`;
57
58
  }
58
59
  export function redactTerminalSecret(text) {
59
- return text
60
- .replace(/\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*)\s*=\s*([^\s]+)/gi, "$1=[redacted]")
61
- .replace(/\b(api[_-]?key|token|secret|password)\s*[:=]\s*([^\s]+)/gi, "$1=[redacted]")
62
- .replace(/\b(?:sk|pk|pibo|ghp|github_pat)_[A-Za-z0-9_\-]{8,}\b/g, "[redacted]");
60
+ return redactSensitiveText(text).replace(/\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD)[A-Z0-9_]*)\s*:\s*\[redacted\]/gi, "$1=[redacted]");
63
61
  }
64
62
  function contextProgress(usage) {
65
63
  if (!usage || (usage.tokens === undefined && usage.contextWindow === undefined && usage.percent === undefined)) {
@@ -147,6 +147,24 @@ export class PiboDataSessionStore {
147
147
  `).run(sequence, new Date().toISOString(), piboSessionId);
148
148
  });
149
149
  }
150
+ claimAgentObservationSequence(parentPiboSessionId, minimum) {
151
+ return this.dataStore.transaction(() => {
152
+ const session = this.db.prepare("SELECT 1 AS present FROM sessions WHERE id = ? AND deleted_at IS NULL").get(parentPiboSessionId);
153
+ if (!session)
154
+ return minimum;
155
+ const row = this.db.prepare("SELECT next_sequence FROM session_agent_observation_counters WHERE parent_pibo_session_id = ?").get(parentPiboSessionId);
156
+ const migrationSafeSeed = Math.trunc(Date.now()) * 1_000;
157
+ const sequence = Math.max(minimum, row?.next_sequence ?? migrationSafeSeed);
158
+ this.db.prepare(`
159
+ INSERT INTO session_agent_observation_counters (parent_pibo_session_id, next_sequence, updated_at)
160
+ VALUES (?, ?, ?)
161
+ ON CONFLICT(parent_pibo_session_id) DO UPDATE SET
162
+ next_sequence = excluded.next_sequence,
163
+ updated_at = excluded.updated_at
164
+ `).run(parentPiboSessionId, sequence + 1, new Date().toISOString());
165
+ return sequence;
166
+ });
167
+ }
150
168
  claimOrAttachOutputPart(input) {
151
169
  return this.dataStore.transaction(() => {
152
170
  const indexAttribute = outputPartIndexAttribute(input.kind);
@@ -49,6 +49,7 @@ export class InMemoryPiboSessionStore {
49
49
  byPiSessionId = new Map();
50
50
  byNativeSession = new Map();
51
51
  outputRenderHighWater = new Map();
52
+ agentObservationNextSequence = new Map();
52
53
  outputToolInvocationNextOrdinal = new Map();
53
54
  get(id) {
54
55
  return this.byId.get(id);
@@ -120,6 +121,13 @@ export class InMemoryPiboSessionStore {
120
121
  if (sequence > current)
121
122
  this.outputRenderHighWater.set(piboSessionId, sequence);
122
123
  }
124
+ claimAgentObservationSequence(parentPiboSessionId, minimum) {
125
+ if (!this.byId.has(parentPiboSessionId))
126
+ return minimum;
127
+ const sequence = Math.max(minimum, this.agentObservationNextSequence.get(parentPiboSessionId) ?? 1);
128
+ this.agentObservationNextSequence.set(parentPiboSessionId, sequence + 1);
129
+ return sequence;
130
+ }
123
131
  claimOutputToolInvocationOrdinal(piboSessionId, eventId, toolCallId) {
124
132
  const key = outputToolInvocationCounterKey(piboSessionId, eventId, toolCallId);
125
133
  const ordinal = this.outputToolInvocationNextOrdinal.get(key) ?? 0;
@@ -163,6 +171,7 @@ export class InMemoryPiboSessionStore {
163
171
  if (nativeKey)
164
172
  this.byNativeSession.delete(nativeKey);
165
173
  this.outputRenderHighWater.delete(id);
174
+ this.agentObservationNextSequence.delete(id);
166
175
  const counterPrefix = `${JSON.stringify([id]).slice(0, -1)},`;
167
176
  for (const key of this.outputToolInvocationNextOrdinal.keys()) {
168
177
  if (key.startsWith(counterPrefix))
package/dist/setup/cli.js CHANGED
@@ -7,7 +7,7 @@ import { Command } from "commander";
7
7
  import { getPiboConfigValue, loadPiboConfig } from "../config/config.js";
8
8
  import { getPiboHome } from "../core/pibo-home.js";
9
9
  import { getWslInfo, isWsl } from "../core/wsl.js";
10
- import { createInstallationPlan, inspectInstallation, installationManifestPath, materializeInstallationPlan, parseInstallationComponent, parseInstallationProfile, readInstallationManifest, runPendingInstallationActions, uninstallInstallation, validateInstallationPlanTargets, } from "./installation-profiles.js";
10
+ import { createInstallationPlan, inspectInstallation, installationManifestPath, installationOutputPath, materializeInstallationPlan, parseInstallationComponent, parseInstallationProfile, readInstallationManifest, runPendingInstallationActions, uninstallInstallation, validateInstallationPlanTargets, } from "./installation-profiles.js";
11
11
  function parsePort(value) {
12
12
  const port = Number(value);
13
13
  if (!Number.isInteger(port) || port < 1 || port > 65535)
@@ -126,7 +126,7 @@ export function createUserHostSetupPlan(options = {}) {
126
126
  const wwwDomain = options.wwwDomain ?? (options.domain ? `www.${options.domain}` : undefined);
127
127
  const warnings = [];
128
128
  if (!options.domain)
129
- warnings.push("No production domain was provided; generated Caddy/Auth examples use placeholders.");
129
+ warnings.push("No production domain was provided; the Caddyfile is omitted and Auth examples use placeholders.");
130
130
  if (process.platform === "win32" && !isWsl()) {
131
131
  warnings.push("Pibo host setup targets Linux. Native Windows is not supported. Install WSL2 (https://aka.ms/wsl) and run setup inside the WSL distribution. See docs/project/guides/pibo-on-windows-via-wsl.md.");
132
132
  }
@@ -149,7 +149,14 @@ export function createUserHostSetupPlan(options = {}) {
149
149
  content: userEnvTemplate({ domain: options.domain, piboHome }),
150
150
  },
151
151
  ];
152
- if (options.includeCaddy !== false) {
152
+ if (serviceName !== "pibo-web") {
153
+ generatedFiles.push({
154
+ path: `${piboHome}/gateway-web-service`,
155
+ purpose: "Managed production gateway service identity",
156
+ content: `${serviceName}\n`,
157
+ });
158
+ }
159
+ if (options.includeCaddy !== false && options.domain) {
153
160
  generatedFiles.push({
154
161
  path: "/etc/caddy/Caddyfile",
155
162
  purpose: "HTTPS reverse proxy for the production gateway",
@@ -213,7 +220,7 @@ export function createDeveloperHostSetupPlan(options = {}) {
213
220
  piboHome: prodHome,
214
221
  serviceKind: "prod",
215
222
  webPort: prodWebPort,
216
- execStart: `${prodEntrypoint} gateway:web --web-host 127.0.0.1 --web-port ${prodWebPort}`,
223
+ execStart: `${prodEntrypoint} gateway:web --web-host 127.0.0.1 --web-port ${prodWebPort} --gateway-port ${prodGatewayPort}`,
217
224
  }),
218
225
  },
219
226
  {
@@ -871,7 +878,7 @@ export async function runSetupCli(argv = process.argv) {
871
878
  .action((options) => {
872
879
  if (options.root && options.apply)
873
880
  throw new Error("Use either --root for staging or --apply for the host, not both");
874
- const manifestPath = options.root ? join(options.root, installationManifestPath(options.piboHome).replace(/^\/+/, "")) : installationManifestPath(options.piboHome);
881
+ const manifestPath = installationOutputPath(installationManifestPath(options.piboHome), options.root);
875
882
  const manifest = readInstallationManifest(manifestPath);
876
883
  if (!manifest)
877
884
  throw new Error(`No installation manifest found at ${manifestPath}`);
@@ -893,7 +900,7 @@ export async function runSetupCli(argv = process.argv) {
893
900
  .action((name, options) => {
894
901
  if (options.root && options.apply)
895
902
  throw new Error("Use either --root for staging or --apply for the host, not both");
896
- const manifestPath = options.root ? join(options.root, installationManifestPath(options.piboHome).replace(/^\/+/, "")) : installationManifestPath(options.piboHome);
903
+ const manifestPath = installationOutputPath(installationManifestPath(options.piboHome), options.root);
897
904
  const manifest = readInstallationManifest(manifestPath);
898
905
  if (!manifest)
899
906
  throw new Error(`No installation manifest found at ${manifestPath}`);
@@ -915,7 +922,7 @@ export async function runSetupCli(argv = process.argv) {
915
922
  .action((options) => {
916
923
  if (options.root && options.apply)
917
924
  throw new Error("Use either --root for staging or --apply for the host, not both");
918
- const manifestPath = options.root ? join(options.root, installationManifestPath(options.piboHome).replace(/^\/+/, "")) : installationManifestPath(options.piboHome);
925
+ const manifestPath = installationOutputPath(installationManifestPath(options.piboHome), options.root);
919
926
  const manifest = readInstallationManifest(manifestPath);
920
927
  if (!manifest)
921
928
  throw new Error(`No installation manifest found at ${manifestPath}`);
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
- import { dirname, isAbsolute, join } from "node:path";
3
+ import { dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
4
4
  const CODE_SERVER_VERSION = "4.135.0";
5
5
  const BROWSER_USE_VERSION = "0.12.6";
6
6
  const AGENT_BROWSER_VERSION = "0.27.0";
@@ -28,6 +28,9 @@ function sha256(content) {
28
28
  function shellQuote(value) {
29
29
  return `'${value.replace(/'/g, `'\\''`)}'`;
30
30
  }
31
+ function systemdQuote(value) {
32
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
33
+ }
31
34
  function normalizeContent(content) {
32
35
  return content.endsWith("\n") ? content : `${content}\n`;
33
36
  }
@@ -36,8 +39,8 @@ export function installationManifestPath(piboHome) {
36
39
  }
37
40
  function gatewayService(options) {
38
41
  const integrationEnvironment = [
39
- ...(options.hasVscode ? ["Environment=PIBO_VSCODE_WEB_URL=/apps/vscode/", `Environment=PIBO_VSCODE_WORKSPACE_ROOT=${options.workspaceRoot}`] : []),
40
- ...(options.hasMcpDefaults ? [`Environment=MCP_CONFIG_PATH=${options.piboHome}/setup/mcp-defaults.json`] : []),
42
+ ...(options.hasVscode ? ["Environment=PIBO_VSCODE_WEB_URL=/apps/vscode/", `Environment=${systemdQuote(`PIBO_VSCODE_WORKSPACE_ROOT=${options.workspaceRoot}`)}`] : []),
43
+ ...(options.hasMcpDefaults ? [`Environment=${systemdQuote(`MCP_CONFIG_PATH=${options.piboHome}/setup/mcp-defaults.json`)}`] : []),
41
44
  ];
42
45
  return `[Unit]
43
46
  Description=Pibo web gateway
@@ -49,7 +52,7 @@ Type=simple
49
52
  User=root
50
53
  WorkingDirectory=/root
51
54
  Environment=HOME=/root
52
- Environment=PIBO_HOME=${options.piboHome}
55
+ Environment=${systemdQuote(`PIBO_HOME=${options.piboHome}`)}
53
56
  Environment=NODE_ENV=production
54
57
  Environment=PIBO_GATEWAY_WEB_PORT=4788
55
58
  ${integrationEnvironment.length > 0 ? `${integrationEnvironment.join("\n")}\n` : ""}ExecStart=${options.piboCommand} gateway:web --web-host 127.0.0.1 --web-port 4788
@@ -76,14 +79,14 @@ WorkingDirectory=${workspaceRoot}
76
79
  Environment=HOME=/var/lib/pibo-code
77
80
  Environment=XDG_DATA_HOME=/var/lib/pibo-code/.local/share
78
81
  Environment=XDG_CONFIG_HOME=/var/lib/pibo-code/.config
79
- ExecStart=/opt/pibo/code-server/${CODE_SERVER_VERSION}/bin/code-server --bind-addr 127.0.0.1:4790 --auth none --disable-telemetry --disable-update-check --disable-workspace-trust ${workspaceRoot}
82
+ ExecStart=/opt/pibo/code-server/${CODE_SERVER_VERSION}/bin/code-server --bind-addr 127.0.0.1:4790 --auth none --disable-telemetry --disable-update-check --disable-workspace-trust ${systemdQuote(workspaceRoot)}
80
83
  Restart=always
81
84
  RestartSec=5
82
85
  NoNewPrivileges=true
83
86
  PrivateTmp=true
84
87
  ProtectSystem=strict
85
88
  ProtectHome=read-only
86
- ReadWritePaths=${workspaceRoot} /var/lib/pibo-code
89
+ ReadWritePaths=${systemdQuote(workspaceRoot)} /var/lib/pibo-code
87
90
 
88
91
  [Install]
89
92
  WantedBy=multi-user.target
@@ -254,7 +257,17 @@ function ownedDirectoryMarkerParent(path) {
254
257
  function outputPath(path, root) {
255
258
  if (!root)
256
259
  return path;
257
- return isAbsolute(path) ? join(root, path.replace(/^\/+/, "")) : join(root, path);
260
+ const resolvedRoot = resolve(root);
261
+ const stagedPath = isAbsolute(path) ? relative(parse(path).root, path) : path;
262
+ const destination = resolve(resolvedRoot, stagedPath);
263
+ const relativeDestination = relative(resolvedRoot, destination);
264
+ if (relativeDestination === ".." || relativeDestination.startsWith(`..${sep}`) || isAbsolute(relativeDestination)) {
265
+ throw new Error(`Refusing staged path outside root: ${path}`);
266
+ }
267
+ return destination;
268
+ }
269
+ export function installationOutputPath(path, root) {
270
+ return outputPath(path, root);
258
271
  }
259
272
  function atomicWrite(path, content, mode) {
260
273
  mkdirSync(dirname(path), { recursive: true });
@@ -55,6 +55,9 @@ export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent,
55
55
  if (!node)
56
56
  return;
57
57
  applyStoredPayloadRef(node, payload, storedEvent.storedPayloadRef);
58
+ if (payload.type === "session_error" && payload.eventId) {
59
+ node.parentId = settleTurnFromSessionError(byId, payload.eventId, payload, storedEvent.createdAt);
60
+ }
58
61
  if (node.type === "user.message" &&
59
62
  node.status === "running" &&
60
63
  !node.parentId &&
@@ -280,10 +283,11 @@ export function markIncompletePersistedTurns(nodes, byId, piboSessionId, events,
280
283
  turn.status = "error";
281
284
  turn.summary = INCOMPLETE_TURN_SUMMARY;
282
285
  turn.error = INCOMPLETE_TURN_ERROR;
286
+ const last = lastByEventId.get(eventId) ?? start;
287
+ settleInterruptedTurnChildren(byId, turnId, INCOMPLETE_TURN_ERROR);
283
288
  const markerId = `event:incomplete-turn:${eventId}`;
284
289
  if (byId.has(markerId))
285
290
  continue;
286
- const last = lastByEventId.get(eventId) ?? start;
287
291
  const marker = {
288
292
  id: markerId,
289
293
  parentId: turnId,
@@ -306,6 +310,33 @@ export function markIncompletePersistedTurns(nodes, byId, piboSessionId, events,
306
310
  }
307
311
  return [...incompleteEventIds].some((eventId) => byId.has(`event:incomplete-turn:${eventId}`));
308
312
  }
313
+ function settleTurnFromSessionError(byId, eventId, event, completedAt) {
314
+ const turnId = messageTurnNodeId(eventId);
315
+ const turn = byId.get(turnId) ?? [...byId.values()].find((candidate) => candidate.type === "agent.turn" && candidate.eventId === eventId);
316
+ if (!turn)
317
+ return undefined;
318
+ turn.status = "error";
319
+ turn.completedAt = completedAt ?? turn.completedAt;
320
+ turn.summary = event.errorDetails?.userMessage ?? event.error;
321
+ turn.error = event.error;
322
+ settleInterruptedTurnChildren(byId, turn.id, event.error, completedAt);
323
+ return turn.id;
324
+ }
325
+ function settleInterruptedTurnChildren(byId, turnId, error, completedAt) {
326
+ for (const node of byId.values()) {
327
+ if (node.parentId !== turnId)
328
+ continue;
329
+ const isOpenContent = node.status === "running" && (node.type === "assistant.message" ||
330
+ node.type === "model.reasoning" ||
331
+ node.type === "tool.call");
332
+ const isUnfinishedTool = node.type === "tool.call" && node.completedAt === undefined;
333
+ if (!isOpenContent && !isUnfinishedTool)
334
+ continue;
335
+ node.status = "error";
336
+ node.completedAt = completedAt;
337
+ node.error = error;
338
+ }
339
+ }
309
340
  export function eventsCanAffectAsyncAgentRunStatus(events) {
310
341
  return events.some((event) => {
311
342
  const type = event.payload.type;
@@ -34,12 +34,12 @@ function resultText(prefix, value) {
34
34
  export function normalizePiboAgentSessionName(value) {
35
35
  if (typeof value !== "string")
36
36
  throw new Error("Agent session name is required.");
37
- if ([...value].length > PIBO_AGENT_SESSION_NAME_MAX_LENGTH) {
38
- throw new Error(`Agent session name must be at most ${PIBO_AGENT_SESSION_NAME_MAX_LENGTH} characters.`);
39
- }
40
37
  const normalized = value.trim();
41
38
  if (!normalized)
42
39
  throw new Error("Agent session name must not be empty.");
40
+ if ([...normalized].length > PIBO_AGENT_SESSION_NAME_MAX_LENGTH) {
41
+ throw new Error(`Agent session name must be at most ${PIBO_AGENT_SESSION_NAME_MAX_LENGTH} characters.`);
42
+ }
43
43
  return normalized;
44
44
  }
45
45
  function preparePiboAgentSessionNameInput(input) {
@@ -289,10 +289,10 @@ function killLeaseProcess(status, lease) {
289
289
  }
290
290
  async function releaseAssociatedBrowserPoolLease(status, lease) {
291
291
  if (!lease.browserPoolLeaseId)
292
- return;
292
+ return undefined;
293
293
  const identity = browserPoolIdentityForLease(status, lease);
294
294
  const paths = browserPoolPaths(browserPoolRootForLease(status, lease), identity);
295
- await releaseBrowserPoolLease(paths, identity, { leaseId: lease.browserPoolLeaseId });
295
+ return releaseBrowserPoolLease(paths, identity, { leaseId: lease.browserPoolLeaseId });
296
296
  }
297
297
  async function reapAssociatedBrowserPoolLeaseProcess(status, lease) {
298
298
  if (!lease.browserPoolLeaseId)
@@ -613,7 +613,15 @@ export async function releaseBrowserUseLease(context, id, options = {}) {
613
613
  }));
614
614
  }
615
615
  if (lease.browserPoolLeaseId) {
616
- await releaseAssociatedBrowserPoolLease(context.status, lease);
616
+ const poolRelease = await releaseAssociatedBrowserPoolLease(context.status, lease);
617
+ if (poolRelease && (poolRelease.cleanupStatus === 'failed' || poolRelease.state.state === 'dirty')) {
618
+ throw new Error(formatCliError({
619
+ code: ErrorCode.CLIENT_ERROR,
620
+ type: 'BROWSER_USE_POOL_CLEANUP_FAILED',
621
+ message: `Browser pool cleanup failed for lease "${lease.id}": ${poolRelease.lastError ?? 'managed browser cleanup failed'}`,
622
+ suggestion: `The auth lease and profile were preserved. Run \`pibo tools browser-use pool reap --worker-id ${poolRelease.state.workerId} --pool-id ${poolRelease.state.poolId} --idle-timeout-ms 0\`, then retry release.`,
623
+ }));
624
+ }
617
625
  }
618
626
  else {
619
627
  killLeaseProcess(context.status, lease);
@@ -679,7 +679,6 @@ export async function runToolsCli(argv = process.argv) {
679
679
  program
680
680
  .name('pibo tools')
681
681
  .description('Install and inspect curated external CLI tools')
682
- .helpOption(false)
683
682
  .showHelpAfterError();
684
683
  program
685
684
  .command('list')
@@ -180,7 +180,7 @@ async function piboResultToMcp(result, options) {
180
180
  tool: options.tool,
181
181
  toolCallId: options.toolCallId,
182
182
  contentType: "application/json",
183
- value: encoded,
183
+ value: structuredContent,
184
184
  });
185
185
  payloadRefs.push(stored.ref);
186
186
  structuredContent = undefined;