@pasko70/pibo 3.2.3 → 3.2.4

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 (69) 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 +38 -0
  41. package/dist/data/schema.js +9 -1
  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/runtime/node-worker-source.js +39 -5
  63. package/dist/tools/runtime/python-backend.js +6 -2
  64. package/dist/tools/runtime/registry.js +38 -6
  65. package/dist/vscode/cli.js +1 -2
  66. package/dist/vscode/install.js +21 -14
  67. package/npm-shrinkwrap.json +2 -2
  68. package/package.json +3 -1
  69. package/dist/apps/chat-ui/assets/index-Y6jYzsaC.js +0 -228
@@ -1,11 +1,9 @@
1
+ import { redactSensitiveText } from "../core/sensitive-data-redaction.js";
1
2
  export const AGENT_RUNTIME_AUTH_METHOD_IDS = ["device_code", "browser_oauth", "api_key"];
2
3
  export const AGENT_RUNTIME_AUTH_COMPLETION_MODES = ["immediate", "explicit", "notification"];
3
4
  export function redactAgentRuntimeAuthText(value, maxLength = 1_000) {
4
- return value
5
- .replace(/Bearer\s+\S+/gi, "Bearer [redacted]")
6
- .replace(/\b(?:sk|pk|ghp|github_pat|pibo)[-_][A-Za-z0-9_-]{8,}\b/g, "[redacted]")
7
- .replace(/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, "[redacted]")
8
- .replace(/\b(access[_-]?token|refresh[_-]?token|id[_-]?token|api[_-]?key|token|secret|password|account[_-]?id)\b(\s*["']?\s*[:=]\s*["']?)([^\s"'&,}]+)/gi, "$1$2[redacted]")
5
+ return redactSensitiveText(value)
6
+ .replace(/\b(account[_-]?id)\b(\s*["']?\s*[:=]\s*["']?)([^\s"'&,}]+)/gi, "$1$2[redacted]")
9
7
  .replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, "[redacted account]")
10
8
  .replace(/`(?:\/|[A-Za-z]:[\\/])[^`\r\n]+`/g, "`[redacted path]`")
11
9
  .replace(/\bfile:\/\/\/?[^\s"'`,)]+/gi, "file://[redacted path]")
@@ -432,7 +432,7 @@ function boundEntries(entries, limits) {
432
432
  const normalized = normalizeEntrySize(entry);
433
433
  const size = entryBytes(normalized);
434
434
  if (selected.length >= limits.maxEntries || bytes + size > limits.maxBytes)
435
- continue;
435
+ break;
436
436
  selected.push(normalized);
437
437
  bytes += size;
438
438
  }
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { redactSensitiveText } from "../../core/sensitive-data-redaction.js";
2
3
  const DEFAULT_STARTUP_TIMEOUT_MS = 10_000;
3
4
  const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;
4
5
  const DEFAULT_SHUTDOWN_TIMEOUT_MS = 2_000;
@@ -92,11 +93,7 @@ function validateInitializeResponse(value) {
92
93
  };
93
94
  }
94
95
  function redactCodexAppServerDiagnostic(value) {
95
- return value
96
- .replace(/Bearer\s+\S+/gi, "Bearer [redacted]")
97
- .replace(/\b(?:sk|pk|ghp|github_pat|pibo)[-_][A-Za-z0-9_-]{8,}\b/g, "[redacted]")
98
- .replace(/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, "[redacted]")
99
- .replace(/\b(access[_-]?token|refresh[_-]?token|id[_-]?token|api[_-]?key|token|secret|password)\b(\s*["']?\s*[:=]\s*["']?)([^\s"'&,}]+)/gi, "$1$2[redacted]")
96
+ return redactSensitiveText(value)
100
97
  .replace(/`(?:\/|[A-Za-z]:[\\/])[^`\r\n]+`/g, "`[redacted path]`")
101
98
  .replace(/\bfile:\/\/\/?[^\s"'`,)]+/gi, "file://[redacted path]")
