@pasko70/pibo 3.4.4 → 3.5.1

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 (91) hide show
  1. package/dist/agent-runtime/routed-session.js +111 -25
  2. package/dist/agent-runtimes/codex-native/turn.js +27 -3
  3. package/dist/agent-runtimes/pi/adapter.js +1 -0
  4. package/dist/agent-runtimes/pi/routed-session.js +2 -2
  5. package/dist/apps/chat/bounded-event-stream.js +98 -0
  6. package/dist/apps/chat/chat-settings-routes.js +3 -3
  7. package/dist/apps/chat/data/chat-data-mappers.js +40 -14
  8. package/dist/apps/chat/data/event-command-service.js +30 -21
  9. package/dist/apps/chat/data/history-query-service.js +34 -25
  10. package/dist/apps/chat/data/read-state-service.js +37 -3
  11. package/dist/apps/chat/data/session-query-service.js +19 -11
  12. package/dist/apps/chat/data/timeline-query-service.js +19 -7
  13. package/dist/apps/chat/message-command-dispatcher.js +149 -0
  14. package/dist/apps/chat/output-compactor.js +9 -0
  15. package/dist/apps/chat/output-event-policy.js +9 -1
  16. package/dist/apps/chat/stream.js +21 -3
  17. package/dist/apps/chat/telemetry-retention-service.js +113 -8
  18. package/dist/apps/chat/trace-response-cache.js +46 -0
  19. package/dist/apps/chat/trace-v2.js +12 -6
  20. package/dist/apps/chat/trace.js +1 -1
  21. package/dist/apps/chat/web-app.js +645 -297
  22. package/dist/apps/chat-ui/assets/{dist-V06sfuZa.js → dist-B-auLrzD.js} +1 -1
  23. package/dist/apps/chat-ui/assets/{dist-CH3SvpYV.js → dist-BA_dsINH.js} +1 -1
  24. package/dist/apps/chat-ui/assets/{dist-23lt_7qm.js → dist-eJZar_0-.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{dist-Bf2KScPo.js → dist-wNNR2Bci.js} +1 -1
  26. package/dist/apps/chat-ui/assets/{dist-DsgL8w-W.js → dist-zcmEsIEp.js} +1 -1
  27. package/dist/apps/chat-ui/assets/index-DEkbN5Vo.js +229 -0
  28. package/dist/apps/chat-ui/assets/index-DZK6Tzil.css +1 -0
  29. package/dist/apps/chat-ui/index.html +2 -2
  30. package/dist/apps/chat-vscode-web/assets/index-DZgW1fCB.js +44 -0
  31. package/dist/apps/chat-vscode-web/index.html +1 -1
  32. package/dist/cli-session/localSessionSource.js +3 -2
  33. package/dist/compute/pool/seeds.js +25 -3
  34. package/dist/core/events.js +4 -0
  35. package/dist/core/output-render-sequence.js +65 -6
  36. package/dist/core/provider-capacity.js +33 -0
  37. package/dist/core/provider-telemetry.js +21 -6
  38. package/dist/core/runtime-capacity.js +174 -0
  39. package/dist/core/runtime-telemetry.js +40 -12
  40. package/dist/core/session-router.js +178 -13
  41. package/dist/data/async-chat-reads.js +38 -0
  42. package/dist/data/async-chat-storage.js +96 -0
  43. package/dist/data/async-telemetry-maintenance.js +9 -0
  44. package/dist/data/bounded-worker-client.js +256 -0
  45. package/dist/data/chat-read-projections.js +159 -0
  46. package/dist/data/chat-read-worker.js +73 -0
  47. package/dist/data/chat-storage-worker.js +167 -0
  48. package/dist/data/ingest-service.js +152 -18
  49. package/dist/data/message-command-store.js +290 -0
  50. package/dist/data/payload-store.js +92 -11
  51. package/dist/data/pibo-store.js +10 -8
  52. package/dist/data/schema.js +39 -4
  53. package/dist/data/session-store.js +3 -1
  54. package/dist/data/storage-backup.js +278 -0
  55. package/dist/data/storage-maintenance.js +344 -0
  56. package/dist/data/storage-verification-worker.js +25 -0
  57. package/dist/data/telemetry-capture.js +188 -0
  58. package/dist/data/telemetry-command.js +3 -0
  59. package/dist/data/telemetry-maintenance-worker.js +40 -0
  60. package/dist/data/telemetry-maintenance.js +110 -0
  61. package/dist/data/telemetry-retention.js +16 -7
  62. package/dist/data/telemetry-worker.js +111 -0
  63. package/dist/data/telemetry-writer.js +150 -83
  64. package/dist/data/telemetry.js +5 -0
  65. package/dist/debug/index.js +207 -1
  66. package/dist/debug/message-queue.js +108 -0
  67. package/dist/debug/output-collision-repair.js +140 -0
  68. package/dist/debug/output-integrity.js +38 -2
  69. package/dist/debug/output-repair.js +1 -0
  70. package/dist/debug/storage-backup.js +41 -0
  71. package/dist/debug/storage-maintenance.js +78 -0
  72. package/dist/debug/telemetry-capture.js +66 -0
  73. package/dist/gateway/cli.js +71 -7
  74. package/dist/gateway/server.js +3 -0
  75. package/dist/providers/openai-gpt56.js +11 -6
  76. package/dist/reliability/store.js +119 -23
  77. package/dist/session-ui/terminalRows.js +54 -13
  78. package/dist/sessions/pibo-data-store.js +19 -14
  79. package/dist/shared/debug-features.js +4 -0
  80. package/dist/shared/model-inference-metrics.js +23 -0
  81. package/dist/shared/trace-event-projection.js +69 -2
  82. package/dist/shared/trace-history.js +9 -0
  83. package/dist/shared/trace-live-reducer.js +1 -0
  84. package/dist/shared/trace-patch-nodes.js +19 -0
  85. package/dist/web/channel.js +117 -12
  86. package/dist/web/http.js +135 -41
  87. package/npm-shrinkwrap.json +2 -2
  88. package/package.json +1 -1
  89. package/dist/apps/chat-ui/assets/index-BOceJ0jM.css +0 -1
  90. package/dist/apps/chat-ui/assets/index-BOemYq-V.js +0 -228
  91. package/dist/apps/chat-vscode-web/assets/index-CMwTB8o8.js +0 -43
@@ -1,3 +1,5 @@
1
+ import { AsyncTelemetryMaintenance } from "../../data/async-telemetry-maintenance.js";
2
+ import { TelemetryMaintenance } from "../../data/telemetry-maintenance.js";
1
3
  import { telemetryRetentionCutoff, } from "../../core/telemetry-retention-settings.js";
2
4
  export const TELEMETRY_RETENTION_CLASSES = [
3
5
  "live",
@@ -7,14 +9,33 @@ export const TELEMETRY_RETENTION_CLASSES = [
7
9
  "incident",
8
10
  ];
9
11
  export const DEFAULT_TELEMETRY_RETENTION_MAINTENANCE_INTERVAL_MS = 24 * 60 * 60 * 1000;
12
+ /** Bounded recovery: back off a failing maintenance job, then leave it visible instead of respawning. */
13
+ export const TELEMETRY_RETENTION_MAINTENANCE_FAILURE_LIMIT = 8;
14
+ export const TELEMETRY_RETENTION_MAINTENANCE_MAX_RETRY_MS = 30_000;
15
+ export async function disposeTelemetryRetentionMaintenance(state) {
16
+ state.disposed = true;
17
+ if (state.timer)
18
+ clearTimeout(state.timer);
19
+ state.timer = undefined;
20
+ state.running = false;
21
+ await state.worker?.close();
22
+ state.worker = undefined;
23
+ }
10
24
  export function pruneTelemetryOlderThan(input) {
11
25
  const cutoff = telemetryRetentionCutoff(input.days, input.now);
12
- const results = TELEMETRY_RETENTION_CLASSES.map((retentionClass) => input.dataStore.telemetry.prune({ retentionClass, before: cutoff, apply: input.apply }));
26
+ const results = [];
27
+ for (const retentionClass of TELEMETRY_RETENTION_CLASSES) {
28
+ const result = input.dataStore.telemetry.prune({ retentionClass, before: cutoff, apply: input.apply });
29
+ results.push(result);
30
+ if (result.completed === false)
31
+ break;
32
+ }
13
33
  return {
14
34
  cutoff,
15
35
  days: input.days,
16
36
  applied: input.apply === true,
17
37
  results,
38
+ completed: results.every(result => result.completed !== false),
18
39
  rowsDeleted: results.reduce((sum, result) => sum + result.rowsDeleted, 0),
19
40
  bytesMatched: results.reduce((sum, result) => sum + result.bytesMatched, 0),
20
41
  };
@@ -27,6 +48,8 @@ export function isTelemetryRetentionMaintenanceDue(input) {
27
48
  return !(input.state.lastCheckedAt && now.getTime() - input.state.lastCheckedAt < intervalMs);
28
49
  }
29
50
  export function maybeRunTelemetryRetentionMaintenance(input) {
51
+ if (input.state.disposed)
52
+ return;
30
53
  if (!input.settings.enabled)
31
54
  return;
32
55
  if (input.state.running)
@@ -39,19 +62,70 @@ export function maybeRunTelemetryRetentionMaintenance(input) {
39
62
  input.state.lastCheckedAt = now.getTime();
40
63
  return;
41
64
  }
42
- if (hasActiveRuntimeWork(input.context))
43
- return;
44
65
  input.state.lastCheckedAt = now.getTime();
45
66
  input.state.running = true;
46
- setTimeout(() => {
67
+ const cutoff = telemetryRetentionCutoff(input.settings.days, now);
68
+ const local = input.dataStore.path === ":memory:" ? new TelemetryMaintenance(input.dataStore.db) : undefined;
69
+ let consecutiveFailures = 0;
70
+ const run = async () => {
71
+ let failed = false;
47
72
  try {
48
- pruneTelemetryOlderThan({ dataStore: input.dataStore, days: input.settings.days, now, apply: true });
49
- input.onPruned?.(now.toISOString());
73
+ if (input.state.disposed)
74
+ return;
75
+ if (local) {
76
+ if (!local.status() || local.status()?.status === "completed" || local.status()?.status === "cancelled")
77
+ local.start(cutoff);
78
+ local.step();
79
+ }
80
+ else if (!input.state.worker) {
81
+ input.state.worker = new AsyncTelemetryMaintenance(input.dataStore.path);
82
+ await input.state.worker.command("start", cutoff);
83
+ }
84
+ const status = local?.status() ?? await input.state.worker.command("status");
85
+ if (input.state.disposed)
86
+ return;
87
+ if (status?.status === "completed") {
88
+ input.onPruned?.(now.toISOString());
89
+ input.state.running = false;
90
+ await input.state.worker?.close();
91
+ input.state.worker = undefined;
92
+ return;
93
+ }
94
+ if (status?.status === "cancelled" || status?.status === "paused") {
95
+ input.state.running = false;
96
+ await input.state.worker?.close();
97
+ input.state.worker = undefined;
98
+ return;
99
+ }
100
+ }
101
+ catch {
102
+ failed = true;
103
+ input.state.failures = (input.state.failures ?? 0) + 1;
104
+ await input.state.worker?.close();
105
+ input.state.worker = undefined;
106
+ }
107
+ if (input.state.disposed)
108
+ return;
109
+ if (!failed) {
110
+ consecutiveFailures = 0;
111
+ input.state.timer = setTimeout(() => { input.state.timer = undefined; void run(); }, local ? 25 : 250);
112
+ input.state.timer.unref?.();
113
+ return;
50
114
  }
51
- finally {
115
+ // A persistent failure must not respawn a worker thread every 250 ms forever. Back off, then stop
116
+ // with the failure count visible so a later due check retries instead of burning one worker per attempt.
117
+ consecutiveFailures++;
118
+ if (consecutiveFailures >= TELEMETRY_RETENTION_MAINTENANCE_FAILURE_LIMIT) {
52
119
  input.state.running = false;
120
+ input.state.timer = undefined;
121
+ return;
53
122
  }
54
- }, 0).unref?.();
123
+ const backoffMs = Math.min(TELEMETRY_RETENTION_MAINTENANCE_MAX_RETRY_MS, 250 * 2 ** (consecutiveFailures - 1));
124
+ input.state.timer = setTimeout(() => { input.state.timer = undefined; void run(); }, backoffMs);
125
+ input.state.timer.unref?.();
126
+ };
127
+ input.state.timer = setTimeout(() => { input.state.timer = undefined; void run(); }, 0);
128
+ input.state.timer.unref?.();
55
129
  }
56
130
  export function isPersistentRetentionDue(lastPrunedAt, now, intervalMs) {
57
131
  if (!lastPrunedAt)
@@ -67,3 +141,34 @@ export function hasActiveRuntimeWork(context) {
67
141
  ?? [];
68
142
  return statuses.some((status) => Boolean(status?.processing || status?.streaming || (status?.queuedMessages ?? 0) > 0));
69
143
  }
144
+ /** Explicit HTTP maintenance keeps SQL off the gateway and yields between every bounded batch. */
145
+ export async function pruneTelemetryOlderThanAsync(input) {
146
+ if (input.dataStore.path === ":memory:")
147
+ return pruneTelemetryOlderThan(input);
148
+ const cutoff = telemetryRetentionCutoff(input.days), worker = new AsyncTelemetryMaintenance(input.dataStore.path);
149
+ const abort = () => { void worker.close(); };
150
+ input.signal?.addEventListener("abort", abort, { once: true });
151
+ try {
152
+ if (input.signal?.aborted)
153
+ throw Error("Maintenance request cancelled");
154
+ if (!input.apply) {
155
+ const results = await worker.preview(cutoff);
156
+ return { cutoff, days: input.days, applied: false, results, rowsDeleted: 0, bytesMatched: results.reduce((n, r) => n + r.bytesMatched, 0), completed: true };
157
+ }
158
+ let status = await worker.command("start", cutoff);
159
+ if (status?.status === "paused")
160
+ status = await worker.command("resume");
161
+ const until = Date.now() + 30000;
162
+ while (status?.status === "running" && Date.now() < until) {
163
+ await new Promise(resolve => setTimeout(resolve, 100));
164
+ status = await worker.command("status");
165
+ }
166
+ if (status?.status === "running")
167
+ status = await worker.command("pause");
168
+ return { cutoff: status?.cutoff ?? cutoff, days: input.days, applied: true, results: [], rowsDeleted: status?.deleted ?? 0, bytesMatched: 0, completed: status?.status === "completed" };
169
+ }
170
+ finally {
171
+ input.signal?.removeEventListener("abort", abort);
172
+ await worker.close();
173
+ }
174
+ }
@@ -0,0 +1,46 @@
1
+ /** Exact encoded responses; bounded independently from reconstructed trace caches. */
2
+ export class TraceResponseCache {
3
+ maximumBytes;
4
+ maximumEntries;
5
+ entries = new Map();
6
+ bytes = 0;
7
+ constructor(maximumBytes = 8 * 1024 * 1024, maximumEntries = 32) {
8
+ this.maximumBytes = maximumBytes;
9
+ this.maximumEntries = maximumEntries;
10
+ }
11
+ get(key, request) {
12
+ const entry = this.entries.get(key);
13
+ if (!entry)
14
+ return;
15
+ this.entries.delete(key);
16
+ this.entries.set(key, entry);
17
+ const headers = new Headers(entry.headers);
18
+ headers.set("server-timing", 'trace_cache;desc="watermark-hit"');
19
+ const etag = headers.get("etag");
20
+ const requested = request.headers.get("if-none-match");
21
+ if (etag && requested?.split(',').some(v => v.trim() === '*' || v.trim() === etag || v.trim() === 'W/' + etag))
22
+ return new Response(null, { status: 304, headers });
23
+ return new Response(entry.body.slice(), { status: entry.status, headers });
24
+ }
25
+ async set(key, response) {
26
+ if (response.status !== 200 || !response.headers.get("content-type")?.includes("json"))
27
+ return;
28
+ const body = new Uint8Array(await response.clone().arrayBuffer());
29
+ const bytes = body.byteLength + Buffer.byteLength(key);
30
+ if (bytes > Math.min(this.maximumBytes, 1024 * 1024))
31
+ return;
32
+ const previous = this.entries.get(key);
33
+ if (previous)
34
+ this.bytes -= previous.bytes;
35
+ this.entries.delete(key);
36
+ this.entries.set(key, { body, headers: [...response.headers.entries()], status: response.status, bytes });
37
+ this.bytes += bytes;
38
+ while (this.bytes > this.maximumBytes || this.entries.size > this.maximumEntries) {
39
+ const oldest = this.entries.keys().next().value;
40
+ this.bytes -= this.entries.get(oldest).bytes;
41
+ this.entries.delete(oldest);
42
+ }
43
+ }
44
+ clear() { this.entries.clear(); this.bytes = 0; }
45
+ status() { return { entries: this.entries.size, bytes: this.bytes, maximumBytes: this.maximumBytes, maximumEntries: this.maximumEntries }; }
46
+ }
@@ -148,17 +148,21 @@ export function looksLikeImagePayloadPreview(value) {
148
148
  return /"type"\s*:\s*"(?:image|input_image|output_image|image_url)"/i.test(value)
149
149
  || /"mime(?:Type|_type)"\s*:\s*"image\//i.test(value);
150
150
  }
151
- export function readTracePayloadChunk(input) {
151
+ export async function readTracePayloadChunk(input) {
152
152
  const parsed = parseTracePayloadRef(input.ref);
153
153
  if (!parsed)
154
154
  return undefined;
155
155
  const payload = input.payloadStore.getPayload(parsed.payloadId);
156
156
  if (!payload)
157
157
  return undefined;
158
- const bytes = Buffer.from(input.payloadStore.readPayloadBytes(parsed.payloadId));
159
- const offset = Math.max(0, Math.min(input.offset, bytes.byteLength));
158
+ const offset = Math.max(0, Math.min(input.offset, payload.byteSize));
160
159
  const limit = Math.max(1, Math.min(input.limit, TRACE_V2_PAYLOAD_MAX_LIMIT_BYTES));
161
- const chunk = bytes.subarray(offset, Math.min(bytes.byteLength, offset + limit));
160
+ const bytes = await input.payloadStore.readPayloadRange(parsed.payloadId, offset, limit + 3);
161
+ let length = Math.min(limit, bytes.length);
162
+ // Include a complete UTF-8 code point at the boundary; cursors remain byte offsets.
163
+ while (length < bytes.length && (bytes[length] & 0xc0) === 0x80)
164
+ length++;
165
+ const chunk = bytes.subarray(0, length);
162
166
  const preview = payload.previewText ?? "";
163
167
  const traceRef = {
164
168
  ref: input.ref,
@@ -168,7 +172,7 @@ export function readTracePayloadChunk(input) {
168
172
  truncatedPreview: Buffer.byteLength(preview, "utf8") < payload.byteSize,
169
173
  hash: payload.sha256,
170
174
  };
171
- const nextOffset = offset + chunk.byteLength < bytes.byteLength ? offset + chunk.byteLength : undefined;
175
+ const nextOffset = offset + chunk.byteLength < payload.byteSize ? offset + chunk.byteLength : undefined;
172
176
  return {
173
177
  ref: traceRef,
174
178
  offset,
@@ -248,6 +252,8 @@ function compactTraceNode(node, payloadStore, piboSessionId, depth) {
248
252
  completedAt: node.completedAt,
249
253
  durationMs: node.durationMs,
250
254
  toolMetrics: node.toolMetrics,
255
+ modelInferences: node.modelInferences,
256
+ compactionStats: node.compactionStats,
251
257
  orderKey: node.orderKey,
252
258
  depth,
253
259
  hasChildren: node.children.length > 0,
@@ -268,7 +274,7 @@ function compactTraceNode(node, payloadStore, piboSessionId, depth) {
268
274
  });
269
275
  }
270
276
  function inlinePayloadForNodeValue(node, kind, value) {
271
- return inlinePayloadForValue(value, inlinePayloadByteLimit(node, kind));
277
+ return node.payloadRefs?.[kind] ? undefined : inlinePayloadForValue(value, inlinePayloadByteLimit(node, kind));
272
278
  }
273
279
  function inlinePayloadByteLimit(node, kind) {
274
280
  if ((node.type === "user.message" || node.type === "assistant.message" || node.type === "model.reasoning") && kind !== "error") {
@@ -5,7 +5,7 @@ import { isBuiltInHistoryReconciliationProof } from "../../agent-runtimes/histor
5
5
  import { buildTraceViewFromEvents, traceNodesFromHistoryEntries } from "../../shared/trace-engine.js";
6
6
  import { chatWebSessionSidebarOrder, isChatWebSessionArchived, isChatWebSessionPinned } from "./session-metadata.js";
7
7
  import { workflowSessionKindFromMetadata } from "../../sessions/workflow-session-kind.js";
8
- export const TRACE_PROJECTION_VERSION = "runtime-history-v2-incomplete-status";
8
+ export const TRACE_PROJECTION_VERSION = "runtime-history-v2-debug-metrics";
9
9
  export { compareTraceNodes, sortTraceNodes, nestTraceNodes, flattenTraceNodes, mapTraceNodesById, buildTraceViewFromEvents, traceNodesFromHistoryEntries, } from "../../shared/trace-engine.js";
10
10
  export const TRACE_TRANSCRIPT_TAIL_MAX_BYTES = PI_HISTORY_TAIL_MAX_BYTES;
11
11
  export const TRACE_TRANSCRIPT_HISTORY_PAGE_MAX_BYTES = PI_HISTORY_PAGE_MAX_BYTES;