pk-annotator 0.6.2 → 0.8.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.
Files changed (27) hide show
  1. package/README.ko.md +17 -9
  2. package/README.md +11 -3
  3. package/dist/overlay.mjs +48 -4
  4. package/dist/{pka-mcp-server-DJ7A2u68.mjs → pka-mcp-server-BhcwUcgs.mjs} +251 -19
  5. package/dist/pka-mcp.mjs +1 -1
  6. package/dist/{pka-overlay-agent-art-BKBtrWqF.mjs → pka-overlay-agent-art-n6aH_1kF.mjs} +1 -1
  7. package/dist/{pka-overlay-agent-theme-CfCyg5Gv.mjs → pka-overlay-agent-theme-BiWi6_IJ.mjs} +13 -1
  8. package/dist/pka-overlay-app-DjGKtS7t.mjs +274 -0
  9. package/dist/pka-overlay-channel-BZI-qbPH.mjs +113 -0
  10. package/dist/pka-overlay-composer-DCu_eGT0.mjs +244 -0
  11. package/dist/{pka-overlay-copy-CvKjfQl1.mjs → pka-overlay-copy-B-PxSL-y.mjs} +1 -1
  12. package/dist/{pka-overlay-gifenc-BGK0llcr.mjs → pka-overlay-gifenc-DqOpaD2I.mjs} +1 -1
  13. package/dist/{pka-overlay-perf-panel-gQYSZ0gA.mjs → pka-overlay-perf-panel-DZY5Nwil.mjs} +2 -2
  14. package/dist/pka-overlay-send-Cu8DXY6B.mjs +139 -0
  15. package/dist/{pka-overlay-snapdom-djDPjHu_.mjs → pka-overlay-snapdom-BArbLl8i.mjs} +1 -1
  16. package/dist/pka-overlay-thread-store-C55iLxND.mjs +1 -0
  17. package/dist/pka-overlay-untracked-fetch-Dj1et-Fv.mjs +1 -0
  18. package/dist/pka.mjs +24 -4
  19. package/dist/{recording-CpEDddkZ.mjs → recording-D_Qkyq0N.mjs} +73 -23
  20. package/dist/vite.mjs +318 -13
  21. package/package.json +1 -1
  22. package/dist/pka-overlay-app-CsIxnRqJ.mjs +0 -246
  23. package/dist/pka-overlay-channel-D5Ghnvgb.mjs +0 -113
  24. package/dist/pka-overlay-composer-CT2x7vgj.mjs +0 -244
  25. package/dist/pka-overlay-send-BZyT4xuW.mjs +0 -139
  26. package/dist/pka-overlay-thread-store-DOxLXsAv.mjs +0 -1
  27. package/dist/pka-overlay-untracked-fetch-BCn3eft5.mjs +0 -1
package/dist/vite.mjs CHANGED
@@ -1,7 +1,8 @@
1
- import { A as createStore, B as Id, C as agentsDir, D as MissingAnnotationError, H as State, I as resolveInside, L as AnnotationDraft, M as isErrno, N as listIds, O as PkaError, R as ErrorGroup, T as AgentPresence, U as ThreadEntry, V as RelativePath, b as upsertErrorGroups, d as checkCaptureFiles, f as create, g as loadAnnotationUpdates, k as checkStoreSize, r as RecordingManifestDraft, t as RecordingErrors, w as liveAgents, z as ID_PATTERN } from "./recording-CpEDddkZ.mjs";
1
+ import { A as createStore, B as Id, C as agentsDir, D as MissingAnnotationError, H as State, I as resolveInside, L as AnnotationDraft, M as isErrno, N as listIds, O as PkaError, R as ErrorGroup, T as AgentPresence, U as ThreadEntry, V as RelativePath, b as upsertErrorGroups, d as checkCaptureFiles, f as create, g as loadAnnotationUpdates, k as checkStoreSize, r as RecordingManifestDraft, t as RecordingErrors, w as liveAgents, z as ID_PATTERN } from "./recording-D_Qkyq0N.mjs";
2
+ import { createRequire } from "node:module";
2
3
  import { execFile } from "node:child_process";
