@pasko70/pibo 3.4.0 → 3.4.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.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="theme-color" content="#101d22" />
7
7
  <title>Pibo</title>
8
- <script type="module" crossorigin src="/apps/chat-vscode/assets/index-Spj6M0tn.js"></script>
8
+ <script type="module" crossorigin src="/apps/chat-vscode/assets/index-CZmxeSn3.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-b18ZkEo0.css">
10
10
  </head>
11
11
  <body>
@@ -1463,6 +1463,11 @@ export class PiboSessionRouter {
1463
1463
  this.runtimeResourceSessions.delete(piboSession.id);
1464
1464
  throw error;
1465
1465
  }
1466
+ const resourceInspection = resources.getInspection();
1467
+ const statusResources = {
1468
+ enabledSkills: [...new Set(resourceInspection.skills.map((skill) => skill.name))],
1469
+ contextFiles: [...new Set(resourceInspection.context.map((contribution) => (contribution.sourcePath ?? contribution.path ?? contribution.label)).filter((value) => Boolean(value)))],
1470
+ };
1466
1471
  session = new RoutedSession(piboSession.id, runtimeSession, this.emitOutput, this.pluginRegistry, {
1467
1472
  forwardLegacyPiEvents: this.options.forwardPiEvents ?? false,
1468
1473
  onNativeEventTelemetry: this.telemetryRecorder
@@ -1517,6 +1522,7 @@ export class PiboSessionRouter {
1517
1522
  const { runtimeInstanceId: _runtimeInstanceId, ...result } = await runtimeRegistry.logoutAgentRuntimeAuth(binding.runtimeInstanceId, input);
1518
1523
  return result;
1519
1524
  },
1525
+ statusResources,
1520
1526
  });
1521
1527
  this.sessions.set(piboSession.id, session);
1522
1528
  return session;
