@pasko70/pibo 3.5.0 → 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 (46) hide show
  1. package/dist/agent-runtime/routed-session.js +80 -19
  2. package/dist/agent-runtimes/codex-native/turn.js +27 -3
  3. package/dist/apps/chat/data/chat-data-mappers.js +8 -1
  4. package/dist/apps/chat/data/read-state-service.js +24 -3
  5. package/dist/apps/chat/message-command-dispatcher.js +16 -1
  6. package/dist/apps/chat/web-app.js +40 -19
  7. package/dist/apps/chat-ui/assets/{dist-D79vyxSX.js → dist-B-auLrzD.js} +1 -1
  8. package/dist/apps/chat-ui/assets/{dist-DFZ8cwh0.js → dist-BA_dsINH.js} +1 -1
  9. package/dist/apps/chat-ui/assets/{dist-D6TjFhAm.js → dist-eJZar_0-.js} +1 -1
  10. package/dist/apps/chat-ui/assets/{dist-BG0n7zLd.js → dist-wNNR2Bci.js} +1 -1
  11. package/dist/apps/chat-ui/assets/{dist-cOjokPrK.js → dist-zcmEsIEp.js} +1 -1
  12. package/dist/apps/chat-ui/assets/{index-RMHUTJ62.js → index-DEkbN5Vo.js} +43 -43
  13. package/dist/apps/chat-ui/assets/index-DZK6Tzil.css +1 -0
  14. package/dist/apps/chat-ui/index.html +2 -2
  15. package/dist/apps/chat-vscode-web/assets/{index-xacbCyTx.js → index-DZgW1fCB.js} +11 -11
  16. package/dist/apps/chat-vscode-web/index.html +1 -1
  17. package/dist/cli-session/localSessionSource.js +3 -2
  18. package/dist/core/output-render-sequence.js +63 -6
  19. package/dist/core/session-router.js +99 -11
  20. package/dist/data/async-chat-storage.js +7 -2
  21. package/dist/data/bounded-worker-client.js +1 -1
  22. package/dist/data/chat-read-projections.js +4 -4
  23. package/dist/data/chat-storage-worker.js +24 -3
  24. package/dist/data/ingest-service.js +73 -4
  25. package/dist/data/message-command-store.js +153 -11
  26. package/dist/data/schema.js +24 -3
  27. package/dist/data/storage-maintenance.js +344 -0
  28. package/dist/data/storage-verification-worker.js +25 -0
  29. package/dist/debug/index.js +155 -1
  30. package/dist/debug/message-queue.js +108 -0
  31. package/dist/debug/output-collision-repair.js +140 -0
  32. package/dist/debug/output-integrity.js +38 -2
  33. package/dist/debug/output-repair.js +1 -0
  34. package/dist/debug/storage-backup.js +12 -4
  35. package/dist/debug/storage-maintenance.js +78 -0
  36. package/dist/gateway/cli.js +71 -7
  37. package/dist/gateway/server.js +1 -0
  38. package/dist/reliability/store.js +119 -23
  39. package/dist/session-ui/terminalRows.js +7 -8
  40. package/dist/sessions/pibo-data-store.js +18 -14
  41. package/dist/shared/trace-event-projection.js +13 -3
  42. package/dist/web/channel.js +114 -12
  43. package/dist/web/http.js +105 -44
  44. package/npm-shrinkwrap.json +2 -2
  45. package/package.json +1 -1
  46. package/dist/apps/chat-ui/assets/index-hEkrlRk-.css +0 -1
package/dist/web/http.js CHANGED
@@ -78,74 +78,136 @@ export async function nodeRequestToWebRequest(request, baseURL, signal) {
78
78
  });
79
79
  }
