pk-annotator 0.6.1 → 0.7.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/README.ko.md +9 -1
- package/README.md +9 -1
- package/dist/overlay.mjs +48 -4
- package/dist/{pka-mcp-server-DFwBXimY.mjs → pka-mcp-server-DeBVJQYE.mjs} +233 -18
- package/dist/pka-mcp.mjs +1 -1
- package/dist/{pka-overlay-agent-art-BKBtrWqF.mjs → pka-overlay-agent-art-n6aH_1kF.mjs} +1 -1
- package/dist/{pka-overlay-agent-theme-CfCyg5Gv.mjs → pka-overlay-agent-theme-DfiOu97H.mjs} +13 -1
- package/dist/{pka-overlay-app-DARf2EHO.mjs → pka-overlay-app-D-PF-HmH.mjs} +69 -48
- package/dist/pka-overlay-channel-C6qfFolf.mjs +113 -0
- package/dist/pka-overlay-composer-CK1sTHAv.mjs +244 -0
- package/dist/{pka-overlay-copy-Cwa7iOSP.mjs → pka-overlay-copy-BTMmwtSZ.mjs} +1 -1
- package/dist/{pka-overlay-gifenc-BGK0llcr.mjs → pka-overlay-gifenc-DqOpaD2I.mjs} +1 -1
- package/dist/{pka-overlay-perf-panel-BK2LasjA.mjs → pka-overlay-perf-panel-xd8kgxtm.mjs} +2 -2
- package/dist/{pka-overlay-send-YoUmUPr4.mjs → pka-overlay-send-UrgGuLhh.mjs} +6 -6
- package/dist/pka-overlay-snapdom-BArbLl8i.mjs +34 -0
- package/dist/{pka-overlay-thread-store-DOxLXsAv.mjs → pka-overlay-thread-store-X3FM2ev_.mjs} +1 -1
- package/dist/pka-overlay-untracked-fetch-Dj1et-Fv.mjs +1 -0
- package/dist/pka.mjs +2 -1
- package/dist/{recording-CpEDddkZ.mjs → recording-82fajWWq.mjs} +41 -14
- package/dist/vite.mjs +255 -3
- package/package.json +1 -1
- package/dist/pka-overlay-channel-D5Ghnvgb.mjs +0 -113
- package/dist/pka-overlay-composer-DX5x5955.mjs +0 -244
- package/dist/pka-overlay-snapdom-DHKWlXm4.mjs +0 -34
- package/dist/pka-overlay-untracked-fetch-BCn3eft5.mjs +0 -1
package/dist/{pka-overlay-thread-store-DOxLXsAv.mjs → pka-overlay-thread-store-X3FM2ev_.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{_ as e,g as t,h as n,n as r,v as i}from"./overlay.mjs";import{M as a,O as o,P as s,_ as c,c as l,d as u,j as d,n as f,u as p,y as m}from"./pka-overlay-channel-C6qfFolf.mjs";import{t as h}from"./pka-overlay-store-eSg4Q7Sz.mjs";const g=d({id:c,prompt:a(),createdAt:m,elements:o().int().nonnegative()});function _(){let e=sessionStorage.getItem(r);if(e===null)return{sent:[],discarded:null};let t=[],n=null,i;try{i=JSON.parse(e)}catch(e){n=`it is not JSON (${e instanceof Error?e.message:String(e)})`}if(n===null&&!Array.isArray(i)&&(n=`it is not a list`),Array.isArray(i))for(let[e,r]of i.entries()){let i=g.safeParse(r);i.success?t.push(i.data):n??=`entry ${e+1}: ${s(i.error)}`}if(n===null)return{sent:t,discarded:null};let a=`Discarded unreadable History entries in sessionStorage ${r}; ${n}`;return console.error(`[pk-annotator] ${a}`),sessionStorage.setItem(r,JSON.stringify(t)),{sent:t,discarded:a}}function v(a){let o=h({..._(),states:/* @__PURE__ */ new Map,entries:/* @__PURE__ */ new Map,unread:!1}),s=e=>o.get().sent.some(t=>t.id===e),c=o.get().sent.map(e=>e.id),d=/* @__PURE__ */ new Set,m=()=>{d=new Set(c.splice(0,200)),d.size>0&&i(a,f.sync,{ids:[...d]})},g=[e(a,f.state,l,e=>{if(!s(e.id))return;let t=o.get().states.get(e.id)?.status;o.set({states:new Map(o.get().states).set(e.id,e.state)}),(e.state.status===`resolved`||e.state.status===`dismissed`)&&t!==e.state.status&&n()}),e(a,f.thread,u,({id:e,entry:t})=>{if(!s(e))return;let{entries:r,unread:i}=o.get();o.set({entries:new Map(r).set(e,[...r.get(e)??[],t]),unread:i||t.from===`agent`}),t.from===`agent`&&n()}),e(a,f.synced,p,e=>{let t=new Map(o.get().states),n=new Map(o.get().entries);for(let r of e.annotations)t.set(r.id,r.state),n.set(r.id,r.thread);let i=new Set(e.annotations.map(e=>e.id)),a=o.get().sent.filter(e=>!d.has(e.id)||i.has(e.id));sessionStorage.setItem(r,JSON.stringify(a)),o.set({sent:a,states:t,entries:n}),m()})],v=o.subscribe(()=>{let{sent:e,states:n}=o.get();t(e.some(e=>n.get(e.id)?.status===`acknowledged`))});return m(),{...o,added(e){let t=[e,...o.get().sent.filter(t=>t.id!==e.id)];sessionStorage.setItem(r,JSON.stringify(t)),o.set({sent:t,states:new Map(o.get().states).set(e.id,{status:`pending`,history:[{status:`pending`,at:e.createdAt}]})})},disconnect(){for(let e of g)e();v(),t(!1)}}}export{v as connectThread};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{y as e}from"./overlay.mjs";function t(t,n){return e().fetch(t,n)}export{t};
|
package/dist/pka.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { B as Id, F as readJsonLines, I as resolveInside, M as isErrno, O as PkaError, P as readJson, S as wait, W as Timestamp, _ as prune, a as ErrorsInput, c as ReplyInput, h as list, i as TimelineEntry, j as findStore, l as SetStatusInput, m as get, n as RecordingManifest, o as GetInput, p as errors, s as ListInput, u as attach, v as reply, x as validateAttachTarget, y as setStatus, z as ID_PATTERN } from "./recording-
|
|
2
|
+
import { B as Id, F as readJsonLines, I as resolveInside, M as isErrno, O as PkaError, P as readJson, S as wait, W as Timestamp, _ as prune, a as ErrorsInput, c as ReplyInput, h as list, i as TimelineEntry, j as findStore, l as SetStatusInput, m as get, n as RecordingManifest, o as GetInput, p as errors, s as ListInput, u as attach, v as reply, x as validateAttachTarget, y as setStatus, z as ID_PATTERN } from "./recording-82fajWWq.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { createWriteStream } from "node:fs";
|
|
5
5
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
@@ -1423,6 +1423,7 @@ function printAnnotation(view) {
|
|
|
1423
1423
|
line(` [${element.n}] ${element.source ?? "(no source)"}${element.usedAt === void 0 ? "" : ` usedAt ${element.usedAt}`} ${element.owners.join(" > ")} ${label || element.selector.css}`);
|
|
1424
1424
|
}
|
|
1425
1425
|
for (const attachment of view.attachments) line(` ${attachment.kind}: ${attachment.path}${attachment.summary ? ` ${attachment.summary}` : ""}`);
|
|
1426
|
+
for (const mark of view.marks ?? []) line(` mark ${mark.n}: ${mark.url}`);
|
|
1426
1427
|
for (const entry of view.thread ?? []) line(` ${entry.at} ${entry.from}: ${entry.text}`);
|
|
1427
1428
|
if (view.omitted !== void 0) line(view.omitted.note);
|
|
1428
1429
|
}
|
|
@@ -30,6 +30,7 @@ const Selector = z.strictObject({
|
|
|
30
30
|
});
|
|
31
31
|
const ElementRef = z.strictObject({
|
|
32
32
|
n: z.number().int().positive(),
|
|
33
|
+
url: z.string().optional(),
|
|
33
34
|
source: z.string().regex(/^.+:\d+:\d+$/, "source must be file:line:col").optional(),
|
|
34
35
|
usedAt: z.string().regex(/^.+:\d+:\d+$/, "usedAt must be file:line:col").optional(),
|
|
35
36
|
owners: z.array(z.string()),
|
|
@@ -53,6 +54,11 @@ const Attachment = z.strictObject({
|
|
|
53
54
|
path: RelativePath,
|
|
54
55
|
summary: z.string()
|
|
55
56
|
});
|
|
57
|
+
const MarkPage = z.strictObject({
|
|
58
|
+
n: z.number().int().positive(),
|
|
59
|
+
url: z.string(),
|
|
60
|
+
route: z.string()
|
|
61
|
+
});
|
|
56
62
|
const Annotation = z.strictObject({
|
|
57
63
|
id: Id,
|
|
58
64
|
createdAt: Timestamp,
|
|
@@ -61,7 +67,8 @@ const Annotation = z.strictObject({
|
|
|
61
67
|
viewport: Viewport,
|
|
62
68
|
prompt: z.string(),
|
|
63
69
|
elements: z.array(ElementRef),
|
|
64
|
-
attachments: z.array(Attachment)
|
|
70
|
+
attachments: z.array(Attachment),
|
|
71
|
+
marks: z.array(MarkPage).optional()
|
|
65
72
|
});
|
|
66
73
|
const AnnotationDraft = Annotation.omit({
|
|
67
74
|
id: true,
|
|
@@ -734,6 +741,7 @@ function optionalPageText(value, max) {
|
|
|
734
741
|
}
|
|
735
742
|
const ElementView = z.strictObject({
|
|
736
743
|
n: z.number(),
|
|
744
|
+
url: z.string().optional().describe("The page the element was picked on"),
|
|
737
745
|
source: z.string().optional(),
|
|
738
746
|
usedAt: z.string().optional(),
|
|
739
747
|
owners: z.array(z.string()),
|
|
@@ -749,6 +757,11 @@ const ElementView = z.strictObject({
|
|
|
749
757
|
html: z.string().optional(),
|
|
750
758
|
box: Box.optional()
|
|
751
759
|
});
|
|
760
|
+
const MarkView = z.strictObject({
|
|
761
|
+
n: z.number().describe("Matches the \"## Mark n\" section of the prompt"),
|
|
762
|
+
url: z.string(),
|
|
763
|
+
route: z.string()
|
|
764
|
+
});
|
|
752
765
|
const AnnotationView = z.strictObject({
|
|
753
766
|
id: Id,
|
|
754
767
|
createdAt: Timestamp,
|
|
@@ -765,13 +778,15 @@ const AnnotationView = z.strictObject({
|
|
|
765
778
|
path: z.string(),
|
|
766
779
|
summary: z.string().optional()
|
|
767
780
|
})),
|
|
781
|
+
marks: z.array(MarkView).optional().describe("The page each saved mark of a batch was made on"),
|
|
768
782
|
threadCount: z.number(),
|
|
769
783
|
omitted: z.strictObject({
|
|
770
784
|
elements: z.number(),
|
|
771
785
|
attachments: z.number(),
|
|
786
|
+
marks: z.number(),
|
|
772
787
|
promptCharacters: z.number(),
|
|
773
788
|
note: z.string()
|
|
774
|
-
}).optional().describe("Elements and
|
|
789
|
+
}).optional().describe("Elements, attachments, and mark pages left out of a concise response"),
|
|
775
790
|
history: z.array(StatusEvent).optional(),
|
|
776
791
|
thread: z.array(ThreadEntry).optional()
|
|
777
792
|
});
|
|
@@ -801,6 +816,7 @@ function elementView(element, detail) {
|
|
|
801
816
|
const full = detail === "full";
|
|
802
817
|
const view = {
|
|
803
818
|
n: element.n,
|
|
819
|
+
url: optionalPageText(element.url, CAP.label),
|
|
804
820
|
source: optionalPageText(element.source, CAP.label),
|
|
805
821
|
usedAt: optionalPageText(element.usedAt, CAP.label),
|
|
806
822
|
owners: element.owners.slice(0, CAP.owners).map((owner) => pageText(owner, CAP.short)),
|
|
@@ -840,6 +856,11 @@ function annotationView(record, detail) {
|
|
|
840
856
|
})),
|
|
841
857
|
threadCount: record.thread.length
|
|
842
858
|
};
|
|
859
|
+
if (annotation.marks !== void 0) view.marks = annotation.marks.map((mark) => ({
|
|
860
|
+
n: mark.n,
|
|
861
|
+
url: pageText(mark.url, CAP.label),
|
|
862
|
+
route: pageText(mark.route, CAP.label)
|
|
863
|
+
}));
|
|
843
864
|
if (full) {
|
|
844
865
|
view.viewport = annotation.viewport;
|
|
845
866
|
view.history = state.history;
|
|
@@ -861,16 +882,19 @@ function promptPrefix(prompt) {
|
|
|
861
882
|
}
|
|
862
883
|
}
|
|
863
884
|
/**
|
|
864
|
-
* Caps the prompt, then keeps the leading elements and
|
|
865
|
-
* `[element n]
|
|
885
|
+
* Caps the prompt, then keeps the leading elements, attachments, and mark pages that fit
|
|
886
|
+
* CONCISE_BYTES, so `[element n]`, `[attachment n]`, and `## Mark n` keep their numbers, and
|
|
887
|
+
* says how much was left out.
|
|
866
888
|
*/
|
|
867
889
|
function withinBudget(view) {
|
|
868
890
|
const { elements, attachments } = view;
|
|
891
|
+
const marks = view.marks ?? [];
|
|
869
892
|
const prompt = promptPrefix(view.prompt);
|
|
870
893
|
const promptCharacters = view.prompt.length - prompt.length;
|
|
871
|
-
const omitted = (keptElements, keptAttachments) => ({
|
|
894
|
+
const omitted = (keptElements, keptAttachments, keptMarks) => ({
|
|
872
895
|
elements: elements.length - keptElements,
|
|
873
896
|
attachments: attachments.length - keptAttachments,
|
|
897
|
+
marks: marks.length - keptMarks,
|
|
874
898
|
promptCharacters,
|
|
875
899
|
note: OMITTED_NOTE
|
|
876
900
|
});
|
|
@@ -879,7 +903,8 @@ function withinBudget(view) {
|
|
|
879
903
|
prompt,
|
|
880
904
|
elements: [],
|
|
881
905
|
attachments: [],
|
|
882
|
-
|
|
906
|
+
...view.marks !== void 0 && { marks: [] },
|
|
907
|
+
omitted: omitted(0, 0, 0)
|
|
883
908
|
});
|
|
884
909
|
const fit = (items) => {
|
|
885
910
|
const kept = [];
|
|
@@ -893,14 +918,16 @@ function withinBudget(view) {
|
|
|
893
918
|
};
|
|
894
919
|
const keptElements = fit(elements);
|
|
895
920
|
const keptAttachments = fit(attachments);
|
|
896
|
-
const
|
|
921
|
+
const keptMarks = fit(marks);
|
|
922
|
+
const result = promptCharacters === 0 && keptElements.length === elements.length && keptAttachments.length === attachments.length && keptMarks.length === marks.length ? view : {
|
|
897
923
|
...view,
|
|
898
924
|
prompt,
|
|
899
925
|
elements: keptElements,
|
|
900
926
|
attachments: keptAttachments,
|
|
901
|
-
|
|
927
|
+
...view.marks !== void 0 && { marks: keptMarks },
|
|
928
|
+
omitted: omitted(keptElements.length, keptAttachments.length, keptMarks.length)
|
|
902
929
|
};
|
|
903
|
-
if (jsonBytes(result) > 2e4) throw new PkaError(`The concise view of annotation ${view.id} is over ${CONCISE_BYTES} bytes without its elements and
|
|
930
|
+
if (jsonBytes(result) > 2e4) throw new PkaError(`The concise view of annotation ${view.id} is over ${CONCISE_BYTES} bytes without its elements, attachments, and mark pages; request detail full.`);
|
|
904
931
|
return result;
|
|
905
932
|
}
|
|
906
933
|
const OMITTED_NOTE = "Left out to keep this response small. get_annotation (pka get) with detail full returns all of them.";
|
|
@@ -1108,13 +1135,13 @@ function isOffered(state, claim) {
|
|
|
1108
1135
|
if (claim === void 0) return state.status === "pending";
|
|
1109
1136
|
return isOrphaned(state, claim, Date.now());
|
|
1110
1137
|
}
|
|
1138
|
+
async function isOfferedId(store, id) {
|
|
1139
|
+
const state = await readListedState(store, id);
|
|
1140
|
+
return state !== void 0 && !isClosed(state.status) && isOffered(state, await readClaim(store, id));
|
|
1141
|
+
}
|
|
1111
1142
|
async function oldestOffered(store, skip) {
|
|
1112
1143
|
const ids = (await listIds(store)).filter((id) => !skip.has(id));
|
|
1113
|
-
|
|
1114
|
-
const state = await readListedState(store, id);
|
|
1115
|
-
return state !== void 0 && !isClosed(state.status) && isOffered(state, await readClaim(store, id));
|
|
1116
|
-
};
|
|
1117
|
-
for (let start = 0; start < ids.length; start += SCAN_BATCH) for (const id of await firstMatching(ids.slice(start, start + SCAN_BATCH), SCAN_BATCH, offered)) {
|
|
1144
|
+
for (let start = 0; start < ids.length; start += SCAN_BATCH) for (const id of await firstMatching(ids.slice(start, start + SCAN_BATCH), SCAN_BATCH, (id) => isOfferedId(store, id))) {
|
|
1118
1145
|
const record = await loadListed(store, id);
|
|
1119
1146
|
if (record !== void 0 && isOffered(record.state, record.claim)) return record;
|
|
1120
1147
|
}
|
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-
|
|
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-82fajWWq.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";
|
|
@@ -27,7 +28,10 @@ const CHANNEL = {
|
|
|
27
28
|
presence: "pka:presence",
|
|
28
29
|
agent: "pka:agent",
|
|
29
30
|
setup: "pka:setup",
|
|
30
|
-
setupInfo: "pka:setup-info"
|
|
31
|
+
setupInfo: "pka:setup-info",
|
|
32
|
+
update: "pka:update",
|
|
33
|
+
installUpdate: "pka:install-update",
|
|
34
|
+
updateResult: "pka:update-result"
|
|
31
35
|
};
|
|
32
36
|
const MAX_CHUNK_BYTES = 524288;
|
|
33
37
|
const CreateMessage = z.strictObject({
|
|
@@ -52,6 +56,11 @@ const SymbolicateMessage = z.strictObject({
|
|
|
52
56
|
});
|
|
53
57
|
const PresenceMessage = z.strictObject({});
|
|
54
58
|
const SetupMessage = z.strictObject({ requestId: Id });
|
|
59
|
+
const Version = z.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/);
|
|
60
|
+
const InstallUpdateMessage = z.strictObject({
|
|
61
|
+
requestId: Id,
|
|
62
|
+
version: Version
|
|
63
|
+
});
|
|
55
64
|
z.strictObject({
|
|
56
65
|
requestId: Id,
|
|
57
66
|
id: Id,
|
|
@@ -97,6 +106,18 @@ z.strictObject({
|
|
|
97
106
|
}).nullable(),
|
|
98
107
|
cause: z.enum(["presence", "change"])
|
|
99
108
|
});
|
|
109
|
+
z.strictObject({
|
|
110
|
+
current: Version,
|
|
111
|
+
latest: Version
|
|
112
|
+
});
|
|
113
|
+
z.discriminatedUnion("outcome", [z.strictObject({
|
|
114
|
+
requestId: Id,
|
|
115
|
+
outcome: z.enum(["restart", "restart-manually"])
|
|
116
|
+
}), z.strictObject({
|
|
117
|
+
requestId: Id,
|
|
118
|
+
outcome: z.literal("failed"),
|
|
119
|
+
message: z.string()
|
|
120
|
+
})]);
|
|
100
121
|
//#endregion
|
|
101
122
|
//#region src/vite/source.ts
|
|
102
123
|
const ATTRIBUTE = "data-pka-src";
|
|
@@ -226,6 +247,213 @@ async function symbolicate(environment, workspaceRoot, stack) {
|
|
|
226
247
|
};
|
|
227
248
|
}
|
|
228
249
|
//#endregion
|
|
250
|
+
//#region src/vite/update.ts
|
|
251
|
+
const execFileAsync$1 = promisify(execFile);
|
|
252
|
+
const REGISTRY_LATEST = "https://registry.npmjs.org/pk-annotator/latest";
|
|
253
|
+
const CHECK_TTL_MS = 36e5;
|
|
254
|
+
const INSTALL_TIMEOUT_MS = 3e5;
|
|
255
|
+
const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
256
|
+
const REGISTRY_SPEC = /^([~^]?)\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
257
|
+
const LOCKFILES = [
|
|
258
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
259
|
+
["bun.lock", "bun"],
|
|
260
|
+
["bun.lockb", "bun"],
|
|
261
|
+
["yarn.lock", "yarn"],
|
|
262
|
+
["package-lock.json", "npm"]
|
|
263
|
+
];
|
|
264
|
+
const Manifest = z.object({
|
|
265
|
+
dependencies: z.record(z.string(), z.string()).optional(),
|
|
266
|
+
devDependencies: z.record(z.string(), z.string()).optional(),
|
|
267
|
+
packageManager: z.string().optional()
|
|
268
|
+
});
|
|
269
|
+
const Published = z.object({ version: z.string().regex(SEMVER) });
|
|
270
|
+
/** This module's own package.json; a self-reference resolves through its real path. */
|
|
271
|
+
const OWN_MANIFEST = createRequire(import.meta.url).resolve("pk-annotator/package.json");
|
|
272
|
+
const RUNNING = Published.parse(JSON.parse(readFileSync(OWN_MANIFEST, "utf8"))).version;
|
|
273
|
+
function parseVersion(version) {
|
|
274
|
+
const match = SEMVER.exec(version);
|
|
275
|
+
if (match === null) throw new Error(`Not a semantic version: ${version}`);
|
|
276
|
+
return {
|
|
277
|
+
core: [
|
|
278
|
+
Number(match[1]),
|
|
279
|
+
Number(match[2]),
|
|
280
|
+
Number(match[3])
|
|
281
|
+
],
|
|
282
|
+
pre: match[4]
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/** Whether `candidate` is a later release than `current`; a prerelease never outranks its release. */
|
|
286
|
+
function isNewer(candidate, current) {
|
|
287
|
+
const next = parseVersion(candidate);
|
|
288
|
+
const now = parseVersion(current);
|
|
289
|
+
for (const [index, part] of next.core.entries()) {
|
|
290
|
+
const other = now.core[index];
|
|
291
|
+
if (part !== other) return part > other;
|
|
292
|
+
}
|
|
293
|
+
return next.pre === void 0 && now.pre !== void 0;
|
|
294
|
+
}
|
|
295
|
+
function installCommand(install, version) {
|
|
296
|
+
const dev = {
|
|
297
|
+
pnpm: "--save-dev",
|
|
298
|
+
npm: "--save-dev",
|
|
299
|
+
yarn: "--dev",
|
|
300
|
+
bun: "--dev"
|
|
301
|
+
}[install.manager];
|
|
302
|
+
const exact = {
|
|
303
|
+
pnpm: "--save-exact",
|
|
304
|
+
npm: "--save-exact",
|
|
305
|
+
yarn: "--exact",
|
|
306
|
+
bun: "--exact"
|
|
307
|
+
}[install.manager];
|
|
308
|
+
return [
|
|
309
|
+
install.manager,
|
|
310
|
+
install.manager === "npm" ? "install" : "add",
|
|
311
|
+
...install.dev ? [dev] : [],
|
|
312
|
+
...install.exact ? [exact] : [],
|
|
313
|
+
`pk-annotator@${version}`
|
|
314
|
+
];
|
|
315
|
+
}
|
|
316
|
+
async function readManifest(dir) {
|
|
317
|
+
const file = path.join(dir, "package.json");
|
|
318
|
+
const text = await readFile(file, "utf8").catch((cause) => {
|
|
319
|
+
if (isErrno(cause, "ENOENT")) return void 0;
|
|
320
|
+
throw cause;
|
|
321
|
+
});
|
|
322
|
+
if (text === void 0) return void 0;
|
|
323
|
+
const parsed = Manifest.safeParse(JSON.parse(text));
|
|
324
|
+
if (!parsed.success) throw new Error(`${file} is not a valid package.json: ${parsed.error.message}`);
|
|
325
|
+
return parsed.data;
|
|
326
|
+
}
|
|
327
|
+
async function exists(file) {
|
|
328
|
+
return stat(file).then(() => true, (cause) => {
|
|
329
|
+
if (isErrno(cause, "ENOENT")) return false;
|
|
330
|
+
throw cause;
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* The manifest that declares pk-annotator, from the Vite root up to the workspace root, and
|
|
335
|
+
* the workspace's package manager. Null when the dependency is not a registry version, for
|
|
336
|
+
* example this repository's fixture or a workspace link.
|
|
337
|
+
*/
|
|
338
|
+
async function findInstall(viteRoot, workspaceRoot) {
|
|
339
|
+
if (!OWN_MANIFEST.split(path.sep).includes("node_modules")) return null;
|
|
340
|
+
let declared;
|
|
341
|
+
for (let dir = path.resolve(viteRoot);; dir = path.dirname(dir)) {
|
|
342
|
+
const manifest = await readManifest(dir);
|
|
343
|
+
const dev = manifest?.devDependencies?.["pk-annotator"];
|
|
344
|
+
const spec = dev ?? manifest?.dependencies?.["pk-annotator"];
|
|
345
|
+
if (spec !== void 0) {
|
|
346
|
+
const match = REGISTRY_SPEC.exec(spec);
|
|
347
|
+
if (match === null) return null;
|
|
348
|
+
declared = {
|
|
349
|
+
dir,
|
|
350
|
+
dev: dev !== void 0,
|
|
351
|
+
exact: match[1] === ""
|
|
352
|
+
};
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
if (dir === path.resolve(workspaceRoot) || dir === path.dirname(dir)) return null;
|
|
356
|
+
}
|
|
357
|
+
const field = (await readManifest(workspaceRoot))?.packageManager?.split("@")[0];
|
|
358
|
+
if (field === "pnpm" || field === "npm" || field === "yarn" || field === "bun") return {
|
|
359
|
+
...declared,
|
|
360
|
+
manager: field
|
|
361
|
+
};
|
|
362
|
+
for (const [lockfile, manager] of LOCKFILES) if (await exists(path.join(workspaceRoot, lockfile))) return {
|
|
363
|
+
...declared,
|
|
364
|
+
manager
|
|
365
|
+
};
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
/** The real path of the pk-annotator manifest that `dir` resolves, read fresh from disk. */
|
|
369
|
+
async function resolveInstalled(dir) {
|
|
370
|
+
for (let from = dir;; from = path.dirname(from)) {
|
|
371
|
+
const found = await realpath(path.join(from, "node_modules/pk-annotator/package.json")).catch((cause) => {
|
|
372
|
+
if (isErrno(cause, "ENOENT")) return void 0;
|
|
373
|
+
throw cause;
|
|
374
|
+
});
|
|
375
|
+
if (found !== void 0) return found;
|
|
376
|
+
if (from === path.dirname(from)) throw new Error(`${dir} resolves no pk-annotator`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Checks npm for a newer pk-annotator when a page mounts, at most once an hour, and installs
|
|
381
|
+
* it on request with the workspace's package manager. Nothing runs between those events.
|
|
382
|
+
*/
|
|
383
|
+
function createUpdater(viteRoot, workspaceRoot, warn) {
|
|
384
|
+
let install;
|
|
385
|
+
let checked;
|
|
386
|
+
let installing = false;
|
|
387
|
+
let staleWarned = false;
|
|
388
|
+
async function latest() {
|
|
389
|
+
if (checked === void 0 || Date.now() - checked.at > CHECK_TTL_MS) {
|
|
390
|
+
const request = fetch(REGISTRY_LATEST, { signal: AbortSignal.timeout(1e4) }).then(async (response) => {
|
|
391
|
+
if (!response.ok) throw new Error(`${REGISTRY_LATEST} answered ${response.status}`);
|
|
392
|
+
return Published.parse(await response.json()).version;
|
|
393
|
+
}).catch((cause) => {
|
|
394
|
+
warn(`update check failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
395
|
+
return null;
|
|
396
|
+
});
|
|
397
|
+
checked = {
|
|
398
|
+
at: Date.now(),
|
|
399
|
+
latest: request
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
return checked.latest;
|
|
403
|
+
}
|
|
404
|
+
return {
|
|
405
|
+
/** The update to offer, or null when the installed version is current or not managed by version. */
|
|
406
|
+
async check() {
|
|
407
|
+
install ??= findInstall(viteRoot, workspaceRoot);
|
|
408
|
+
if (await install === null) return null;
|
|
409
|
+
const onDisk = Published.parse(JSON.parse(await readFile(OWN_MANIFEST, "utf8"))).version;
|
|
410
|
+
if (onDisk !== RUNNING) {
|
|
411
|
+
if (!staleWarned) {
|
|
412
|
+
staleWarned = true;
|
|
413
|
+
warn(`${onDisk} is installed but ${RUNNING} is running; restart the dev server`);
|
|
414
|
+
}
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
const version = await latest();
|
|
418
|
+
return version !== null && isNewer(version, RUNNING) ? {
|
|
419
|
+
current: RUNNING,
|
|
420
|
+
latest: version
|
|
421
|
+
} : null;
|
|
422
|
+
},
|
|
423
|
+
/**
|
|
424
|
+
* Installs `version`, which must be the release the last check offered. "restart" means a
|
|
425
|
+
* dev server restart loads it; "restart-manually" means the package was replaced in place,
|
|
426
|
+
* where Node keeps the old plugin module until the process restarts.
|
|
427
|
+
*/
|
|
428
|
+
async install(version) {
|
|
429
|
+
const target = await install;
|
|
430
|
+
if (target === void 0 || target === null) throw new Error("This workspace does not install pk-annotator from the npm registry");
|
|
431
|
+
if (checked === void 0 || await checked.latest !== version) throw new Error(`${version} is not the release last offered`);
|
|
432
|
+
if (installing) throw new Error("An update is already installing");
|
|
433
|
+
installing = true;
|
|
434
|
+
try {
|
|
435
|
+
const [command, ...args] = installCommand(target, version);
|
|
436
|
+
try {
|
|
437
|
+
await execFileAsync$1(command, args, {
|
|
438
|
+
cwd: target.dir,
|
|
439
|
+
timeout: INSTALL_TIMEOUT_MS
|
|
440
|
+
});
|
|
441
|
+
} catch (cause) {
|
|
442
|
+
const stderr = cause instanceof Error && "stderr" in cause ? String(cause.stderr).trim() : "";
|
|
443
|
+
const detail = stderr === "" ? String(cause) : stderr.slice(-600);
|
|
444
|
+
throw new Error(`${[command, ...args].join(" ")} failed in ${target.dir}: ${detail}`, { cause });
|
|
445
|
+
}
|
|
446
|
+
const resolved = await resolveInstalled(target.dir);
|
|
447
|
+
const installed = Published.parse(JSON.parse(await readFile(resolved, "utf8"))).version;
|
|
448
|
+
if (installed !== version) throw new Error(`Installed ${version}, but ${target.dir} resolves pk-annotator ${installed}`);
|
|
449
|
+
return resolved === OWN_MANIFEST ? "restart-manually" : "restart";
|
|
450
|
+
} finally {
|
|
451
|
+
installing = false;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
//#endregion
|
|
229
457
|
//#region src/vite/index.ts
|
|
230
458
|
const AnnotatorOptions = z.strictObject({
|
|
231
459
|
bodies: z.array(z.string().startsWith("/")).optional(),
|
|
@@ -544,6 +772,27 @@ async function serve(server, maxStoreBytes, storeRoot) {
|
|
|
544
772
|
};
|
|
545
773
|
client.send(CHANNEL.setupInfo, reply);
|
|
546
774
|
});
|
|
775
|
+
const updater = createUpdater(server.config.root, workspaceRoot, warn);
|
|
776
|
+
listen(CHANNEL.installUpdate, InstallUpdateMessage, async (message, client) => {
|
|
777
|
+
let result;
|
|
778
|
+
try {
|
|
779
|
+
const outcome = await updater.install(message.version);
|
|
780
|
+
result = {
|
|
781
|
+
requestId: message.requestId,
|
|
782
|
+
outcome
|
|
783
|
+
};
|
|
784
|
+
} catch (cause) {
|
|
785
|
+
error(`update to ${message.version} failed: ${describeError(cause)}`);
|
|
786
|
+
result = {
|
|
787
|
+
requestId: message.requestId,
|
|
788
|
+
outcome: "failed",
|
|
789
|
+
message: describeError(cause)
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
client.send(CHANNEL.updateResult, result);
|
|
793
|
+
if (result.outcome === "restart-manually") warn(`updated to ${message.version}; restart the dev server to load it`);
|
|
794
|
+
if (result.outcome === "restart") await server.restart();
|
|
795
|
+
});
|
|
547
796
|
listen(CHANNEL.sync, SyncMessage, async (message, client) => {
|
|
548
797
|
const synced = { annotations: [] };
|
|
549
798
|
for (const id of message.ids) try {
|
|
@@ -592,6 +841,9 @@ async function serve(server, maxStoreBytes, storeRoot) {
|
|
|
592
841
|
agentQueue = agentQueue.then(async () => pushAgent(await readAgent())).catch((cause) => error(`reading agent presence failed: ${describeError(cause)}`));
|
|
593
842
|
}
|
|
594
843
|
listen(CHANNEL.presence, PresenceMessage, async (_message, client) => {
|
|
844
|
+
updater.check().then((offer) => {
|
|
845
|
+
if (offer !== null) client.send(CHANNEL.update, offer);
|
|
846
|
+
}, (cause) => error(`update check failed: ${describeError(cause)}`));
|
|
595
847
|
const answered = agentQueue.then(async () => {
|
|
596
848
|
const agent = await readAgent();
|
|
597
849
|
pushAgent(agent);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pk-annotator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|