102
99
  .slice(0, 4_000);
@@ -1,31 +1,7 @@
1
- const SENSITIVE_KEY_PATTERN = /(?:authorization|cookie|credential|access[_-]?token|refresh[_-]?token|id[_-]?token|api[_-]?key|token|secret|password)/i;
1
+ import { redactSensitiveText, redactSensitiveValue } from "../../core/sensitive-data-redaction.js";
2
2
  export function redactCodexNativeSensitiveText(value) {
3
- return value
4
- .replace(/Bearer\s+\S+/gi, "Bearer [redacted]")
5
- .replace(/\b(?:sk|pk|ghp|github_pat|pibo)[-_][A-Za-z0-9_-]{8,}\b/g, "[redacted]")
6
- .replace(/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, "[redacted]")
7
- .replace(/\b(access[_-]?token|refresh[_-]?token|id[_-]?token|api[_-]?key|token|secret|password)\b(\s*["']?\s*[:=]\s*["']?)([^\s"'&,}]+)/gi, "$1$2[redacted]");
3
+ return redactSensitiveText(value);
8
4
  }
9
5
  export function redactCodexNativeValue(value, key, depth = 0) {
10
- if (key && SENSITIVE_KEY_PATTERN.test(key))
11
- return "[redacted]";
12
- if (depth > 32)
13
- return "[maximum depth reached]";
14
- if (value === null || typeof value === "boolean")
15
- return value;
16
- if (typeof value === "string")
17
- return redactCodexNativeSensitiveText(value);
18
- if (typeof value === "number")
19
- return Number.isFinite(value) ? value : String(value);
20
- if (Array.isArray(value))
21
- return value.map((entry) => redactCodexNativeValue(entry, undefined, depth + 1));
22
- if (value && typeof value === "object") {
23
- const result = {};
24
- for (const [entryKey, entryValue] of Object.entries(value)) {
25
- if (entryValue !== undefined)
26
- result[entryKey] = redactCodexNativeValue(entryValue, entryKey, depth + 1);
27
- }
28
- return result;
29
- }
30
- return String(value);
6
+ return redactSensitiveValue(value, key, depth);
31
7
  }
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { redactSensitiveText } from "../../core/sensitive-data-redaction.js";
2
3
  import { randomUUID } from "node:crypto";
3
4
  import { OMP_RPC_CHUNK_PAYLOAD_BYTES, OMP_RPC_MAX_FRAME_BYTES, OMP_RPC_PROTOCOL_VERSION, } from "./protocol-types.js";
4
5
  const DEFAULT_STARTUP_TIMEOUT_MS = 15_000;