80
80
  export async function sendWebResponse(response, webResponse, options = {}) {
81
+ assertResponseCanStart(response);
81
82
  const headers = responseHeaders(webResponse);
82
83
  const compressEncoding = preferredResponseEncoding(response.req?.headers["accept-encoding"], webResponse);
83
- if (compressEncoding && webResponse.body) {
84
- const body = await readResponseBody(webResponse);
85
- if (body.length >= MIN_COMPRESS_RESPONSE_BYTES && body.length <= MAX_SYNC_GZIP_RESPONSE_BYTES) {
86
- const compressionStartedAt = performance.now();
87
- const compressed = gzipSync(body, { level: 1 });
88
- appendServerTimingHeader(headers, `response_compress;dur=${(performance.now() - compressionStartedAt).toFixed(1)}`);
89
- headers["content-encoding"] = compressEncoding;
90
- headers["content-length"] = String(compressed.length);
91
- headers.vary = appendVary(headers.vary, "accept-encoding");
84
+ let reader = webResponse.body?.getReader();
85
+ let wroteHeaders = false;
86
+ let clientClosed = false;
87
+ const cancelReader = () => {
88
+ if (reader)
89
+ void reader.cancel().catch(() => undefined);
90
+ };
91
+ const close = () => {
92
+ clientClosed = true;
93
+ cancelReader();
94
+ };
95
+ const abort = () => {
96
+ cancelReader();
97
+ try {
98
+ if (!response.destroyed && !response.writableEnded)
99
+ response.end();
100
+ }
101
+ catch {
102
+ tryDestroyResponse(response);
103
+ }
104
+ try {
105
+ response.socket?.end();
106
+ }
107
+ catch {
108
+ tryDestroyResponse(response);
109
+ }
110
+ };
111
+ if (reader) {
112
+ response.once("close", close);
113
+ options.signal?.addEventListener("abort", abort, { once: true });
114
+ if (options.signal?.aborted)
115
+ abort();
116
+ }
117
+ try {
118
+ if (compressEncoding && reader) {
119
+ const body = await readResponseBody(reader);
120
+ if (clientClosed || options.signal?.aborted || response.destroyed || response.writableEnded || response.writableFinished)
121
+ return;
122
+ assertResponseCanStart(response);
123
+ if (body.length >= MIN_COMPRESS_RESPONSE_BYTES && body.length <= MAX_SYNC_GZIP_RESPONSE_BYTES) {
124
+ const compressionStartedAt = performance.now();
125
+ const compressed = gzipSync(body, { level: 1 });
126
+ appendServerTimingHeader(headers, `response_compress;dur=${(performance.now() - compressionStartedAt).toFixed(1)}`);
127
+ headers["content-encoding"] = compressEncoding;
128
+ headers["content-length"] = String(compressed.length);
129
+ headers.vary = appendVary(headers.vary, "accept-encoding");
130
+ response.writeHead(webResponse.status, headers);
131
+ wroteHeaders = true;
132
+ response.end(compressed);
133
+ return;
134
+ }
135
+ if (body.length > MAX_SYNC_GZIP_RESPONSE_BYTES) {
136
+ headers["x-pibo-compression-skipped"] = "sync-gzip-size-limit";
137
+ }
92
138
  response.writeHead(webResponse.status, headers);
93
- response.end(compressed);
139
+ wroteHeaders = true;
140
+ response.end(body);
94
141
  return;
95
142
  }
96
- if (body.length > MAX_SYNC_GZIP_RESPONSE_BYTES) {
97
- headers["x-pibo-compression-skipped"] = "sync-gzip-size-limit";
98
- }
99
143
  response.writeHead(webResponse.status, headers);
100
- response.end(body);
101
- return;
102
- }
103
- response.writeHead(webResponse.status, headers);
104
- if (!webResponse.body) {
105
- response.end();
106
- return;
107
- }
108
- const reader = webResponse.body.getReader();
109
- const cancel = () => {
110
- void reader.cancel().catch(() => undefined);
111
- };
112
- const abort = () => {
113
- const socket = response.socket;
114
- void reader.cancel().catch(() => undefined);
115
- if (!response.writableEnded)
144
+ wroteHeaders = true;
145
+ if (!reader) {
116
146
  response.end();
117
- socket?.end();
118
- };
119
- response.once("close", cancel);
120
- options.signal?.addEventListener("abort", abort, { once: true });
121
- if (options.signal?.aborted)
122
- abort();
123
- try {
147
+ return;
148
+ }
124
149
  while (true) {
125
150
  const { done, value } = await reader.read();
126
151
  if (done)
127
152
  break;
128
153
  const bytes = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
129
154
  for (let offset = 0; offset < bytes.length; offset += 64 * 1024) {
130
- if (response.destroyed || response.writableEnded)
155
+ if (response.destroyed || response.writableEnded || response.writableFinished) {
156
+ cancelReader();
131
157
  return;
158
+ }
132
159
  if (!response.write(bytes.subarray(offset, offset + 64 * 1024))) {
133
160
  const drained = await waitForResponseDrain(response, (webResponse.headers.get("content-type") ?? "").startsWith("text/event-stream") ? 5000 : undefined, options.signal);
134
161
  if (!drained) {
135
- void reader.cancel().catch(() => undefined);
136
- if (!response.destroyed)
137
- response.destroy();
162
+ cancelReader();
163
+ tryDestroyResponse(response);
138
164
  return;
139
165
  }
140
166
  }
141
167
  }
142
168
  }
143
- if (!response.writableEnded)
169
+ if (!response.destroyed && !response.writableEnded && !response.writableFinished)
144
170
  response.end();
145
171
  }
172
+ catch (error) {
173
+ cancelReader();
174
+ if (clientClosed || response.destroyed || response.writableEnded || response.writableFinished)
175
+ return;
176
+ if (wroteHeaders || response.headersSent)
177
+ tryDestroyResponse(response, error);
178
+ throw error;
179
+ }
146
180
  finally {
147
- response.off("close", cancel);
181
+ response.off("close", close);
148
182
  options.signal?.removeEventListener("abort", abort);
183
+ if (reader) {
184
+ try {
185
+ reader.releaseLock();
186
+ }
187
+ catch {
188
+ // Cancellation/read settlement owns the lock until the pending read resolves.
189
+ }
190
+ }
191
+ }
192
+ }
193
+ function assertResponseCanStart(response) {
194
+ if (response.destroyed)
195
+ throw new Error("HTTP response is destroyed");
196
+ if (response.writableEnded)
197
+ throw new Error("HTTP response has ended");
198
+ if (response.writableFinished)
199
+ throw new Error("HTTP response has finished");
200
+ if (response.headersSent)
201
+ throw new Error("HTTP response headers were already sent");
202
+ }
203
+ function tryDestroyResponse(response, error) {
204
+ if (response.destroyed)
205
+ return;
206
+ try {
207
+ response.destroy(error instanceof Error ? error : undefined);
208
+ }
209
+ catch {
210
+ // The request boundary is best-effort once the response implementation itself fails.
149
211
  }
150
212
  }
151
213
  function appendServerTimingHeader(headers, value) {
@@ -198,9 +260,8 @@ function responseCanBeCompressed(webResponse) {
198
260
  const contentType = webResponse.headers.get("content-type") ?? "";
199
261
  return /^application\/json\b/.test(contentType);
200
262
  }
201
- async function readResponseBody(webResponse) {
263
+ async function readResponseBody(reader) {
202
264
  const chunks = [];
203
- const reader = webResponse.body.getReader();
204
265
  while (true) {
205
266
  const { done, value } = await reader.read();
206
267
  if (done)
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@pasko70/pibo",
3
- "version": "3.5.0",
3
+ "version": "3.5.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@pasko70/pibo",
9
- "version": "3.5.0",
9
+ "version": "3.5.1",
10
10
  "workspaces": [
11
11
  "packages/workflows"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pasko70/pibo",
3
- "version": "3.5.0",
3
+ "version": "3.5.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",