@@ -8,7 +8,10 @@ import { PreviewCapacityError, PreviewStore, createDefaultPreviewStore, previewE
8
8
  export const PREVIEW_WEB_APP_NAME = "pibo.session-live-previews";
9
9
  export const PREVIEW_WEB_MOUNT_PATH = "/apps/previews";
10
10
  export const PREVIEW_WEB_API_PREFIX = "/api/previews";
11
+ export const PREVIEW_EVENTS_PATH = `${PREVIEW_WEB_API_PREFIX}/events`;
11
12
  export const PREVIEW_SESSION_EXCHANGE_PATH = "/__pibo/session";
13
+ const DEFAULT_PREVIEW_EVENT_POLL_INTERVAL_MS = 1_000;
14
+ const PREVIEW_EVENT_HEARTBEAT_INTERVAL_MS = 25_000;
12
15
  function escapeHtml(value) {
13
16
  return value
14
17
  .replaceAll("&", "&amp;")
@@ -69,6 +72,79 @@ async function publicExposure(exposure, baseURL) {
69
72
  openUrl: `${PREVIEW_WEB_API_PREFIX}/${encodeURIComponent(exposure.id)}/open`,
70
73
  };
71
74
  }
75
+ function writePreviewEvent(controller, preview) {
76
+ controller.enqueue(new TextEncoder().encode([
77
+ "event: preview-created",
78
+ `data: ${JSON.stringify({ type: "preview-created", preview })}`,
79
+ "",
80
+ "",
81
+ ].join("\n")));
82
+ }
83
+ function createPreviewEventStream(input) {
84
+ const store = input.databasePath ? new PreviewStore(input.databasePath) : createDefaultPreviewStore();
85
+ const knownPreviewIds = new Set(store.listExposures({ piboSessionId: input.piboSessionId }).map((preview) => preview.id));
86
+ let closed = false;
87
+ let polling = false;
88
+ let pollTimer;
89
+ let heartbeatTimer;
90
+ const close = () => {
91
+ if (closed)
92
+ return;
93
+ closed = true;
94
+ if (pollTimer)
95
+ clearInterval(pollTimer);
96
+ if (heartbeatTimer)
97
+ clearInterval(heartbeatTimer);
98
+ store.close();
99
+ };
100
+ const stream = new ReadableStream({
101
+ start(controller) {
102
+ controller.enqueue(new TextEncoder().encode(": ready\n\n"));
103
+ const poll = async () => {
104
+ if (closed || polling)
105
+ return;
106
+ polling = true;
107
+ try {
108
+ const created = store.listExposures({ piboSessionId: input.piboSessionId })
109
+ .filter((preview) => !knownPreviewIds.has(preview.id))
110
+ .reverse();
111
+ for (const exposure of created) {
112
+ knownPreviewIds.add(exposure.id);
113
+ const preview = await publicExposure(exposure, input.baseURL);
114
+ if (closed)
115
+ return;
116
+ writePreviewEvent(controller, preview);
117
+ }
118
+ }
119
+ catch (error) {
120
+ if (!closed)
121
+ controller.error(error);
122
+ close();
123
+ }
124
+ finally {
125
+ polling = false;
126
+ }
127
+ };
128
+ pollTimer = setInterval(() => void poll(), input.pollIntervalMs);
129
+ heartbeatTimer = setInterval(() => {
130
+ if (!closed)
131
+ controller.enqueue(new TextEncoder().encode(": heartbeat\n\n"));
132
+ }, PREVIEW_EVENT_HEARTBEAT_INTERVAL_MS);
133
+ },
134
+ cancel() {
135
+ close();
136
+ },
137
+ });
138
+ return new Response(stream, {
139
+ headers: {
140
+ "content-type": "text/event-stream; charset=utf-8",
141
+ "cache-control": "no-cache, no-transform",
142
+ "content-encoding": "identity",
143
+ "x-accel-buffering": "no",
144
+ connection: "keep-alive",
145
+ },
146
+ });
147
+ }
72
148
  function readBody(request, maxBytes) {
73
149
  return new Promise((resolve, reject) => {
74
150
  const contentLength = Number(request.headers["content-length"]);
@@ -206,6 +282,7 @@ export function createPreviewWebApp(options = {}) {
206
282
  if (!Number.isInteger(browserSessionTtlMinutes) || browserSessionTtlMinutes < 1 || browserSessionTtlMinutes > 24 * 60) {
207
283
  throw new Error("Preview browser session lifetime must be between 1 minute and 24 hours");
208
284
  }
285
+ const eventPollIntervalMs = Math.max(50, options.eventPollIntervalMs ?? DEFAULT_PREVIEW_EVENT_POLL_INTERVAL_MS);
209
286
  const maxProxyConnections = options.maxProxyConnections ??
210
287
  configured.preview?.maxProxyConnections ??
211
288
  DEFAULT_MAX_PREVIEW_PROXY_CONNECTIONS;
@@ -261,6 +338,14 @@ export function createPreviewWebApp(options = {}) {
261
338
  return new Response(null, { status: allowed ? 200 : 403, headers: { "cache-control": "no-store" } });
262
339
  }
263
340
  await context.requireSession({ request });
341
+ if (url.pathname === PREVIEW_EVENTS_PATH && request.method === "GET") {
342
+ const piboSessionId = url.searchParams.get("piboSessionId")?.trim();
343
+ if (!piboSessionId)
344
+ return responseJson({ error: "piboSessionId is required" }, { status: 400 });
345
+ if (!baseURL)
346
+ return responseJson({ error: "Live previews are not configured. Set preview.baseURL." }, { status: 503 });
347
+ return createPreviewEventStream({ baseURL, databasePath, piboSessionId, pollIntervalMs: eventPollIntervalMs });
348
+ }
264
349
  if (url.pathname === PREVIEW_WEB_API_PREFIX && request.method === "GET") {
265
350
  const piboSessionId = url.searchParams.get("piboSessionId")?.trim();
266
351
  if (!piboSessionId)
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@pasko70/pibo",
3
- "version": "3.4.0",
3
+ "version": "3.4.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@pasko70/pibo",
9
- "version": "3.4.0",
9
+ "version": "3.4.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.4.0",
3
+ "version": "3.4.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",