@pasko70/pibo 1.13.0 → 1.14.0
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.
- package/dist/apps/chat/static-assets.js +3 -1
- package/dist/apps/chat/web-app.js +10 -0
- package/dist/apps/chat-ui/assets/{dist-B_8EcoB6.js → dist-1eQpSfYj.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DVPXynJE.js → dist-BG1J2hGI.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-UYBI2F8E.js → dist-BIwXuAMQ.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-D6pYe6d_.js → chat-ui/assets/dist-BeCHnL7P.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CVLKu7NV.js → dist-CtsdsIpj.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DugRsLaG.js → dist-DfdQQObC.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-BFRDpzcv.js → chat-ui/assets/dist-DmqwP_tM.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-B5mpygOZ.js → chat-ui/assets/dist-LWXXqiGT.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-B_zXPNMb.js → chat-ui/assets/dist-hoDORUlL.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-XmG4qG-A.js → dist-ur4yXrlj.js} +1 -1
- package/dist/apps/chat-ui/assets/index-CoCl8FG8.css +1 -0
- package/dist/apps/chat-ui/assets/index-DLNTzhdh.js +237 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-R_7kgvvD.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/{chat-ui/assets/dist-CnX234jg.js → context-files-ui/assets/dist-B7gXtTZC.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-CELRUR8b.js → context-files-ui/assets/dist-BD262Ouw.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-Em3LWmNi.js → dist-BKuGdcnX.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-CVmpu9FK.js → context-files-ui/assets/dist-C-Jm45P4.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-DM6yvMZv.js → dist-CHdx1Jri.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-BLI6sQZG.js → dist-CULMH-fB.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-CLrlJjPP.js → dist-Cjq6RWEt.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-D8ppb7gk.js → dist-CntqFySz.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-SEP88Pg7.js → dist-DxEGebCR.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-KOXty1JB.js → context-files-ui/assets/dist-kPuQud7t.js} +1 -1
- package/dist/apps/context-files-ui/assets/index-DZWsbuvg.js +202 -0
- package/dist/apps/context-files-ui/index.html +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.14.0.vsix +0 -0
- package/dist/cli-session/localSessionSource.js +2 -0
- package/dist/core/session-router.js +3 -0
- package/dist/gateway/client.js +8 -0
- package/dist/gateway/server.js +18 -2
- package/dist/gateway/web.js +1 -0
- package/dist/local/extension.js +2 -0
- package/dist/loops/plugin.js +34 -1
- package/dist/loops/service.js +85 -0
- package/dist/mcp/config-command.js +5 -3
- package/dist/plugins/context-files-store.js +287 -101
- package/dist/plugins/context-files.js +102 -154
- package/dist/shared/trace-live-reducer.js +6 -1
- package/dist/web/channel.js +105 -16
- package/dist/web/http.js +14 -2
- package/package.json +11 -7
- package/dist/apps/chat-ui/assets/index-DhFQ1th-.js +0 -237
- package/dist/apps/chat-ui/assets/index-hjygk9Ol.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-1wWonouX.js +0 -41
- package/dist/apps/context-files-ui/assets/index-C7X2UvWe.js +0 -202
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.13.0.vsix +0 -0
package/dist/web/channel.js
CHANGED
|
@@ -6,6 +6,7 @@ import { PiboWebHttpError, nodeRequestToWebRequest, responseHtml, responseJson,
|
|
|
6
6
|
export const DEFAULT_WEB_CHANNEL_HOST = "127.0.0.1";
|
|
7
7
|
export const DEFAULT_WEB_CHANNEL_PORT = 4788;
|
|
8
8
|
export const WEB_CHANNEL_NAME = "web-host";
|
|
9
|
+
const DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS = 5_000;
|
|
9
10
|
/**
|
|
10
11
|
* Internal header that carries the TCP socket peer address from the web host
|
|
11
12
|
* channel to the auth plugin. This is one of three independent safety layers
|
|
@@ -168,11 +169,44 @@ function createCanonicalRedirect(request, canonicalBaseURL) {
|
|
|
168
169
|
}
|
|
169
170
|
return redirect(new URL(`${url.pathname}${url.search}`, canonical.origin).toString());
|
|
170
171
|
}
|
|
172
|
+
function isEventStreamResponse(response) {
|
|
173
|
+
return response.headers.get("content-type")?.toLowerCase().startsWith("text/event-stream") === true;
|
|
174
|
+
}
|
|
175
|
+
async function waitForServerClose(closePromise, timeoutMs) {
|
|
176
|
+
return await new Promise((resolve, reject) => {
|
|
177
|
+
let settled = false;
|
|
178
|
+
const timeout = setTimeout(() => {
|
|
179
|
+
settled = true;
|
|
180
|
+
resolve(false);
|
|
181
|
+
}, timeoutMs);
|
|
182
|
+
closePromise.then(() => {
|
|
183
|
+
if (settled)
|
|
184
|
+
return;
|
|
185
|
+
settled = true;
|
|
186
|
+
clearTimeout(timeout);
|
|
187
|
+
resolve(true);
|
|
188
|
+
}, (error) => {
|
|
189
|
+
if (settled)
|
|
190
|
+
return;
|
|
191
|
+
settled = true;
|
|
192
|
+
clearTimeout(timeout);
|
|
193
|
+
reject(error);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
171
197
|
export function createWebHostChannel(options = {}) {
|
|
172
198
|
const host = options.host ?? DEFAULT_WEB_CHANNEL_HOST;
|
|
173
199
|
const port = options.port ?? DEFAULT_WEB_CHANNEL_PORT;
|
|
200
|
+
const shutdownDrainTimeoutMs = options.shutdownDrainTimeoutMs ?? DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS;
|
|
201
|
+
if (!Number.isFinite(shutdownDrainTimeoutMs) || shutdownDrainTimeoutMs < 0) {
|
|
202
|
+
throw new Error("Web channel shutdown drain timeout must be a non-negative finite number");
|
|
203
|
+
}
|
|
174
204
|
let server;
|
|
175
205
|
let context;
|
|
206
|
+
let stopPromise;
|
|
207
|
+
let shuttingDown = false;
|
|
208
|
+
const sockets = new Set();
|
|
209
|
+
const eventStreamControllers = new Map();
|
|
176
210
|
const requireContext = () => {
|
|
177
211
|
if (!context)
|
|
178
212
|
throw new Error("Web channel is not started");
|
|
@@ -185,6 +219,20 @@ export function createWebHostChannel(options = {}) {
|
|
|
185
219
|
}
|
|
186
220
|
return auth.handleRequest(request);
|
|
187
221
|
};
|
|
222
|
+
const sendResponse = async (nodeResponse, webResponse) => {
|
|
223
|
+
const eventStreamController = isEventStreamResponse(webResponse) ? new AbortController() : undefined;
|
|
224
|
+
if (eventStreamController)
|
|
225
|
+
eventStreamControllers.set(nodeResponse, eventStreamController);
|
|
226
|
+
try {
|
|
227
|
+
await sendWebResponse(nodeResponse, webResponse, { signal: eventStreamController?.signal });
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
if (eventStreamController)
|
|
231
|
+
eventStreamControllers.delete(nodeResponse);
|
|
232
|
+
if (shuttingDown)
|
|
233
|
+
server?.closeIdleConnections();
|
|
234
|
+
}
|
|
235
|
+
};
|
|
188
236
|
const handleRequest = async (nodeRequest, nodeResponse) => {
|
|
189
237
|
try {
|
|
190
238
|
const baseURL = createRequestBaseURL(nodeRequest, host, port);
|
|
@@ -198,51 +246,51 @@ export function createWebHostChannel(options = {}) {
|
|
|
198
246
|
const url = new URL(request.url);
|
|
199
247
|
const canonicalRedirect = createCanonicalRedirect(request, options.canonicalBaseURL);
|
|
200
248
|
if (canonicalRedirect) {
|
|
201
|
-
await
|
|
249
|
+
await sendResponse(nodeResponse, canonicalRedirect);
|
|
202
250
|
return;
|
|
203
251
|
}
|
|
204
252
|
if (url.pathname === "/health") {
|
|
205
|
-
await
|
|
253
|
+
await sendResponse(nodeResponse, responseJson({
|
|
206
254
|
status: "ok",
|
|
207
255
|
mode: process.env.PIBO_FALLBACK_MODE === "1" ? "fallback" : "main",
|
|
208
256
|
}));
|
|
209
257
|
return;
|
|
210
258
|
}
|
|
211
259
|
if (url.pathname === "/gateway/status") {
|
|
212
|
-
await
|
|
260
|
+
await sendResponse(nodeResponse, createGatewayStatusResponse(requireContext(), options));
|
|
213
261
|
return;
|
|
214
262
|
}
|
|
215
263
|
if (url.pathname.startsWith("/api/auth/")) {
|
|
216
264
|
const authResponse = stripSocketPeerHeaderFromResponse(await handleAuthRequest(request));
|
|
217
|
-
await
|
|
265
|
+
await sendResponse(nodeResponse, authResponse);
|
|
218
266
|
return;
|
|
219
267
|
}
|
|
220
268
|
const ctx = requireContext();
|
|
221
269
|
const simpleApiResponse = await handleSimpleAgentApiRequest(request, ctx);
|
|
222
270
|
if (simpleApiResponse) {
|
|
223
|
-
await
|
|
271
|
+
await sendResponse(nodeResponse, simpleApiResponse);
|
|
224
272
|
return;
|
|
225
273
|
}
|
|
226
274
|
const apps = ctx.getWebApps();
|
|
227
275
|
const app = apps.find((candidate) => matchPrefix(url.pathname, candidate.mountPath) || matchPrefix(url.pathname, candidate.apiPrefix));
|
|
228
276
|
if (app) {
|
|
229
277
|
const response = await app.handleRequest(request, createAppContext(ctx));
|
|
230
|
-
await
|
|
278
|
+
await sendResponse(nodeResponse, response ?? notFound());
|
|
231
279
|
return;
|
|
232
280
|
}
|
|
233
281
|
if (url.pathname === "/" && apps[0]) {
|
|
234
|
-
await
|
|
282
|
+
await sendResponse(nodeResponse, redirect(apps[0].mountPath));
|
|
235
283
|
return;
|
|
236
284
|
}
|
|
237
285
|
if (url.pathname === "/") {
|
|
238
|
-
await
|
|
286
|
+
await sendResponse(nodeResponse, responseHtml("<!doctype html><title>Pibo</title><p>No web apps registered.</p>"));
|
|
239
287
|
return;
|
|
240
288
|
}
|
|
241
|
-
await
|
|
289
|
+
await sendResponse(nodeResponse, notFound());
|
|
242
290
|
}
|
|
243
291
|
catch (error) {
|
|
244
292
|
const status = error instanceof PiboAuthError || error instanceof PiboWebHttpError ? error.statusCode : 500;
|
|
245
|
-
await
|
|
293
|
+
await sendResponse(nodeResponse, responseJson({ error: error instanceof Error ? error.message : String(error) }, { status }));
|
|
246
294
|
}
|
|
247
295
|
};
|
|
248
296
|
return {
|
|
@@ -251,12 +299,19 @@ export function createWebHostChannel(options = {}) {
|
|
|
251
299
|
description: "Same-origin HTTP host for pibo web apps and auth routes.",
|
|
252
300
|
auth: { mode: "required" },
|
|
253
301
|
async start(channelContext) {
|
|
302
|
+
if (stopPromise)
|
|
303
|
+
await stopPromise;
|
|
254
304
|
if (server)
|
|
255
305
|
return;
|
|
306
|
+
shuttingDown = false;
|
|
256
307
|
context = channelContext;
|
|
257
308
|
server = createServer((request, response) => {
|
|
258
309
|
void handleRequest(request, response);
|
|
259
310
|
});
|
|
311
|
+
server.on("connection", (socket) => {
|
|
312
|
+
sockets.add(socket);
|
|
313
|
+
socket.once("close", () => sockets.delete(socket));
|
|
314
|
+
});
|
|
260
315
|
await new Promise((resolve, reject) => {
|
|
261
316
|
server.once("error", reject);
|
|
262
317
|
server.listen(port, host, () => {
|
|
@@ -270,12 +325,46 @@ export function createWebHostChannel(options = {}) {
|
|
|
270
325
|
}
|
|
271
326
|
},
|
|
272
327
|
async stop() {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
328
|
+
if (stopPromise)
|
|
329
|
+
return await stopPromise;
|
|
330
|
+
if (!server) {
|
|
331
|
+
context = undefined;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const closingServer = server;
|
|
335
|
+
shuttingDown = true;
|
|
336
|
+
stopPromise = (async () => {
|
|
337
|
+
try {
|
|
338
|
+
const closePromise = new Promise((resolve, reject) => {
|
|
339
|
+
closingServer.close((error) => (error ? reject(error) : resolve()));
|
|
340
|
+
});
|
|
341
|
+
const eventStreamCount = eventStreamControllers.size;
|
|
342
|
+
for (const controller of eventStreamControllers.values())
|
|
343
|
+
controller.abort();
|
|
344
|
+
closingServer.closeIdleConnections();
|
|
345
|
+
const drained = await waitForServerClose(closePromise, shutdownDrainTimeoutMs);
|
|
346
|
+
if (!drained) {
|
|
347
|
+
const connectionCount = sockets.size;
|
|
348
|
+
console.warn(`[web-host] graceful shutdown timed out after ${shutdownDrainTimeoutMs} ms; force-closing ${connectionCount} active connection(s)`);
|
|
349
|
+
for (const socket of sockets)
|
|
350
|
+
socket.destroy();
|
|
351
|
+
await closePromise;
|
|
352
|
+
}
|
|
353
|
+
else if (eventStreamCount > 0) {
|
|
354
|
+
console.error(`[web-host] graceful shutdown closed ${eventStreamCount} active event stream(s)`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
finally {
|
|
358
|
+
server = undefined;
|
|
359
|
+
context = undefined;
|
|
360
|
+
shuttingDown = false;
|
|
361
|
+
}
|
|
362
|
+
})();
|
|
363
|
+
try {
|
|
364
|
+
await stopPromise;
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
stopPromise = undefined;
|
|
279
368
|
}
|
|
280
369
|
},
|
|
281
370
|
getAddress() {
|
package/dist/web/http.js
CHANGED
|
@@ -76,7 +76,7 @@ export async function nodeRequestToWebRequest(request, baseURL) {
|
|
|
76
76
|
body,
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
|
-
export async function sendWebResponse(response, webResponse) {
|
|
79
|
+
export async function sendWebResponse(response, webResponse, options = {}) {
|
|
80
80
|
const headers = responseHeaders(webResponse);
|
|
81
81
|
const compressEncoding = preferredResponseEncoding(response.req?.headers["accept-encoding"], webResponse);
|
|
82
82
|
if (compressEncoding && webResponse.body) {
|
|
@@ -108,7 +108,17 @@ export async function sendWebResponse(response, webResponse) {
|
|
|
108
108
|
const cancel = () => {
|
|
109
109
|
void reader.cancel();
|
|
110
110
|
};
|
|
111
|
+
const abort = () => {
|
|
112
|
+
const socket = response.socket;
|
|
113
|
+
void reader.cancel();
|
|
114
|
+
if (!response.writableEnded)
|
|
115
|
+
response.end();
|
|
116
|
+
socket?.end();
|
|
117
|
+
};
|
|
111
118
|
response.once("close", cancel);
|
|
119
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
120
|
+
if (options.signal?.aborted)
|
|
121
|
+
abort();
|
|
112
122
|
try {
|
|
113
123
|
while (true) {
|
|
114
124
|
const { done, value } = await reader.read();
|
|
@@ -116,10 +126,12 @@ export async function sendWebResponse(response, webResponse) {
|
|
|
116
126
|
break;
|
|
117
127
|
response.write(Buffer.from(value));
|
|
118
128
|
}
|
|
119
|
-
response.
|
|
129
|
+
if (!response.writableEnded)
|
|
130
|
+
response.end();
|
|
120
131
|
}
|
|
121
132
|
finally {
|
|
122
133
|
response.off("close", cancel);
|
|
134
|
+
options.signal?.removeEventListener("abort", abort);
|
|
123
135
|
}
|
|
124
136
|
}
|
|
125
137
|
function appendServerTimingHeader(headers, value) {
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasko70/pibo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
5
|
+
"workspaces": [
|
|
6
|
+
"packages/workflows",
|
|
7
|
+
"src/apps/chat-vscode/extension/vscode-shim"
|
|
8
|
+
],
|
|
8
9
|
"description": "Minimal TypeScript wrapper around Pi Coding Agent.",
|
|
9
10
|
"files": [
|
|
10
11
|
"dist",
|
|
@@ -39,16 +40,18 @@
|
|
|
39
40
|
"vscode:webview:build": "vite build --config src/apps/chat-vscode/extension/webview/vite.config.ts",
|
|
40
41
|
"vscode:extension:build": "esbuild src/apps/chat-vscode/extension/src/extension.ts --bundle --platform=node --target=node24 --format=cjs --outfile=src/apps/chat-vscode/dist/extension/extension.cjs --external:vscode --sourcemap=inline",
|
|
41
42
|
"vscode:package": "node scripts/vscode-package.mjs",
|
|
43
|
+
"workflows:build": "npm run build --workspace @pasko70/pibo-workflows",
|
|
44
|
+
"workflows:typecheck": "npm run typecheck --workspace @pasko70/pibo-workflows",
|
|
42
45
|
"release": "node scripts/release.mjs",
|
|
43
46
|
"release:github": "node scripts/create-github-release.mjs",
|
|
44
|
-
"build": "tsc -p tsconfig.json && npm run web-ui:build && npm run vscode:webview:build && node scripts/ensure-bin-executable.mjs",
|
|
47
|
+
"build": "npm run workflows:build && node --max-old-space-size=1200 node_modules/typescript/bin/tsc -p tsconfig.json && npm run web-ui:build && npm run vscode:webview:build && node scripts/ensure-bin-executable.mjs",
|
|
45
48
|
"start": "node dist/bin/pibo.js",
|
|
46
|
-
"test": "npm run build && node
|
|
49
|
+
"test": "npm run build && node scripts/run-test-suite.mjs",
|
|
47
50
|
"check:product-vocab": "node scripts/legacy-product-vocabulary-gate.mjs",
|
|
48
51
|
"validate:ink-web-derived": "node scripts/ink-cli-web-derived-parity-validate.mjs",
|
|
49
52
|
"compute:limited-worker-smoke": "node scripts/compute-limited-worker-smoke.mjs",
|
|
50
53
|
"validate:goal-endurance": "node scripts/goal-endurance-check.mjs",
|
|
51
|
-
"typecheck": "tsc -p tsconfig.json --noEmit && npm run chat-ui:typecheck && npm run context-files-ui:typecheck && npm run vscode:typecheck",
|
|
54
|
+
"typecheck": "npm run workflows:typecheck && node --max-old-space-size=1200 node_modules/typescript/bin/tsc -p tsconfig.json --noEmit && npm run chat-ui:typecheck && npm run context-files-ui:typecheck && npm run vscode:typecheck",
|
|
52
55
|
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
|
|
53
56
|
"prepack": "npm run build"
|
|
54
57
|
},
|
|
@@ -92,6 +95,7 @@
|
|
|
92
95
|
"tsx": "^4.21.0",
|
|
93
96
|
"typescript": "^6.0.3",
|
|
94
97
|
"vite": "^8.0.10",
|
|
98
|
+
"vscode": "0.0.0-pibo-test-shim",
|
|
95
99
|
"vite-tsconfig-paths": "^5.1.4"
|
|
96
100
|
}
|
|
97
101
|
}
|