3
4
  import { randomBytes } from "node:crypto";
4
- import { constants, watch } from "node:fs";
5
+ import { constants, readFileSync, watch } from "node:fs";
5
6
  import { lstat, mkdir, open, readFile, readdir, realpath, rm, stat } from "node:fs/promises";
6
7
  import path, { posix } from "node:path";
7
8
  import { fileURLToPath } from "node:url";
@@ -14,6 +15,9 @@ import { TraceMap, originalPositionFor } from "@jridgewell/trace-mapping";
14
15
  const CHANNEL = {
15
16
  create: "pka:create",
16
17
  file: "pka:file",
18
+ uploadReady: "pka:upload-ready",
19
+ fileWritten: "pka:file-written",
20
+ cancelUpload: "pka:cancel-upload",
17
21
  errors: "pka:errors",
18
22
  created: "pka:created",
19
23
  createFailed: "pka:create-failed",
@@ -27,7 +31,10 @@ const CHANNEL = {
27
31
  presence: "pka:presence",
28
32
  agent: "pka:agent",
29
33
  setup: "pka:setup",
30
- setupInfo: "pka:setup-info"
34
+ setupInfo: "pka:setup-info",
35
+ update: "pka:update",
36
+ installUpdate: "pka:install-update",
37
+ updateResult: "pka:update-result"
31
38
  };
32
39
  const MAX_CHUNK_BYTES = 524288;
33
40
  const CreateMessage = z.strictObject({
@@ -44,6 +51,7 @@ const FileChunkMessage = z.strictObject({
44
51
  offset: z.number().int().nonnegative(),
45
52
  data: z.base64().max(Math.ceil(MAX_CHUNK_BYTES / 3) * 4)
46
53
  });
54
+ const CancelUploadMessage = z.strictObject({ requestId: Id });
47
55
  const ErrorsMessage = z.strictObject({ groups: z.array(ErrorGroup).min(1).max(50) });
48
56
  const SyncMessage = z.strictObject({ ids: z.array(Id).max(200) });
49
57
  const SymbolicateMessage = z.strictObject({
@@ -52,6 +60,17 @@ const SymbolicateMessage = z.strictObject({
52
60
  });
53
61
  const PresenceMessage = z.strictObject({});
54
62
  const SetupMessage = z.strictObject({ requestId: Id });
63
+ const Version = z.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/);
64
+ const InstallUpdateMessage = z.strictObject({
65
+ requestId: Id,
66
+ version: Version
67
+ });
68
+ z.strictObject({ requestId: Id });
69
+ z.strictObject({
70
+ requestId: Id,
71
+ path: RelativePath,
72
+ offset: z.number().int().nonnegative()
73
+ });
55
74
  z.strictObject({
56
75
  requestId: Id,
57
76
  id: Id,
@@ -97,6 +116,18 @@ z.strictObject({
97
116
  }).nullable(),
98
117
  cause: z.enum(["presence", "change"])
99
118
  });
119
+ z.strictObject({
120
+ current: Version,
121
+ latest: Version
122
+ });
123
+ z.discriminatedUnion("outcome", [z.strictObject({
124
+ requestId: Id,
125
+ outcome: z.enum(["restart", "restart-manually"])
126
+ }), z.strictObject({
127
+ requestId: Id,
128
+ outcome: z.literal("failed"),
129
+ message: z.string()
130
+ })]);
100
131
  //#endregion
101
132
  //#region src/vite/source.ts
102
133
  const ATTRIBUTE = "data-pka-src";
@@ -226,6 +257,213 @@ async function symbolicate(environment, workspaceRoot, stack) {
226
257
  };
227
258
  }
228
259
  //#endregion
260
+ //#region src/vite/update.ts
261
+ const execFileAsync$1 = promisify(execFile);
262
+ const REGISTRY_LATEST = "https://registry.npmjs.org/pk-annotator/latest";
263
+ const CHECK_TTL_MS = 36e5;
264
+ const INSTALL_TIMEOUT_MS = 3e5;
265
+ const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
266
+ const REGISTRY_SPEC = /^([~^]?)\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
267
+ const LOCKFILES = [
268
+ ["pnpm-lock.yaml", "pnpm"],
269
+ ["bun.lock", "bun"],
270
+ ["bun.lockb", "bun"],
271
+ ["yarn.lock", "yarn"],
272
+ ["package-lock.json", "npm"]
273
+ ];
274
+ const Manifest = z.object({
275
+ dependencies: z.record(z.string(), z.string()).optional(),
276
+ devDependencies: z.record(z.string(), z.string()).optional(),
277
+ packageManager: z.string().optional()
278
+ });
279
+ const Published = z.object({ version: z.string().regex(SEMVER) });
280
+ /** This module's own package.json; a self-reference resolves through its real path. */
281
+ const OWN_MANIFEST = createRequire(import.meta.url).resolve("pk-annotator/package.json");
282
+ const RUNNING = Published.parse(JSON.parse(readFileSync(OWN_MANIFEST, "utf8"))).version;
283
+ function parseVersion(version) {
284
+ const match = SEMVER.exec(version);
285
+ if (match === null) throw new Error(`Not a semantic version: ${version}`);
286
+ return {
287
+ core: [
288
+ Number(match[1]),
289
+ Number(match[2]),
290
+ Number(match[3])
291
+ ],
292
+ pre: match[4]
293
+ };
294
+ }
295
+ /** Whether `candidate` is a later release than `current`; a prerelease never outranks its release. */
296
+ function isNewer(candidate, current) {
297
+ const next = parseVersion(candidate);
298
+ const now = parseVersion(current);
299
+ for (const [index, part] of next.core.entries()) {
300
+ const other = now.core[index];
301
+ if (part !== other) return part > other;
302
+ }
303
+ return next.pre === void 0 && now.pre !== void 0;
304
+ }
305
+ function installCommand(install, version) {
306
+ const dev = {
307
+ pnpm: "--save-dev",
308
+ npm: "--save-dev",
309
+ yarn: "--dev",
310
+ bun: "--dev"
311
+ }[install.manager];
312
+ const exact = {
313
+ pnpm: "--save-exact",
314
+ npm: "--save-exact",
315
+ yarn: "--exact",
316
+ bun: "--exact"
317
+ }[install.manager];
318
+ return [
319
+ install.manager,
320
+ install.manager === "npm" ? "install" : "add",
321
+ ...install.dev ? [dev] : [],
322
+ ...install.exact ? [exact] : [],
323
+ `pk-annotator@${version}`
324
+ ];
325
+ }
326
+ async function readManifest(dir) {
327
+ const file = path.join(dir, "package.json");
328
+ const text = await readFile(file, "utf8").catch((cause) => {
329
+ if (isErrno(cause, "ENOENT")) return void 0;
330
+ throw cause;
331
+ });
332
+ if (text === void 0) return void 0;
333
+ const parsed = Manifest.safeParse(JSON.parse(text));
334
+ if (!parsed.success) throw new Error(`${file} is not a valid package.json: ${parsed.error.message}`);
335
+ return parsed.data;
336
+ }
337
+ async function exists(file) {
338
+ return stat(file).then(() => true, (cause) => {
339
+ if (isErrno(cause, "ENOENT")) return false;
340
+ throw cause;
341
+ });
342
+ }
343
+ /**
344
+ * The manifest that declares pk-annotator, from the Vite root up to the workspace root, and
345
+ * the workspace's package manager. Null when the dependency is not a registry version, for
346
+ * example this repository's fixture or a workspace link.
347
+ */
348
+ async function findInstall(viteRoot, workspaceRoot) {
349
+ if (!OWN_MANIFEST.split(path.sep).includes("node_modules")) return null;
350
+ let declared;
351
+ for (let dir = path.resolve(viteRoot);; dir = path.dirname(dir)) {
352
+ const manifest = await readManifest(dir);
353
+ const dev = manifest?.devDependencies?.["pk-annotator"];
354
+ const spec = dev ?? manifest?.dependencies?.["pk-annotator"];
355
+ if (spec !== void 0) {
356
+ const match = REGISTRY_SPEC.exec(spec);
357
+ if (match === null) return null;
358
+ declared = {
359
+ dir,
360
+ dev: dev !== void 0,
361
+ exact: match[1] === ""
362
+ };
363
+ break;
364
+ }
365
+ if (dir === path.resolve(workspaceRoot) || dir === path.dirname(dir)) return null;
366
+ }
367
+ const field = (await readManifest(workspaceRoot))?.packageManager?.split("@")[0];
368
+ if (field === "pnpm" || field === "npm" || field === "yarn" || field === "bun") return {
369
+ ...declared,
370
+ manager: field
371
+ };
372
+ for (const [lockfile, manager] of LOCKFILES) if (await exists(path.join(workspaceRoot, lockfile))) return {
373
+ ...declared,
374
+ manager
375
+ };
376
+ return null;
377
+ }
378
+ /** The real path of the pk-annotator manifest that `dir` resolves, read fresh from disk. */
379
+ async function resolveInstalled(dir) {
380
+ for (let from = dir;; from = path.dirname(from)) {
381
+ const found = await realpath(path.join(from, "node_modules/pk-annotator/package.json")).catch((cause) => {
382
+ if (isErrno(cause, "ENOENT")) return void 0;
383
+ throw cause;
384
+ });
385
+ if (found !== void 0) return found;
386
+ if (from === path.dirname(from)) throw new Error(`${dir} resolves no pk-annotator`);
387
+ }
388
+ }
389
+ /**
390
+ * Checks npm for a newer pk-annotator when a page mounts, at most once an hour, and installs
391
+ * it on request with the workspace's package manager. Nothing runs between those events.
392
+ */
393
+ function createUpdater(viteRoot, workspaceRoot, warn) {
394
+ let install;
395
+ let checked;
396
+ let installing = false;
397
+ let staleWarned = false;
398
+ async function latest() {
399
+ if (checked === void 0 || Date.now() - checked.at > CHECK_TTL_MS) {
400
+ const request = fetch(REGISTRY_LATEST, { signal: AbortSignal.timeout(1e4) }).then(async (response) => {
401
+ if (!response.ok) throw new Error(`${REGISTRY_LATEST} answered ${response.status}`);
402
+ return Published.parse(await response.json()).version;
403
+ }).catch((cause) => {
404
+ warn(`update check failed: ${cause instanceof Error ? cause.message : String(cause)}`);
405
+ return null;
406
+ });
407
+ checked = {
408
+ at: Date.now(),
409
+ latest: request
410
+ };
411
+ }
412
+ return checked.latest;
413
+ }
414
+ return {
415
+ /** The update to offer, or null when the installed version is current or not managed by version. */
416
+ async check() {
417
+ install ??= findInstall(viteRoot, workspaceRoot);
418
+ if (await install === null) return null;
419
+ const onDisk = Published.parse(JSON.parse(await readFile(OWN_MANIFEST, "utf8"))).version;
420
+ if (onDisk !== RUNNING) {
421
+ if (!staleWarned) {
422
+ staleWarned = true;
423
+ warn(`${onDisk} is installed but ${RUNNING} is running; restart the dev server`);
424
+ }
425
+ return null;
426
+ }
427
+ const version = await latest();
428
+ return version !== null && isNewer(version, RUNNING) ? {
429
+ current: RUNNING,
430
+ latest: version
431
+ } : null;
432
+ },
433
+ /**
434
+ * Installs `version`, which must be the release the last check offered. "restart" means a
435
+ * dev server restart loads it; "restart-manually" means the package was replaced in place,
436
+ * where Node keeps the old plugin module until the process restarts.
437
+ */
438
+ async install(version) {
439
+ const target = await install;
440
+ if (target === void 0 || target === null) throw new Error("This workspace does not install pk-annotator from the npm registry");
441
+ if (checked === void 0 || await checked.latest !== version) throw new Error(`${version} is not the release last offered`);
442
+ if (installing) throw new Error("An update is already installing");
443
+ installing = true;
444
+ try {
445
+ const [command, ...args] = installCommand(target, version);
446
+ try {
447
+ await execFileAsync$1(command, args, {
448
+ cwd: target.dir,
449
+ timeout: INSTALL_TIMEOUT_MS
450
+ });
451
+ } catch (cause) {
452
+ const stderr = cause instanceof Error && "stderr" in cause ? String(cause.stderr).trim() : "";
453
+ const detail = stderr === "" ? String(cause) : stderr.slice(-600);
454
+ throw new Error(`${[command, ...args].join(" ")} failed in ${target.dir}: ${detail}`, { cause });
455
+ }
456
+ const resolved = await resolveInstalled(target.dir);
457
+ const installed = Published.parse(JSON.parse(await readFile(resolved, "utf8"))).version;
458
+ if (installed !== version) throw new Error(`Installed ${version}, but ${target.dir} resolves pk-annotator ${installed}`);
459
+ return resolved === OWN_MANIFEST ? "restart-manually" : "restart";
460
+ } finally {
461
+ installing = false;
462
+ }
463
+ }
464
+ };
465
+ }
466
+ //#endregion
229
467
  //#region src/vite/index.ts
230
468
  const AnnotatorOptions = z.strictObject({
231
469
  bodies: z.array(z.string().startsWith("/")).optional(),
@@ -240,6 +478,7 @@ const BODIES_GLOBAL = "__PKA_BODIES__";
240
478
  const STAGING_DIR = ".staging";
241
479
  const MB = 1048576;
242
480
  const MAX_RECORDING_JSON_BYTES = 4 * MB;
481
+ const MAX_UPLOADS = 8;
243
482
  const execFileAsync = promisify(execFile);
244
483
  /** A staging directory named <pid>-<hex> belongs to a running process unless that pid is gone. */
245
484
  function ownerAlive(name) {
@@ -369,10 +608,17 @@ async function serve(server, maxStoreBytes, storeRoot) {
369
608
  try {
370
609
  await step();
371
610
  } catch (thrown) {
372
- await abort(upload, thrown);
611
+ if (!upload.done) await abort(upload, thrown);
373
612
  }
374
613
  }).catch((cause) => error(`cleaning up ${upload.requestId} failed: ${describeError(cause)}`));
375
614
  }
615
+ function cancel(upload, reason) {
616
+ if (upload.done) return;
617
+ upload.done = true;
618
+ upload.queue = upload.queue.then(async () => {
619
+ if (uploads.get(upload.requestId) === upload) await abort(upload, reason);
620
+ }).catch((cause) => error(`upload cleanup failed: ${describeError(cause)}`));
621
+ }
376
622
  /** Replaces the page's raw stack and top frame with source positions. */
377
623
  async function symbolicateGroup(group) {
378
624
  const { topFrame: _pageTopFrame, ...rest } = group;
@@ -419,7 +665,11 @@ async function serve(server, maxStoreBytes, storeRoot) {
419
665
  }
420
666
  async function finish(upload) {
421
667
  const recordingDirectories = new Set(upload.draft.attachments.filter((attachment) => attachment.kind === "recording").map((attachment) => path.posix.dirname(attachment.path)));
422
- for (const directory of recordingDirectories) await completeRecording(upload, directory);
668
+ for (const directory of recordingDirectories) {
669
+ await completeRecording(upload, directory);
670
+ if (upload.done) return;
671
+ }
672
+ if (upload.done) return;
423
673
  const { id } = await create(store, upload.draft, {
424
674
  dir: upload.dir,
425
675
  paths: [...upload.files.keys()]
@@ -468,6 +718,10 @@ async function serve(server, maxStoreBytes, storeRoot) {
468
718
  refuse(`Request ${requestId} is already in progress`);
469
719
  return;
470
720
  }
721
+ if (uploads.size >= MAX_UPLOADS || [...uploads.values()].some((upload) => upload.client === client)) {
722
+ refuse("Another upload is in progress; wait for it to finish and retry");
723
+ return;
724
+ }
471
725
  try {
472
726
  checkCaptureFiles(message.draft, message.files.map((file) => file.path));
473
727
  } catch (thrown) {
@@ -487,11 +741,19 @@ async function serve(server, maxStoreBytes, storeRoot) {
487
741
  }])),
488
742
  remaining: declaredBytes,
489
743
  done: false,
744
+ busy: true,
490
745
  queue: Promise.resolve()
491
746
  };
492
747
  uploads.set(requestId, upload);
493
- enqueue(upload, () => prepare(upload, declaredBytes));
494
- if (declaredBytes === 0) enqueue(upload, () => finish(upload));
748
+ enqueue(upload, async () => {
749
+ await prepare(upload, declaredBytes);
750
+ if (upload.done) return;
751
+ if (declaredBytes === 0) await finish(upload);
752
+ else {
753
+ upload.busy = false;
754
+ client.send(CHANNEL.uploadReady, { requestId });
755
+ }
756
+ });
495
757
  });
496
758
  listen(CHANNEL.file, FileChunkMessage, async (message, client) => {
497
759
  const upload = uploads.get(message.requestId);
@@ -499,18 +761,36 @@ async function serve(server, maxStoreBytes, storeRoot) {
499
761
  warn(`dropped ${CHANNEL.file}: no upload ${message.requestId} in progress for this page`);
500
762
  return;
501
763
  }
764
+ if (upload.done) return;
765
+ if (upload.busy) {
766
+ cancel(upload, new PkaError("Wait for the upload acknowledgement before sending another chunk; reload the page and retry"));
767
+ return;
768
+ }
502
769
  const file = upload.files.get(message.path);
503
770
  const data = Buffer.from(message.data, "base64");
504
- if (file === void 0 || message.offset !== file.received || file.received + data.length > file.bytes) {
505
- const reason = new PkaError(file === void 0 ? `Chunk for ${message.path}, which the request did not declare` : `Chunk for ${message.path} at offset ${message.offset} does not continue the ${file.received} of ${file.bytes} bytes received`);
506
- enqueue(upload, () => Promise.reject(reason));
771
+ if (file === void 0 || data.length === 0 || message.offset !== file.received || file.received + data.length > file.bytes) {
772
+ cancel(upload, new PkaError(file === void 0 ? `Chunk for ${message.path}, which the request did not declare` : `Chunk for ${message.path} at offset ${message.offset} does not continue the ${file.received} of ${file.bytes} bytes received`));
507
773
  return;
508
774
  }
509
775
  file.received += data.length;
510
776
  upload.remaining -= data.length;
777
+ upload.busy = true;
511
778
  const target = resolveInside(upload.dir, message.path);
512
- enqueue(upload, () => writeChunk(target, message.offset, data));
513
- if (upload.remaining === 0) enqueue(upload, () => finish(upload));
779
+ enqueue(upload, async () => {
780
+ await writeChunk(target, message.offset, data);
781
+ if (upload.done) return;
782
+ if (upload.remaining !== 0) upload.busy = false;
783
+ client.send(CHANNEL.fileWritten, {
784
+ requestId: upload.requestId,
785
+ path: message.path,
786
+ offset: file.received
787
+ });
788
+ if (upload.remaining === 0) await finish(upload);
789
+ });
790
+ });
791
+ listen(CHANNEL.cancelUpload, CancelUploadMessage, async (message, client) => {
792
+ const upload = uploads.get(message.requestId);
793
+ if (upload?.client === client) cancel(upload, new PkaError("The page cancelled the upload"));
514
794
  });
515
795
  listen(CHANNEL.errors, ErrorsMessage, async (message, client) => {
516
796
  const groups = await Promise.all(message.groups.map(symbolicateGroup));
@@ -544,6 +824,27 @@ async function serve(server, maxStoreBytes, storeRoot) {
544
824
  };
545
825
  client.send(CHANNEL.setupInfo, reply);
546
826
  });
827
+ const updater = createUpdater(server.config.root, workspaceRoot, warn);
828
+ listen(CHANNEL.installUpdate, InstallUpdateMessage, async (message, client) => {
829
+ let result;
830
+ try {
831
+ const outcome = await updater.install(message.version);
832
+ result = {
833
+ requestId: message.requestId,
834
+ outcome
835
+ };
836
+ } catch (cause) {
837
+ error(`update to ${message.version} failed: ${describeError(cause)}`);
838
+ result = {
839
+ requestId: message.requestId,
840
+ outcome: "failed",
841
+ message: describeError(cause)
842
+ };
843
+ }
844
+ client.send(CHANNEL.updateResult, result);
845
+ if (result.outcome === "restart-manually") warn(`updated to ${message.version}; restart the dev server to load it`);
846
+ if (result.outcome === "restart") await server.restart();
847
+ });
547
848
  listen(CHANNEL.sync, SyncMessage, async (message, client) => {
548
849
  const synced = { annotations: [] };
549
850
  for (const id of message.ids) try {
@@ -559,7 +860,7 @@ async function serve(server, maxStoreBytes, storeRoot) {
559
860
  client.send(CHANNEL.synced, synced);
560
861
  });
561
862
  const onDisconnect = (_data, client) => {
562
- for (const upload of uploads.values()) if (upload.client === client) enqueue(upload, () => Promise.reject(new PkaError("The page disconnected before the upload finished")));
863
+ for (const upload of uploads.values()) if (upload.client === client) cancel(upload, new PkaError("The page disconnected before the upload finished"));
563
864
  };
564
865
  hot.on("vite:client:disconnect", onDisconnect);
565
866
  listeners.push(["vite:client:disconnect", onDisconnect]);
@@ -592,6 +893,9 @@ async function serve(server, maxStoreBytes, storeRoot) {
592
893
  agentQueue = agentQueue.then(async () => pushAgent(await readAgent())).catch((cause) => error(`reading agent presence failed: ${describeError(cause)}`));
593
894
  }
594
895
  listen(CHANNEL.presence, PresenceMessage, async (_message, client) => {
896
+ updater.check().then((offer) => {
897
+ if (offer !== null) client.send(CHANNEL.update, offer);
898
+ }, (cause) => error(`update check failed: ${describeError(cause)}`));
595
899
  const answered = agentQueue.then(async () => {
596
900
  const agent = await readAgent();
597
901
  pushAgent(agent);
@@ -694,6 +998,7 @@ async function serve(server, maxStoreBytes, storeRoot) {
694
998
  annotationWatchers.clear();
695
999
  for (const [event, listener] of listeners) hot.off(event, listener);
696
1000
  for (const upload of uploads.values()) upload.done = true;
1001
+ await Promise.all([...uploads.values()].map((upload) => upload.queue));
697
1002
  uploads.clear();
698
1003
  await rm(serverStaging, {
699
1004
  recursive: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pk-annotator",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "description": "Dev-only browser annotation overlay for Vite apps that sends picked elements, captures, and prompts to Claude Code and Codex through an MCP server.",
5
5
  "keywords": [
6
6
  "annotation",