@@ -109,10 +110,7 @@ export class OmpRpcClient {
109
110
  * stderr (which may echo prompts or provider keys) cannot leak secrets.
110
111
  */
111
112
  static redactDiagnostic(message) {
112
- return message
113
- .replace(/\b(?:sk|rk|pk|ghp|github_pat)[-_][A-Za-z0-9._~+/-]{8,}\b/g, "[redacted]")
114
- .replace(/\bBearer\s+\S+/gi, "Bearer [redacted]")
115
- .replace(/\b(?:api[_-]?key|secret|token|password)\b(?=[:=]\s*)\s*[^\s,;]+/gi, "$1 [redacted]");
113
+ return redactSensitiveText(message);
116
114
  }
117
115
  emitDiagnostic(message) {
118
116
  for (const listener of this.diagnosticListeners)
@@ -635,6 +635,7 @@ class PiAgentRuntimeAdapter {
635
635
  const intentTracing = piIntentTracingEnabled(profile.runtimeOptions);
636
636
  const runtime = await createPiboRuntime({
637
637
  cwd: input.workspace,
638
+ piPackageStoreCwd: compatibility?.piPackageStoreCwd,
638
639
  persistSession: compatibility?.persistSession,
639
640
  profile,
640
641
  thinkingLevel: compatibility?.thinkingLevel,
@@ -223,7 +223,7 @@ export async function createPiboRuntime(options = {}) {
223
223
  const skillPaths = options.resources
224
224
  ? [...options.resources.getSkillPaths("source")]
225
225
  : getEnabledSkillPaths(runtimeCwd, profile);
226
- const piPackageOptions = getPiPackageRuntimeOptions(runtimeCwd, profile);
226
+ const piPackageOptions = getPiPackageRuntimeOptions(options.piPackageStoreCwd ?? runtimeCwd, profile);
227
227
  let runtimeSettingsManager;
228
228
  const services = await createAgentSessionServices({
229
229
  cwd: runtimeCwd,
@@ -62,7 +62,7 @@ export function previewCustomAgentUpdate(existing, input, options = {}) {
62
62
  profileName,
63
63
  displayName: input.displayName ?? existing.displayName,
64
64
  folderId: input.folderId === undefined ? existing.folderId : input.folderId ?? undefined,
65
- description: input.description === undefined ? existing.description : input.description,
65
+ description: input.description === undefined ? existing.description : input.description ?? undefined,
66
66
  runtimeInstanceId: input.runtimeInstanceId === undefined ? existing.runtimeInstanceId : sanitizeRuntimeInstanceId(input.runtimeInstanceId),
67
67
  runtimeOptions: input.runtimeOptions === undefined ? existing.runtimeOptions : cloneRuntimeOptions(input.runtimeOptions),
68
68
  nativeSubagents: input.nativeSubagents === undefined ? existing.nativeSubagents : sanitizeBoolean(input.nativeSubagents),
@@ -6,7 +6,7 @@ import { protectPrivateDirectorySync, protectPrivateFileSync } from "../../core/
6
6
  import { PiboWebHttpError } from "../../web/http.js";
7
7
  import { imageMimeTypeFromBytes, TRACE_IMAGE_MAX_DECODED_BYTES } from "./trace-v2.js";
8
8
  export const CHAT_UPLOAD_DIR = piboHomePath("uploads");
9
- const CHAT_FILE_ATTACHMENT_LIMIT = 10;
9
+ export const CHAT_FILE_ATTACHMENT_LIMIT = 10;
10
10
  export function prepareChatFileAttachments(input) {
11
11
  const paths = normalizeChatFileAttachmentPaths(input.attachmentPaths);
12
12
  if (!paths.length)
@@ -34,6 +34,8 @@ export async function saveUploadedChatFiles(request) {
34
34
  }
35
35
  if (!files.length)
36
36
  throw new PiboWebHttpError("No files were uploaded", 400);
37
+ if (files.length > CHAT_FILE_ATTACHMENT_LIMIT)
38
+ throw new PiboWebHttpError(`At most ${CHAT_FILE_ATTACHMENT_LIMIT} uploaded files can be attached`, 400);
37
39
  ensurePrivateChatUploadDirectory();
38
40
  const saved = [];
39
41
  for (const file of files) {
@@ -749,7 +749,7 @@ export function createAgentUpdate(body) {
749
749
  if (body.displayName !== undefined)
750
750
  update.displayName = normalizeAgentDisplayName(body.displayName);
751
751
  if (body.description !== undefined)
752
- update.description = normalizeAgentDescription(body.description);
752
+ update.description = normalizeAgentDescription(body.description) ?? null;
753
753
  if (body.folderId !== undefined)
754
754
  update.folderId = normalizeAgentFolderId(body.folderId);
755
755
  if (body.runtimeInstanceId !== undefined)
@@ -170,6 +170,13 @@ export async function handleChatUserSkillRoute(options) {
170
170
  const existing = userSkillManager.get(route.skillId, scope);
171
171
  if (!existing)
172
172
  throw new PiboWebHttpError("Skill not found", 404);
173
+ const hasEnabledReplacement = userSkillManager.list("all").some((skill) => (skill.id !== existing.id && skill.enabled && skill.name === existing.name));
174
+ const affectedAgents = existing.enabled && !hasEnabledReplacement
175
+ ? options.agentsSelectingSkill(existing.name)
176
+ : [];
177
+ if (affectedAgents.length > 0) {
178
+ throw new PiboWebHttpError(`Skill is selected by custom agents: ${affectedAgents.map((agent) => agent.profileName).join(", ")}`, 409);
179
+ }
173
180
  userSkillManager.remove(existing.id, scope);
174
181
  syncAndInvalidate(options);
175
182
  return responseJson({ removedSkillId: existing.id });
@@ -333,6 +333,24 @@ export class ChatProjectService {
333
333
  const rows = this.db.prepare(`SELECT * FROM project_workflow_wait_tokens ${where} ORDER BY created_at DESC, id DESC LIMIT ?`).all(...values, limit);
334
334
  return rows.map(projectWorkflowWaitTokenFromRow);
335
335
  }
336
+ expireProjectWorkflowWaitToken(input) {
337
+ this.db.exec("BEGIN IMMEDIATE");
338
+ try {
339
+ const token = this.getProjectWorkflowWaitToken(input.waitTokenId);
340
+ if (!token)
341
+ throw new Error("Workflow wait token not found");
342
+ if (token.projectId !== input.projectId || token.piboSessionId !== input.piboSessionId || token.workflowRunId !== input.workflowRunId) {
343
+ throw new Error("Workflow wait token does not belong to this Project workflow session");
344
+ }
345
+ const result = this.expireProjectWorkflowWaitTokenLocked(token, input.resolvedAt ?? new Date().toISOString());
346
+ this.db.exec("COMMIT");
347
+ return result;
348
+ }
349
+ catch (error) {
350
+ this.db.exec("ROLLBACK");
351
+ throw error;
352
+ }
353
+ }
336
354
  listProjectWorkflowHumanActions(filter = {}) {
337
355
  const clauses = [];
338
356
  const values = [];
@@ -370,7 +388,7 @@ export class ChatProjectService {
370
388
  throw new Error(`Workflow wait token is ${token.status} and cannot be resolved again`);
371
389
  const actedAt = input.actedAt ?? new Date().toISOString();
372
390
  if (token.expiresAt && Date.parse(token.expiresAt) <= Date.parse(actedAt)) {
373
- this.db.prepare("UPDATE project_workflow_wait_tokens SET status = 'expired', resolved_at = ? WHERE id = ?").run(actedAt, token.id);
391
+ this.expireProjectWorkflowWaitTokenLocked(token, actedAt);
374
392
  this.db.exec("COMMIT");
375
393
  throw new Error(`Workflow wait token expired at ${token.expiresAt}`);
376
394
  }
@@ -494,6 +512,36 @@ export class ChatProjectService {
494
512
  this.db.prepare("UPDATE project_sessions SET archived = ?, updated_at = ? WHERE pibo_session_id = ?").run(archived ? 1 : 0, now, piboSessionId);
495
513
  return this.getProjectSession(piboSessionId);
496
514
  }
515
+ expireProjectWorkflowWaitTokenLocked(token, resolvedAt) {
516
+ if (token.status !== "pending")
517
+ throw new Error(`Workflow wait token is ${token.status} and cannot be expired again`);
518
+ const resolvedAtMs = Date.parse(resolvedAt);
519
+ if (!Number.isFinite(resolvedAtMs))
520
+ throw new Error("Workflow wait token expiry resolution time is invalid");
521
+ if (!token.expiresAt || Date.parse(token.expiresAt) > resolvedAtMs)
522
+ throw new Error("Workflow wait token has not expired");
523
+ const pendingTokens = this.db.prepare(`SELECT id, expires_at FROM project_workflow_wait_tokens
524
+ WHERE project_id = ? AND pibo_session_id = ? AND workflow_run_id = ? AND status = 'pending'`)
525
+ .all(token.projectId, token.piboSessionId, token.workflowRunId);
526
+ const expire = this.db.prepare("UPDATE project_workflow_wait_tokens SET status = 'expired', resolved_at = ? WHERE id = ? AND status = 'pending'");
527
+ for (const pendingToken of pendingTokens) {
528
+ if (pendingToken.expires_at && Date.parse(pendingToken.expires_at) <= resolvedAtMs)
529
+ expire.run(resolvedAt, pendingToken.id);
530
+ }
531
+ const pending = this.db.prepare(`SELECT COUNT(*) AS count FROM project_workflow_wait_tokens
532
+ WHERE project_id = ? AND pibo_session_id = ? AND workflow_run_id = ? AND status = 'pending'`)
533
+ .get(token.projectId, token.piboSessionId, token.workflowRunId);
534
+ const nextStatus = pending.count === 0 ? "failed" : "waiting";
535
+ this.db.prepare("UPDATE project_workflow_runs SET status = ?, updated_at = ?, failed_at = CASE WHEN ? = 'failed' THEN COALESCE(failed_at, ?) ELSE failed_at END WHERE id = ? AND project_id = ? AND pibo_session_id = ? AND status IN ('running', 'waiting')")
536
+ .run(nextStatus, resolvedAt, nextStatus, resolvedAt, token.workflowRunId, token.projectId, token.piboSessionId);
537
+ this.db.prepare("UPDATE project_sessions SET state = ?, updated_at = ? WHERE pibo_session_id = ? AND project_id = ? AND state IN ('running', 'waiting')")
538
+ .run(nextStatus, resolvedAt, token.piboSessionId, token.projectId);
539
+ return {
540
+ waitToken: this.getProjectWorkflowWaitToken(token.id),
541
+ run: this.getProjectWorkflowRun(token.workflowRunId),
542
+ projectSession: this.getProjectSession(token.piboSessionId),
543
+ };
544
+ }
497
545
  insertProjectWorkflowRunForExistingSession(row, input) {
498
546
  const now = new Date().toISOString();
499
547
  const run = {
@@ -77,12 +77,52 @@ export class ChatSessionQueryService {
77
77
  if (!piboSessionIds.length)
78
78
  return 0;
79
79
  const placeholders = piboSessionIds.map(() => "?").join(", ");
80
- this.store.db.prepare(`DELETE FROM observations WHERE session_id IN (${placeholders})`).run(...piboSessionIds);
81
- this.store.db.prepare(`DELETE FROM chat_messages WHERE session_id IN (${placeholders})`).run(...piboSessionIds);
82
- this.store.db.prepare(`DELETE FROM event_log WHERE session_id IN (${placeholders})`).run(...piboSessionIds);
83
- this.store.db.prepare(`DELETE FROM session_navigation WHERE session_id IN (${placeholders})`).run(...piboSessionIds);
84
- const result = this.store.db.prepare(`UPDATE sessions SET deleted_at = COALESCE(deleted_at, ?) WHERE id IN (${placeholders})`).run(new Date().toISOString(), ...piboSessionIds);
85
- return Number(result.changes ?? 0);
80
+ const payloadReleaseCounts = this.payloadReleaseCounts(placeholders, piboSessionIds);
81
+ const releasedPayloads = [];
82
+ const deleted = this.store.transaction(() => {
83
+ this.store.db.prepare(`DELETE FROM observations WHERE session_id IN (${placeholders})`).run(...piboSessionIds);
84
+ this.store.db.prepare(`DELETE FROM chat_messages WHERE session_id IN (${placeholders})`).run(...piboSessionIds);
85
+ this.store.db.prepare(`DELETE FROM event_log WHERE session_id IN (${placeholders})`).run(...piboSessionIds);
86
+ this.store.db.prepare(`DELETE FROM session_navigation WHERE session_id IN (${placeholders})`).run(...piboSessionIds);
87
+ for (const [payloadId, count] of payloadReleaseCounts) {
88
+ const released = this.store.payloads.releaseReferences(payloadId, count);
89
+ if (released)
90
+ releasedPayloads.push(released);
91
+ }
92
+ const result = this.store.db.prepare(`UPDATE sessions SET deleted_at = COALESCE(deleted_at, ?) WHERE id IN (${placeholders})`).run(new Date().toISOString(), ...piboSessionIds);
93
+ return Number(result.changes ?? 0);
94
+ });
95
+ for (const payload of releasedPayloads)
96
+ this.store.payloads.removeReleasedFile(payload);
97
+ return deleted;
98
+ }
99
+ payloadReleaseCounts(placeholders, piboSessionIds) {
100
+ const logicalReferences = new Map();
101
+ const add = (payloadId, key) => {
102
+ if (!payloadId)
103
+ return;
104
+ let references = logicalReferences.get(payloadId);
105
+ if (!references) {
106
+ references = new Set();
107
+ logicalReferences.set(payloadId, references);
108
+ }
109
+ references.add(key);
110
+ };
111
+ const events = this.store.db.prepare(`SELECT stream_id, payload_ref FROM event_log WHERE session_id IN (${placeholders}) AND payload_ref IS NOT NULL`).all(...piboSessionIds);
112
+ const eventPayloads = new Map(events.map((row) => [row.stream_id, row.payload_ref]));
113
+ for (const row of events)
114
+ add(row.payload_ref, `event:${row.stream_id}`);
115
+ const messages = this.store.db.prepare(`SELECT id, source_stream_id, content_payload_ref FROM chat_messages WHERE session_id IN (${placeholders}) AND content_payload_ref IS NOT NULL`).all(...piboSessionIds);
116
+ for (const row of messages) {
117
+ const linked = row.source_stream_id !== null && eventPayloads.get(row.source_stream_id) === row.content_payload_ref;
118
+ add(row.content_payload_ref, linked ? `event:${row.source_stream_id}` : `message:${row.id}`);
119
+ }
120
+ const observations = this.store.db.prepare(`SELECT id, event_stream_id, payload_ref FROM observations WHERE session_id IN (${placeholders}) AND payload_ref IS NOT NULL`).all(...piboSessionIds);
121
+ for (const row of observations) {
122
+ const linked = row.event_stream_id !== null && eventPayloads.get(row.event_stream_id) === row.payload_ref;
123
+ add(row.payload_ref, linked ? `event:${row.event_stream_id}` : `observation:${row.id}`);
124
+ }
125
+ return new Map([...logicalReferences].map(([payloadId, references]) => [payloadId, references.size]));
86
126
  }
87
127
  close() { }
88
128
  sessionIndexMatches(session, status = "idle") {
@@ -1,5 +1,6 @@
1
1
  import { PiboWebHttpError, readJsonBody, responseJson } from '../../web/http.js';
2
2
  import { getPiboLoopService } from '../../loops/channel.js';
3
+ import { PiboLoopCapacityError } from '../../loops/service.js';
3
4
  import { listLoopJobTemplates } from '../../loops/templates.js';
4
5
  import { isPiboThinkingLevel } from '../../core/thinking.js';
5
6
  import { normalizeLoopStopPolicy, PiboLoopActiveRunModeChangeError } from '../../loops/store.js';
@@ -140,7 +141,15 @@ export async function handleChatLoopApiRequest(options) {
140
141
  if (!service)
141
142
  throw new PiboWebHttpError('Loop service is not running', 503);
142
143
  if (resource.child === 'start') {
143
- const run = await service.startJob(resource.id);
144
+ let run;
145
+ try {
146
+ run = await service.startJob(resource.id);
147
+ }
148
+ catch (error) {
149
+ if (error instanceof PiboLoopCapacityError)
150
+ throw new PiboWebHttpError(error.message, 409);
151
+ throw error;
152
+ }
144
153
  if (!run)
145
154
  throw new PiboWebHttpError('Loop job not found, already running, or stopped by a before-run condition', 404);
146
155
  return responseJson({ run: serializeRun(run) }, { status: 202 });
@@ -1,5 +1,6 @@
1
1
  import { PiboWebHttpError, readJsonBody, responseJson } from '../../web/http.js';
2
2
  import { getPiboRalphService } from '../../ralph/channel.js';
3
+ import { PiboRalphCapacityError } from '../../ralph/service.js';
3
4
  import { listRalphJobTemplates } from '../../ralph/templates.js';
4
5
  import { isPiboThinkingLevel } from '../../core/thinking.js';
5
6
  import { normalizeRalphStopPolicy } from '../../ralph/store.js';
@@ -116,7 +117,15 @@ export async function handleChatRalphApiRequest(options) {
116
117
  if (!service)
117
118
  throw new PiboWebHttpError('Ralph service is not running', 503);
118
119
  if (resource.child === 'start') {
119
- const run = await service.startJob(resource.id);
120
+ let run;
121
+ try {
122
+ run = await service.startJob(resource.id);
123
+ }
124
+ catch (error) {
125
+ if (error instanceof PiboRalphCapacityError)
126
+ throw new PiboWebHttpError(error.message, 409);
127
+ throw error;
128
+ }
120
129
  if (!run)
121
130
  throw new PiboWebHttpError('Ralph job not found, already running, or stopped by a before-run condition', 404);
122
131
  return responseJson({ run: serializeRun(run) }, { status: 202 });
@@ -2379,6 +2379,11 @@ function agentsSelectingPiPackage(state, packageId) {
2379
2379
  .list({ includeArchived: true })
2380
2380
  .filter((agent) => agent.piPackages.some((selected) => aliases.has(selected)));
2381
2381
  }
2382
+ function agentsSelectingSkill(state, skillName) {
2383
+ return state.agentStore
2384
+ .list({ includeArchived: true })
2385
+ .filter((agent) => agent.skills.includes(skillName));
2386
+ }
2382
2387
  function normalizeAgentDeleteConfirmation(value) {
2383
2388
  if (typeof value !== "string" || value.trim().length === 0) {
2384
2389
  throw new PiboWebHttpError("Type the agent name to permanently delete it.", 400);
@@ -2615,6 +2620,7 @@ async function buildContextBuildSnapshotForRequest(input) {
2615
2620
  if (runtime.adapterId === "pi" && runtimeInfo.available) {
2616
2621
  const snapshot = await inspectPiboContextBuild({
2617
2622
  cwd,
2623
+ piPackageStoreCwd: input.piPackageStoreCwd,
2618
2624
  profile,
2619
2625
  activeModel: selectedSession.activeModel,
2620
2626
  thinkingLevel: initialThinkingLevel,
@@ -3283,9 +3289,15 @@ function submitProjectWorkflowHumanAction(input) {
3283
3289
  humanActionOptions: WORKFLOW_HUMAN_ACTION_REF_OPTIONS,
3284
3290
  });
3285
3291
  if (validation.diagnostics.length) {
3286
- if (waitToken && validation.expiredAt) {
3287
- input.state.projectService.saveProjectWorkflowWaitToken({ ...waitToken, status: "expired", resolvedAt: validation.checkedAt });
3288
- }
3292
+ const responseWaitToken = waitToken && validation.expiredAt
3293
+ ? input.state.projectService.expireProjectWorkflowWaitToken({
3294
+ projectId: project.id,
3295
+ piboSessionId: session.id,
3296
+ workflowRunId: projectSession.workflowRunId,
3297
+ waitTokenId: waitToken.id,
3298
+ resolvedAt: validation.checkedAt,
3299
+ }).waitToken
3300
+ : waitToken;
3289
3301
  recordWorkflowLifecycleEvent(input.state, input.webSession, {
3290
3302
  type: "workflow.human_action.submitted",
3291
3303
  workflowId: projectSession.workflowId,
@@ -3297,7 +3309,7 @@ function submitProjectWorkflowHumanAction(input) {
3297
3309
  diagnostics: validation.diagnostics,
3298
3310
  payload: projectWorkflowHumanActionLifecyclePayload(request),
3299
3311
  });
3300
- return projectWorkflowHumanActionDiagnosticResponse("Human action was rejected by wait-token validation", validation.diagnostics, validation.httpStatus, waitToken, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3312
+ return projectWorkflowHumanActionDiagnosticResponse("Human action was rejected by wait-token validation", validation.diagnostics, validation.httpStatus, responseWaitToken, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3301
3313
  }
3302
3314
  try {
3303
3315
  const result = input.state.projectService.resolveProjectWorkflowHumanAction({
@@ -3347,7 +3359,8 @@ function submitProjectWorkflowHumanAction(input) {
3347
3359
  diagnostics,
3348
3360
  payload: projectWorkflowHumanActionLifecyclePayload(request),
3349
3361
  });
3350
- return projectWorkflowHumanActionDiagnosticResponse("Human action was rejected by wait-token validation", diagnostics, 409, waitToken, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3362
+ const responseWaitToken = input.state.projectService.getProjectWorkflowWaitToken(request.waitTokenId) ?? waitToken;
3363
+ return projectWorkflowHumanActionDiagnosticResponse("Human action was rejected by wait-token validation", diagnostics, 409, responseWaitToken, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3351
3364
  }
3352
3365
  }
3353
3366
  function isProjectWorkflowBackedSession(projectSession) {
@@ -3374,6 +3387,7 @@ async function sendProjectMessage(input) {
3374
3387
  const projectSession = input.state.projectService.getProjectSession(selectedSession.id);
3375
3388
  if (!projectSession)
3376
3389
  throw new PiboWebHttpError("Project session not found", 404);
3390
+ requireSharedProject(input.state, input.webSession, projectSession.projectId);
3377
3391
  const actorId = auditActorIdFor(input.webSession);
3378
3392
  const duplicate = clientTxnId ? input.state.eventCommands.findByClientTxn(undefined, actorId, clientTxnId) : undefined;
3379
3393
  if (duplicate)
@@ -3638,6 +3652,7 @@ async function sendChatMessage(input) {
3638
3652
  }
3639
3653
  export function createChatWebApp(options = {}) {
3640
3654
  const integrations = resolveChatWebIntegrations(options);
3655
+ const piPackageStoreCwd = options.piPackageStoreCwd ?? process.cwd();
3641
3656
  ensurePrivateChatUploadDirectory();
3642
3657
  const defaultProfile = options.defaultProfile ?? "base";
3643
3658
  const dataStore = createDataStore(options);
@@ -3934,6 +3949,7 @@ export function createChatWebApp(options = {}) {
3934
3949
  context,
3935
3950
  webSession,
3936
3951
  piboSessionId: url.searchParams.get("piboSessionId") || undefined,
3952
+ piPackageStoreCwd,
3937
3953
  });
3938
3954
  return responseJson({ snapshot });
3939
3955
  }
@@ -4691,6 +4707,7 @@ export function createChatWebApp(options = {}) {
4691
4707
  state.syncedUserSkillNames = names;
4692
4708
  },
4693
4709
  invalidateBootstrapCatalogCache: () => invalidateBootstrapCatalogCache(state),
4710
+ agentsSelectingSkill: (skillName) => agentsSelectingSkill(state, skillName),
4694
4711
  });
4695
4712
  }
4696
4713
  if (url.pathname === `${CHAT_WEB_API_PREFIX}/agent-folders` && request.method === "GET") {