@inspecto-dev/plugin 0.3.8 → 0.3.10

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 (45) hide show
  1. package/dist/astro.cjs +572 -86
  2. package/dist/astro.cjs.map +1 -1
  3. package/dist/astro.d.cts +1 -1
  4. package/dist/astro.d.ts +1 -1
  5. package/dist/astro.js +571 -85
  6. package/dist/astro.js.map +1 -1
  7. package/dist/index.cjs +559 -83
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +1 -0
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.js +558 -82
  12. package/dist/index.js.map +1 -1
  13. package/dist/legacy/rspack/index.cjs +526 -74
  14. package/dist/legacy/rspack/index.cjs.map +1 -1
  15. package/dist/legacy/rspack/index.js +526 -74
  16. package/dist/legacy/rspack/index.js.map +1 -1
  17. package/dist/legacy/rspack/loader.cjs +4 -1
  18. package/dist/legacy/rspack/loader.cjs.map +1 -1
  19. package/dist/legacy/rspack/loader.js +3 -0
  20. package/dist/legacy/rspack/loader.js.map +1 -1
  21. package/dist/legacy/webpack4/index.cjs +526 -74
  22. package/dist/legacy/webpack4/index.cjs.map +1 -1
  23. package/dist/legacy/webpack4/index.js +526 -74
  24. package/dist/legacy/webpack4/index.js.map +1 -1
  25. package/dist/legacy/webpack4/loader.cjs +4 -1
  26. package/dist/legacy/webpack4/loader.cjs.map +1 -1
  27. package/dist/legacy/webpack4/loader.js +3 -0
  28. package/dist/legacy/webpack4/loader.js.map +1 -1
  29. package/dist/rollup.cjs +559 -83
  30. package/dist/rollup.cjs.map +1 -1
  31. package/dist/rollup.js +558 -82
  32. package/dist/rollup.js.map +1 -1
  33. package/dist/rspack.cjs +559 -83
  34. package/dist/rspack.cjs.map +1 -1
  35. package/dist/rspack.js +558 -82
  36. package/dist/rspack.js.map +1 -1
  37. package/dist/vite.cjs +559 -83
  38. package/dist/vite.cjs.map +1 -1
  39. package/dist/vite.js +558 -82
  40. package/dist/vite.js.map +1 -1
  41. package/dist/webpack.cjs +559 -83
  42. package/dist/webpack.cjs.map +1 -1
  43. package/dist/webpack.js +558 -82
  44. package/dist/webpack.js.map +1 -1
  45. package/package.json +8 -12
package/dist/astro.js CHANGED
@@ -520,7 +520,6 @@ function dispatchPromptThroughIde(runtime, payload) {
520
520
  line: payload.line,
521
521
  column: payload.column,
522
522
  snippet: payload.snippet,
523
- ...payload.screenshotContext ? { screenshotContext: payload.screenshotContext } : {},
524
523
  overrides: runtime.overrides,
525
524
  autoSend: runtime.autoSend
526
525
  });
@@ -654,6 +653,188 @@ function assertPathWithinIdeOpenScope(file, projectRoot) {
654
653
  }
655
654
  }
656
655
 
656
+ // src/server/session-store.ts
657
+ var DEFAULT_STATUS = "pending";
658
+ function createAnnotationSessionStore(options = {}) {
659
+ const sessions = /* @__PURE__ */ new Map();
660
+ const listeners = /* @__PURE__ */ new Set();
661
+ const now = options.now ?? (() => Date.now());
662
+ const createId = options.createId ?? createRandomId;
663
+ function findNewestMatchingSession(statuses) {
664
+ return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
665
+ }
666
+ function updateSessionStatus(id, status) {
667
+ const session = sessions.get(id);
668
+ if (!session) return null;
669
+ const timestamp = now();
670
+ session.status = status;
671
+ session.updatedAt = timestamp;
672
+ if (status === "acknowledged") {
673
+ session.acknowledgedAt = timestamp;
674
+ }
675
+ if (status === "resolved") {
676
+ session.resolvedAt = timestamp;
677
+ }
678
+ emit({ type: "session-status-updated", session });
679
+ return cloneSession(session);
680
+ }
681
+ function claimSession(id, statuses) {
682
+ const session = sessions.get(id);
683
+ if (!session || statuses && !statuses.has(session.status)) return null;
684
+ if (session.status === "acknowledged") return cloneSession(session);
685
+ return updateSessionStatus(id, "acknowledged");
686
+ }
687
+ function emit(event) {
688
+ const snapshot = cloneSession(event.session);
689
+ for (const listener of listeners) {
690
+ listener({ type: event.type, session: snapshot });
691
+ }
692
+ }
693
+ const store = {
694
+ createSession(input) {
695
+ const timestamp = now();
696
+ const session = {
697
+ id: createId(),
698
+ instruction: input.instruction?.trim() ?? "",
699
+ annotations: cloneArray(input.annotations),
700
+ ...input.deliveryMode ? { deliveryMode: input.deliveryMode } : {},
701
+ status: DEFAULT_STATUS,
702
+ messages: cloneArray(input.messages ?? []),
703
+ createdAt: timestamp,
704
+ updatedAt: timestamp,
705
+ ...input.runtimeContext ? { runtimeContext: cloneValue(input.runtimeContext) } : {},
706
+ ...input.cssContextPrompt?.trim() ? { cssContextPrompt: input.cssContextPrompt.trim() } : {},
707
+ ...input.pageUrl ? { pageUrl: input.pageUrl } : {},
708
+ ...input.route ? { route: input.route } : {}
709
+ };
710
+ sessions.set(session.id, session);
711
+ emit({ type: "session-created", session });
712
+ return cloneSession(session);
713
+ },
714
+ getSession(id) {
715
+ const session = sessions.get(id);
716
+ return session ? cloneSession(session) : null;
717
+ },
718
+ listSessions(options2 = {}) {
719
+ const statuses = normalizeStatuses(options2.status);
720
+ return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt).map((session) => cloneSession(session));
721
+ },
722
+ async claimNextSession(options2 = {}) {
723
+ const statuses = normalizeStatuses(DEFAULT_STATUS);
724
+ const existingSession = findNewestMatchingSession(statuses);
725
+ if (existingSession) {
726
+ return {
727
+ session: claimSession(existingSession.id, statuses),
728
+ timedOut: false,
729
+ matchedExisting: true
730
+ };
731
+ }
732
+ const timeoutMs = normalizeTimeoutMs(options2.timeoutMs);
733
+ if (timeoutMs === 0) {
734
+ return {
735
+ session: null,
736
+ timedOut: true,
737
+ matchedExisting: false
738
+ };
739
+ }
740
+ return await new Promise((resolve2) => {
741
+ let settled = false;
742
+ let timeout = null;
743
+ const finish = (result) => {
744
+ if (settled) return;
745
+ settled = true;
746
+ unsubscribe();
747
+ if (timeout) {
748
+ clearTimeout(timeout);
749
+ }
750
+ resolve2(result);
751
+ };
752
+ const unsubscribe = this.subscribe((event) => {
753
+ const session = claimSession(event.session.id, statuses);
754
+ if (!session) return;
755
+ finish({
756
+ session,
757
+ timedOut: false,
758
+ matchedExisting: false,
759
+ event: event.type
760
+ });
761
+ });
762
+ if (timeoutMs !== null) {
763
+ timeout = setTimeout(() => {
764
+ finish({
765
+ session: null,
766
+ timedOut: true,
767
+ matchedExisting: false
768
+ });
769
+ }, timeoutMs);
770
+ }
771
+ });
772
+ },
773
+ appendMessage(id, input) {
774
+ const session = sessions.get(id);
775
+ if (!session) return null;
776
+ const timestamp = now();
777
+ session.messages.push({
778
+ id: createId(),
779
+ role: input.role,
780
+ text: input.text,
781
+ createdAt: timestamp
782
+ });
783
+ session.updatedAt = timestamp;
784
+ if (input.role === "agent" && isPendingLikeStatus(session.status)) {
785
+ session.status = "in_progress";
786
+ }
787
+ emit({ type: "session-message-appended", session });
788
+ return cloneSession(session);
789
+ },
790
+ updateStatus(id, status) {
791
+ return updateSessionStatus(id, status);
792
+ },
793
+ subscribe(listener) {
794
+ listeners.add(listener);
795
+ return () => {
796
+ listeners.delete(listener);
797
+ };
798
+ },
799
+ clear() {
800
+ sessions.clear();
801
+ listeners.clear();
802
+ }
803
+ };
804
+ return store;
805
+ }
806
+ var annotationSessionStore = createAnnotationSessionStore();
807
+ function normalizeStatuses(status) {
808
+ if (!status) return null;
809
+ return new Set(Array.isArray(status) ? status : [status]);
810
+ }
811
+ function normalizeTimeoutMs(value) {
812
+ if (value === void 0) return null;
813
+ if (!Number.isFinite(value)) return 0;
814
+ return Math.max(0, Math.floor(value));
815
+ }
816
+ function isPendingLikeStatus(status) {
817
+ return status === "pending" || status === "acknowledged";
818
+ }
819
+ function hasAgentReply(session) {
820
+ return session.messages.some((message) => message.role === "agent" && Boolean(message.text?.trim()));
821
+ }
822
+ function createRandomId() {
823
+ return `annotation-session-${Math.random().toString(36).slice(2, 10)}`;
824
+ }
825
+ function cloneSession(session) {
826
+ return cloneValue(session);
827
+ }
828
+ function cloneArray(value) {
829
+ return cloneValue(value);
830
+ }
831
+ function cloneValue(value) {
832
+ if (typeof structuredClone === "function") {
833
+ return structuredClone(value);
834
+ }
835
+ return JSON.parse(JSON.stringify(value));
836
+ }
837
+
657
838
  // src/server/annotation-dispatch.ts
658
839
  var AnnotationDispatchError = class extends Error {
659
840
  constructor(message, errorCode) {
@@ -662,20 +843,30 @@ var AnnotationDispatchError = class extends Error {
662
843
  this.errorCode = errorCode;
663
844
  }
664
845
  };
665
- async function dispatchAnnotationsToAi(req, state) {
846
+ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStore) {
666
847
  try {
667
848
  validateAnnotationDispatchRequest(req, state);
668
849
  const batch = normalizeAnnotationBatch(req);
669
850
  const prompt = buildAnnotationBatchPrompt(batch);
851
+ const deliveryMode = normalizeDeliveryMode(req.deliveryMode);
852
+ const session = store.createSession({
853
+ instruction: batch.instruction,
854
+ annotations: toSessionAnnotations(batch.annotations),
855
+ deliveryMode,
856
+ ...batch.runtimeContext ? { runtimeContext: batch.runtimeContext } : {},
857
+ ...batch.cssContextPrompt ? { cssContextPrompt: batch.cssContextPrompt } : {}
858
+ });
670
859
  const representativeTarget = batch.annotations[0]?.targets[0];
671
- const runtime = resolvePromptDispatchRuntime(state);
672
- return dispatchPromptThroughIde(runtime, {
860
+ const dispatchResult = deliveryMode === "ide" ? dispatchPromptThroughIde(resolvePromptDispatchRuntime(state), {
673
861
  prompt,
674
862
  ...representativeTarget?.file ? { filePath: representativeTarget.file } : {},
675
863
  ...representativeTarget?.line ? { line: representativeTarget.line } : {},
676
- ...representativeTarget?.column ? { column: representativeTarget.column } : {},
677
- ...batch.screenshotContext ? { screenshotContext: batch.screenshotContext } : {}
678
- });
864
+ ...representativeTarget?.column ? { column: representativeTarget.column } : {}
865
+ }) : { success: true };
866
+ return {
867
+ ...dispatchResult,
868
+ session: toSessionSummary(session)
869
+ };
679
870
  } catch (error) {
680
871
  return {
681
872
  success: false,
@@ -684,6 +875,41 @@ async function dispatchAnnotationsToAi(req, state) {
684
875
  };
685
876
  }
686
877
  }
878
+ function normalizeDeliveryMode(input) {
879
+ return input === "agent" ? "agent" : "ide";
880
+ }
881
+ function toSessionAnnotations(annotations) {
882
+ return annotations.map((annotation) => ({
883
+ id: `annotation-${annotation.index}`,
884
+ note: annotation.note,
885
+ intent: annotation.intent,
886
+ targets: annotation.targets.map((target, targetIndex) => ({
887
+ id: `annotation-${annotation.index}-target-${targetIndex + 1}`,
888
+ label: target.label ?? "Unknown target",
889
+ location: {
890
+ file: target.file,
891
+ line: target.line,
892
+ column: target.column
893
+ },
894
+ ...target.selector ? { selector: target.selector } : {},
895
+ ...target.snippet ? { snippet: target.snippet } : {},
896
+ rect: {
897
+ x: 0,
898
+ y: 0,
899
+ width: 0,
900
+ height: 0
901
+ }
902
+ }))
903
+ }));
904
+ }
905
+ function toSessionSummary(session) {
906
+ return {
907
+ id: session.id,
908
+ status: session.status,
909
+ createdAt: session.createdAt,
910
+ updatedAt: session.updatedAt
911
+ };
912
+ }
687
913
  function validateAnnotationDispatchRequest(req, state) {
688
914
  if (!req.annotations.length) {
689
915
  throw new AnnotationDispatchError("At least one annotation is required.", "INVALID_REQUEST");
@@ -704,9 +930,7 @@ function validateAnnotationDispatchRequest(req, state) {
704
930
  function normalizeAnnotationBatch(req) {
705
931
  return {
706
932
  instruction: req.instruction?.trim() ?? "",
707
- responseMode: req.responseMode ?? "unified",
708
933
  ...req.runtimeContext ? { runtimeContext: req.runtimeContext } : {},
709
- ...req.screenshotContext ? { screenshotContext: req.screenshotContext } : {},
710
934
  ...req.cssContextPrompt?.trim() ? { cssContextPrompt: req.cssContextPrompt.trim() } : {},
711
935
  annotations: req.annotations.map((annotation, index) => ({
712
936
  index: index + 1,
@@ -728,12 +952,9 @@ function buildAnnotationBatchPrompt(batch) {
728
952
  const prompt = batch.instruction ? `${batch.instruction}
729
953
 
730
954
  ${body}` : body;
731
- return appendScreenshotContextSection(
732
- appendCssContextSection(
733
- appendRuntimeContextSection(prompt, batch.runtimeContext),
734
- batch.cssContextPrompt
735
- ),
736
- batch.screenshotContext
955
+ return appendCssContextSection(
956
+ appendRuntimeContextSection(prompt, batch.runtimeContext),
957
+ batch.cssContextPrompt
737
958
  );
738
959
  }
739
960
  function appendCssContextSection(prompt, cssContextPrompt) {
@@ -760,20 +981,6 @@ function buildSelectedElementsPrompt(annotations) {
760
981
  }
761
982
  return lines.join("\n");
762
983
  }
763
- function appendScreenshotContextSection(prompt, screenshotContext) {
764
- if (!screenshotContext || !screenshotContext.imageDataUrl && !screenshotContext.imageAssetId) {
765
- return prompt;
766
- }
767
- const lines = [
768
- "Visual screenshot context attached:",
769
- `- capturedAt=${screenshotContext.capturedAt}`,
770
- `- mimeType=${screenshotContext.mimeType}`,
771
- ...screenshotContext.imageAssetId ? [`- imageAssetId=${screenshotContext.imageAssetId}`] : []
772
- ];
773
- return `${prompt}
774
-
775
- ${lines.join("\n")}`;
776
- }
777
984
  function appendRuntimeContextSection(prompt, runtimeContext) {
778
985
  if (!runtimeContext?.records.length) {
779
986
  return prompt;
@@ -821,7 +1028,7 @@ async function buildClientConfig(serverState2) {
821
1028
  ...info,
822
1029
  prompts: resolveIntents(promptsConfig),
823
1030
  hotKeys: userConfig["inspector.hotKey"] ?? "alt",
824
- theme: userConfig["inspector.theme"] ?? "auto",
1031
+ annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "both",
825
1032
  includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
826
1033
  runtimeContext: {
827
1034
  enabled: true,
@@ -829,10 +1036,6 @@ async function buildClientConfig(serverState2) {
829
1036
  maxRuntimeErrors: 3,
830
1037
  maxFailedRequests: 2
831
1038
  },
832
- screenshotContext: {
833
- enabled: false
834
- },
835
- annotationResponseMode: userConfig["prompt.annotationResponseMode"] ?? "unified",
836
1039
  autoSend: userConfig["prompt.autoSend"] ?? false
837
1040
  };
838
1041
  }
@@ -877,7 +1080,7 @@ function handleOpenFileRequest(body, serverState2) {
877
1080
  else if (rawEditorHint === "vscodium") editorHint = "codium";
878
1081
  else if (rawEditorHint === "trae-cn" || rawEditorHint === "trae") editorHint = "trae";
879
1082
  serverLogger2.debug(
880
- `IDE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
1083
+ `SOURCE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
881
1084
  );
882
1085
  if (VSCODE_FAMILY_SCHEMES.includes(rawEditorHint)) {
883
1086
  let normalizedPath = absolutePath.replace(/\\/g, "/");
@@ -886,7 +1089,7 @@ function handleOpenFileRequest(body, serverState2) {
886
1089
  }
887
1090
  const encodedPath = encodeURI(normalizedPath);
888
1091
  const uri = `${rawEditorHint}://file${encodedPath}:${body.line}:${body.column}`;
889
- serverLogger2.debug(`IDE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
1092
+ serverLogger2.debug(`SOURCE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
890
1093
  try {
891
1094
  if (process.platform === "darwin") {
892
1095
  execFileSync2("open", [uri]);
@@ -896,7 +1099,7 @@ function handleOpenFileRequest(body, serverState2) {
896
1099
  execFileSync2("xdg-open", [uri]);
897
1100
  }
898
1101
  } catch (e) {
899
- serverLogger2.error(`Failed to launch URI for IDE_OPEN (${uri}):`, e);
1102
+ serverLogger2.error(`Failed to launch URI for SOURCE_OPEN (${uri}):`, e);
900
1103
  launchIDE2({
901
1104
  file: absolutePath,
902
1105
  line: body.line,
@@ -951,8 +1154,27 @@ function resolveProjectRoot() {
951
1154
  return gitRoot;
952
1155
  }
953
1156
 
1157
+ // src/server/server-url.ts
1158
+ function resolveServerHost(cwd, configRoot) {
1159
+ const userConfig = loadUserConfigSync(false, cwd, configRoot);
1160
+ const configuredHost = userConfig["server.host"]?.trim();
1161
+ if (configuredHost) return configuredHost;
1162
+ if (process.env["VITEST"]) return "127.0.0.1";
1163
+ return "127.0.0.1";
1164
+ }
1165
+ function resolvePublicServerUrl(args) {
1166
+ const userConfig = loadUserConfigSync(false, args.cwd, args.configRoot);
1167
+ const configuredPublicUrl = userConfig["server.publicUrl"]?.trim();
1168
+ if (configuredPublicUrl) {
1169
+ return configuredPublicUrl.replace(/\/$/, "");
1170
+ }
1171
+ const host = resolveServerHost(args.cwd, args.configRoot);
1172
+ return `http://${host}:${args.port}`;
1173
+ }
1174
+
954
1175
  // src/server/index.ts
955
1176
  var serverLogger4 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
1177
+ var PORT_FILE_NAME = "inspecto.port.json";
956
1178
  var serverState = {
957
1179
  port: null,
958
1180
  running: false,
@@ -961,6 +1183,42 @@ var serverState = {
961
1183
  cwd: process.cwd()
962
1184
  };
963
1185
  var serverInstance = null;
1186
+ function getPortFilePath() {
1187
+ return path5.join(os2.tmpdir(), PORT_FILE_NAME);
1188
+ }
1189
+ function getProjectRootHash() {
1190
+ if (!serverState.projectRoot) return null;
1191
+ return crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1192
+ }
1193
+ function readPortData(portFile) {
1194
+ if (!fs5.existsSync(portFile)) return {};
1195
+ try {
1196
+ return JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1197
+ } catch {
1198
+ return {};
1199
+ }
1200
+ }
1201
+ function writeProjectPort(port) {
1202
+ const rootHash = getProjectRootHash();
1203
+ if (!rootHash) return;
1204
+ const portFile = getPortFilePath();
1205
+ const portData = readPortData(portFile);
1206
+ portData[rootHash] = port;
1207
+ fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1208
+ }
1209
+ function removeProjectPort() {
1210
+ const rootHash = getProjectRootHash();
1211
+ if (!rootHash) return;
1212
+ const portFile = getPortFilePath();
1213
+ if (!fs5.existsSync(portFile)) return;
1214
+ const portData = readPortData(portFile);
1215
+ delete portData[rootHash];
1216
+ if (Object.keys(portData).length === 0) {
1217
+ fs5.unlinkSync(portFile);
1218
+ } else {
1219
+ fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1220
+ }
1221
+ }
964
1222
  async function startServer() {
965
1223
  if (serverState.running && serverState.port !== null) {
966
1224
  return serverState.port;
@@ -968,6 +1226,7 @@ async function startServer() {
968
1226
  serverState.projectRoot = resolveProjectRoot();
969
1227
  serverState.configRoot = serverState.projectRoot;
970
1228
  serverState.cwd = process.cwd();
1229
+ const serverHost = resolveServerHost(serverState.cwd, serverState.configRoot);
971
1230
  portfinder.basePort = 5678;
972
1231
  const port = await portfinder.getPortPromise();
973
1232
  watchConfig(
@@ -994,7 +1253,7 @@ async function startServer() {
994
1253
  });
995
1254
  });
996
1255
  await new Promise((resolve2, reject) => {
997
- serverInstance.listen(port, "127.0.0.1", () => {
1256
+ serverInstance.listen(port, serverHost, () => {
998
1257
  serverInstance.unref();
999
1258
  resolve2();
1000
1259
  });
@@ -1005,37 +1264,18 @@ async function startServer() {
1005
1264
  });
1006
1265
  serverState.port = port;
1007
1266
  serverState.running = true;
1008
- const portFile = path5.join(os2.tmpdir(), "inspecto.port.json");
1009
1267
  try {
1010
- let portData = {};
1011
- if (fs5.existsSync(portFile)) {
1012
- try {
1013
- portData = JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1014
- } catch (_e) {
1015
- }
1016
- }
1017
- const rootHash = crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1018
- portData[rootHash] = port;
1019
- fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1268
+ writeProjectPort(port);
1020
1269
  } catch (_e) {
1021
1270
  serverLogger4.warn("Failed to write port file:", _e);
1022
1271
  }
1023
1272
  process.once("exit", () => {
1024
1273
  try {
1025
- if (fs5.existsSync(portFile)) {
1026
- const portData = JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1027
- const rootHash = crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1028
- delete portData[rootHash];
1029
- if (Object.keys(portData).length === 0) {
1030
- fs5.unlinkSync(portFile);
1031
- } else {
1032
- fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1033
- }
1034
- }
1274
+ removeProjectPort();
1035
1275
  } catch {
1036
1276
  }
1037
1277
  });
1038
- serverLogger4.info(`server running at http://127.0.0.1:${port}`);
1278
+ serverLogger4.info(`server running at http://${serverHost}:${port}`);
1039
1279
  return port;
1040
1280
  }
1041
1281
  async function readBody(req) {
@@ -1087,7 +1327,7 @@ async function handleRequest(url, req, res) {
1087
1327
  }
1088
1328
  return;
1089
1329
  }
1090
- if (pathname === INSPECTO_API_PATHS.IDE_OPEN && req.method === "POST") {
1330
+ if ((pathname === INSPECTO_API_PATHS.SOURCE_OPEN || pathname === INSPECTO_API_PATHS.IDE_OPEN) && req.method === "POST") {
1091
1331
  let body;
1092
1332
  try {
1093
1333
  body = JSON.parse(await readBody(req));
@@ -1100,7 +1340,7 @@ async function handleRequest(url, req, res) {
1100
1340
  handleOpenFileRequest(body, serverState);
1101
1341
  } catch (err) {
1102
1342
  serverLogger4.warn(
1103
- `Security: Blocked path traversal attempt in IDE_OPEN: ${body.file}. Reason: ${err.message}`
1343
+ `Security: Blocked path traversal attempt in SOURCE_OPEN: ${body.file}. Reason: ${err.message}`
1104
1344
  );
1105
1345
  res.writeHead(403, { "Content-Type": "application/json" });
1106
1346
  res.end(JSON.stringify({ error: "Access denied: File is outside of project workspace" }));
@@ -1174,6 +1414,212 @@ async function handleRequest(url, req, res) {
1174
1414
  }
1175
1415
  return;
1176
1416
  }
1417
+ if (pathname === INSPECTO_API_PATHS.SESSION_CLAIM_NEXT && req.method === "POST") {
1418
+ try {
1419
+ const rawBody = await readBody(req);
1420
+ const body = rawBody ? JSON.parse(rawBody) : {};
1421
+ const timeoutMs = normalizeSessionClaimTimeout(
1422
+ body.timeoutMs === void 0 ? null : String(body.timeoutMs)
1423
+ );
1424
+ const result = await annotationSessionStore.claimNextSession({
1425
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
1426
+ });
1427
+ res.writeHead(200, { "Content-Type": "application/json" });
1428
+ res.end(
1429
+ JSON.stringify({
1430
+ success: true,
1431
+ timedOut: result.timedOut,
1432
+ matchedExisting: result.matchedExisting,
1433
+ ...result.event ? { event: result.event } : {},
1434
+ ...result.session ? { session: result.session } : {}
1435
+ })
1436
+ );
1437
+ } catch (e) {
1438
+ serverLogger4.error(`Error parsing session claim request:`, e);
1439
+ res.writeHead(400, { "Content-Type": "application/json" });
1440
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1441
+ }
1442
+ return;
1443
+ }
1444
+ if (pathname === INSPECTO_API_PATHS.SESSION_EVENTS && req.method === "GET") {
1445
+ const statusParam = url.searchParams.getAll("status");
1446
+ const statuses = statusParam.length ? new Set(statusParam) : null;
1447
+ const sessionId = url.searchParams.get("sessionId")?.trim() || null;
1448
+ res.writeHead(200, {
1449
+ "Content-Type": "text/event-stream",
1450
+ "Cache-Control": "no-cache",
1451
+ Connection: "keep-alive"
1452
+ });
1453
+ res.write(`event: ready
1454
+ data: ${JSON.stringify({ ok: true })}
1455
+
1456
+ `);
1457
+ const unsubscribe = annotationSessionStore.subscribe((event) => {
1458
+ if (sessionId && event.session.id !== sessionId) {
1459
+ return;
1460
+ }
1461
+ if (statuses && !statuses.has(event.session.status)) {
1462
+ return;
1463
+ }
1464
+ res.write(formatSessionSseEvent(event));
1465
+ });
1466
+ req.on("close", () => {
1467
+ unsubscribe();
1468
+ res.end();
1469
+ });
1470
+ return;
1471
+ }
1472
+ if (pathname === INSPECTO_API_PATHS.SESSIONS && req.method === "GET") {
1473
+ const statusParam = url.searchParams.getAll("status");
1474
+ const sessions = annotationSessionStore.listSessions(
1475
+ statusParam.length ? {
1476
+ status: statusParam
1477
+ } : void 0
1478
+ );
1479
+ res.writeHead(200, { "Content-Type": "application/json" });
1480
+ res.end(JSON.stringify({ success: true, sessions }));
1481
+ return;
1482
+ }
1483
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && req.method === "GET") {
1484
+ const sessionId = pathname.substring(INSPECTO_API_PATHS.SESSIONS.length + 1);
1485
+ const session = annotationSessionStore.getSession(sessionId);
1486
+ if (!session) {
1487
+ res.writeHead(404, { "Content-Type": "application/json" });
1488
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1489
+ return;
1490
+ }
1491
+ res.writeHead(200, { "Content-Type": "application/json" });
1492
+ res.end(JSON.stringify({ success: true, session }));
1493
+ return;
1494
+ }
1495
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX) && req.method === "POST") {
1496
+ const sessionId = pathname.slice(
1497
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
1498
+ -INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX.length
1499
+ );
1500
+ try {
1501
+ const rawBody = await readBody(req);
1502
+ const body = JSON.parse(rawBody);
1503
+ if (!isAnnotationThreadRole(body.role)) {
1504
+ res.writeHead(400, { "Content-Type": "application/json" });
1505
+ res.end(JSON.stringify({ success: false, error: "Reply role is invalid." }));
1506
+ return;
1507
+ }
1508
+ if (!body.text?.trim()) {
1509
+ res.writeHead(400, { "Content-Type": "application/json" });
1510
+ res.end(JSON.stringify({ success: false, error: "Reply text is required." }));
1511
+ return;
1512
+ }
1513
+ const session = annotationSessionStore.appendMessage(sessionId, {
1514
+ role: body.role,
1515
+ text: body.text.trim()
1516
+ });
1517
+ if (!session) {
1518
+ res.writeHead(404, { "Content-Type": "application/json" });
1519
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1520
+ return;
1521
+ }
1522
+ res.writeHead(200, { "Content-Type": "application/json" });
1523
+ res.end(JSON.stringify({ success: true, session }));
1524
+ } catch (e) {
1525
+ serverLogger4.error(`Error parsing session reply request:`, e);
1526
+ res.writeHead(400, { "Content-Type": "application/json" });
1527
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1528
+ }
1529
+ return;
1530
+ }
1531
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX) && req.method === "POST") {
1532
+ const sessionId = pathname.slice(
1533
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
1534
+ -INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX.length
1535
+ );
1536
+ try {
1537
+ const rawBody = await readBody(req);
1538
+ const body = rawBody ? JSON.parse(rawBody) : {};
1539
+ const message = body.message?.trim();
1540
+ const existingSession = annotationSessionStore.getSession(sessionId);
1541
+ if (!existingSession) {
1542
+ res.writeHead(404, { "Content-Type": "application/json" });
1543
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1544
+ return;
1545
+ }
1546
+ if (!message && !hasAgentReply(existingSession)) {
1547
+ res.writeHead(400, { "Content-Type": "application/json" });
1548
+ res.end(
1549
+ JSON.stringify({
1550
+ success: false,
1551
+ error: "Resolve message is required until an agent reply is recorded."
1552
+ })
1553
+ );
1554
+ return;
1555
+ }
1556
+ if (message) {
1557
+ const repliedSession = annotationSessionStore.appendMessage(sessionId, {
1558
+ role: "agent",
1559
+ text: message
1560
+ });
1561
+ if (!repliedSession) {
1562
+ res.writeHead(404, { "Content-Type": "application/json" });
1563
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1564
+ return;
1565
+ }
1566
+ }
1567
+ const session = annotationSessionStore.updateStatus(sessionId, "resolved");
1568
+ if (!session) {
1569
+ res.writeHead(404, { "Content-Type": "application/json" });
1570
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1571
+ return;
1572
+ }
1573
+ res.writeHead(200, { "Content-Type": "application/json" });
1574
+ res.end(JSON.stringify({ success: true, session }));
1575
+ } catch (e) {
1576
+ serverLogger4.error(`Error parsing session resolve request:`, e);
1577
+ res.writeHead(400, { "Content-Type": "application/json" });
1578
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1579
+ }
1580
+ return;
1581
+ }
1582
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX) && req.method === "POST") {
1583
+ const sessionId = pathname.slice(
1584
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
1585
+ -INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX.length
1586
+ );
1587
+ try {
1588
+ const rawBody = await readBody(req);
1589
+ const body = rawBody ? JSON.parse(rawBody) : {};
1590
+ const message = body.message?.trim();
1591
+ const existingSession = annotationSessionStore.getSession(sessionId);
1592
+ if (!existingSession) {
1593
+ res.writeHead(404, { "Content-Type": "application/json" });
1594
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1595
+ return;
1596
+ }
1597
+ if (message) {
1598
+ const repliedSession = annotationSessionStore.appendMessage(sessionId, {
1599
+ role: "agent",
1600
+ text: message
1601
+ });
1602
+ if (!repliedSession) {
1603
+ res.writeHead(404, { "Content-Type": "application/json" });
1604
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1605
+ return;
1606
+ }
1607
+ }
1608
+ const session = annotationSessionStore.updateStatus(sessionId, "dismissed");
1609
+ if (!session) {
1610
+ res.writeHead(404, { "Content-Type": "application/json" });
1611
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1612
+ return;
1613
+ }
1614
+ res.writeHead(200, { "Content-Type": "application/json" });
1615
+ res.end(JSON.stringify({ success: true, session }));
1616
+ } catch (e) {
1617
+ serverLogger4.error(`Error parsing session dismiss request:`, e);
1618
+ res.writeHead(400, { "Content-Type": "application/json" });
1619
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1620
+ }
1621
+ return;
1622
+ }
1177
1623
  if (pathname.startsWith(`${INSPECTO_API_PATHS.AI_TICKET}/`) && req.method === "GET") {
1178
1624
  const ticketId = pathname.substring(INSPECTO_API_PATHS.AI_TICKET.length + 1);
1179
1625
  const payloadStr = readTicket(ticketId);
@@ -1190,7 +1636,7 @@ async function handleRequest(url, req, res) {
1190
1636
  res.end(JSON.stringify({ error: "not found" }));
1191
1637
  }
1192
1638
  async function dispatchToAi(req) {
1193
- const { location, snippet, prompt, screenshotContext } = req;
1639
+ const { location, snippet, prompt } = req;
1194
1640
  const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
1195
1641
 
1196
1642
  \`\`\`
@@ -1203,8 +1649,7 @@ ${snippet}
1203
1649
  filePath: location.file,
1204
1650
  line: location.line,
1205
1651
  column: location.column,
1206
- snippet,
1207
- ...screenshotContext ? { screenshotContext } : {}
1652
+ snippet
1208
1653
  });
1209
1654
  }
1210
1655
  function getBatchDispatchStatusCode(errorCode, success) {
@@ -1213,6 +1658,21 @@ function getBatchDispatchStatusCode(errorCode, success) {
1213
1658
  if (errorCode === "FORBIDDEN_PATH") return 403;
1214
1659
  return 500;
1215
1660
  }
1661
+ function isAnnotationThreadRole(value) {
1662
+ return value === "user" || value === "agent" || value === "system";
1663
+ }
1664
+ function formatSessionSseEvent(event) {
1665
+ return `event: ${event.type}
1666
+ data: ${JSON.stringify(event)}
1667
+
1668
+ `;
1669
+ }
1670
+ function normalizeSessionClaimTimeout(value) {
1671
+ if (!value?.trim()) return 3e4;
1672
+ const parsed = Number.parseInt(value, 10);
1673
+ if (!Number.isFinite(parsed)) return 3e4;
1674
+ return Math.max(0, Math.min(parsed, 3e5));
1675
+ }
1216
1676
 
1217
1677
  // src/index.ts
1218
1678
  import { createUnplugin } from "unplugin";
@@ -1903,7 +2363,9 @@ function transformRouter(options) {
1903
2363
  return transformSvelte({
1904
2364
  filePath,
1905
2365
  source,
2366
+ projectRoot,
1906
2367
  escapeTags: pluginOptions.escapeTags,
2368
+ pathType: pluginOptions.pathType,
1907
2369
  attributeName: pluginOptions.attributeName
1908
2370
  });
1909
2371
  }
@@ -1912,6 +2374,7 @@ function transformRouter(options) {
1912
2374
  filePath,
1913
2375
  source,
1914
2376
  escapeTags: pluginOptions.escapeTags,
2377
+ pathType: pluginOptions.pathType,
1915
2378
  attributeName: pluginOptions.attributeName
1916
2379
  });
1917
2380
  }
@@ -1936,26 +2399,28 @@ var resolveClientModule = () => {
1936
2399
  };
1937
2400
 
1938
2401
  // src/injectors/webpack.ts
1939
- function getWebpackHtmlScript(serverPort) {
2402
+ function getWebpackHtmlScript(serverPort, publicServerUrl) {
1940
2403
  return `
1941
2404
  window.__AI_INSPECTOR_PORT__ = ${serverPort};
1942
- window.addEventListener('load', () => {
1943
- if (window.InspectoClient) {
1944
- window.InspectoClient.mountInspector({
1945
- serverUrl: 'http://127.0.0.1:' + window.__AI_INSPECTOR_PORT__,
1946
- });
1947
- }
1948
- });
2405
+ window.__AI_INSPECTOR_SERVER_URL__ = '${publicServerUrl ?? `http://127.0.0.1:${serverPort}`}';
2406
+ window.addEventListener('load', () => {
2407
+ if (window.InspectoClient) {
2408
+ window.InspectoClient.mountInspector({
2409
+ serverUrl: window.__AI_INSPECTOR_SERVER_URL__,
2410
+ });
2411
+ }
2412
+ });
1949
2413
  `;
1950
2414
  }
1951
- function getWebpackAssetScript(serverPort) {
2415
+ function getWebpackAssetScript(serverPort, publicServerUrl) {
1952
2416
  return `
1953
2417
  if (typeof window !== 'undefined') {
1954
2418
  window.__AI_INSPECTOR_PORT__ = ${serverPort};
2419
+ window.__AI_INSPECTOR_SERVER_URL__ = '${publicServerUrl ?? `http://127.0.0.1:${serverPort}`}';
1955
2420
  const _initInspecto = () => {
1956
2421
  if (window.InspectoClient) {
1957
2422
  window.InspectoClient.mountInspector({
1958
- serverUrl: 'http://127.0.0.1:' + window.__AI_INSPECTOR_PORT__,
2423
+ serverUrl: window.__AI_INSPECTOR_SERVER_URL__,
1959
2424
  });
1960
2425
  } else {
1961
2426
  setTimeout(_initInspecto, 100);
@@ -1969,7 +2434,7 @@ if (typeof window !== 'undefined') {
1969
2434
  }
1970
2435
  `;
1971
2436
  }
1972
- function injectWebpack(compiler, serverPortFn, resolveClientModule2) {
2437
+ function injectWebpack(compiler, serverPortFn, publicServerUrlFn, resolveClientModule2) {
1973
2438
  const inspectoClientPath = resolveClientModule2();
1974
2439
  if (compiler.webpack && compiler.webpack.EntryPlugin) {
1975
2440
  new compiler.webpack.EntryPlugin(compiler.context, inspectoClientPath, {
@@ -1984,11 +2449,12 @@ function injectWebpack(compiler, serverPortFn, resolveClientModule2) {
1984
2449
  const hooks = HtmlWebpackPlugin.constructor.getHooks(compilation);
1985
2450
  hooks.alterAssetTagGroups.tapPromise("inspecto-overlay", async (data) => {
1986
2451
  const port = await serverPortFn();
2452
+ const publicServerUrl = publicServerUrlFn(port);
1987
2453
  data.headTags.unshift({
1988
2454
  tagName: "script",
1989
2455
  voidTag: false,
1990
2456
  meta: { plugin: "inspecto-overlay" },
1991
- innerHTML: getWebpackHtmlScript(port)
2457
+ innerHTML: getWebpackHtmlScript(port, publicServerUrl)
1992
2458
  });
1993
2459
  return data;
1994
2460
  });
@@ -2001,13 +2467,14 @@ function injectWebpack(compiler, serverPortFn, resolveClientModule2) {
2001
2467
  },
2002
2468
  async (assets) => {
2003
2469
  const port = await serverPortFn();
2470
+ const publicServerUrl = publicServerUrlFn(port);
2004
2471
  const mainAssetKey = Object.keys(assets).find(
2005
2472
  (key) => key.endsWith(".js") && (key.includes("main") || key.includes("app") || key.includes("umi"))
2006
2473
  );
2007
2474
  if (!mainAssetKey) return;
2008
2475
  const originalSource = assets[mainAssetKey].source();
2009
2476
  assets[mainAssetKey] = new compiler.webpack.sources.RawSource(
2010
- getWebpackAssetScript(port) + "\n" + originalSource
2477
+ getWebpackAssetScript(port, publicServerUrl) + "\n" + originalSource
2011
2478
  );
2012
2479
  }
2013
2480
  );
@@ -2041,12 +2508,13 @@ function injectRspack(compiler, serverPortFn, resolveClientModule2) {
2041
2508
  }
2042
2509
 
2043
2510
  // src/injectors/vite.ts
2044
- function getViteVirtualModuleScript(serverPort) {
2511
+ function getViteVirtualModuleScript(serverPort, publicServerUrl) {
2045
2512
  return `
2046
2513
  import { mountInspector } from '@inspecto-dev/core';
2047
2514
  window.__AI_INSPECTOR_PORT__ = ${serverPort};
2515
+ window.__AI_INSPECTOR_SERVER_URL__ = '${publicServerUrl ?? `http://127.0.0.1:${serverPort}`}';
2048
2516
  mountInspector({
2049
- serverUrl: 'http://127.0.0.1:' + window.__AI_INSPECTOR_PORT__,
2517
+ serverUrl: window.__AI_INSPECTOR_SERVER_URL__,
2050
2518
  });
2051
2519
  `;
2052
2520
  }
@@ -2079,6 +2547,11 @@ var InspectoPlugin = createUnplugin((userOptions = {}) => {
2079
2547
  }
2080
2548
  return serverPort;
2081
2549
  };
2550
+ const getPublicServerUrl = (port) => resolvePublicServerUrl({
2551
+ cwd: serverState.cwd || process.cwd(),
2552
+ configRoot: serverState.configRoot || projectRoot,
2553
+ port
2554
+ });
2082
2555
  return {
2083
2556
  name: "inspecto-overlay",
2084
2557
  enforce: "pre",
@@ -2091,7 +2564,7 @@ var InspectoPlugin = createUnplugin((userOptions = {}) => {
2091
2564
  },
2092
2565
  webpack: (compiler) => {
2093
2566
  if (isProduction) return;
2094
- injectWebpack(compiler, ensureServer, resolveClientModule);
2567
+ injectWebpack(compiler, ensureServer, getPublicServerUrl, resolveClientModule);
2095
2568
  },
2096
2569
  rspack: (compiler) => {
2097
2570
  if (isProduction) return;
@@ -2117,7 +2590,10 @@ var InspectoPlugin = createUnplugin((userOptions = {}) => {
2117
2590
  },
2118
2591
  load(id) {
2119
2592
  if (id === VITE_VIRTUAL_MODULE_ID) {
2120
- return getViteVirtualModuleScript(serverPort ?? DEFAULT_PORT);
2593
+ return getViteVirtualModuleScript(
2594
+ serverPort ?? DEFAULT_PORT,
2595
+ getPublicServerUrl(serverPort ?? DEFAULT_PORT)
2596
+ );
2121
2597
  }
2122
2598
  return null;
2123
2599
  },
@@ -2175,10 +2651,10 @@ var rollupPlugin = InspectoPlugin.rollup;
2175
2651
  var esbuildPlugin = InspectoPlugin.esbuild;
2176
2652
 
2177
2653
  // src/astro.ts
2178
- function getAstroInjectedScript(serverPort) {
2654
+ function getAstroInjectedScript(serverPort, publicServerUrl) {
2179
2655
  return `
2180
2656
  import { mountInspector } from '@inspecto-dev/core';
2181
- window.__AI_INSPECTOR_SERVER_URL__ = 'http://127.0.0.1:${serverPort}';
2657
+ window.__AI_INSPECTOR_SERVER_URL__ = '${publicServerUrl ?? `http://127.0.0.1:${serverPort}`}';
2182
2658
  mountInspector({
2183
2659
  serverUrl: window.__AI_INSPECTOR_SERVER_URL__,
2184
2660
  });
@@ -2198,7 +2674,17 @@ function astroIntegration(options) {
2198
2674
  return;
2199
2675
  }
2200
2676
  const serverPort = await startServer();
2201
- injectScript("page", getAstroInjectedScript(serverPort));
2677
+ injectScript(
2678
+ "page",
2679
+ getAstroInjectedScript(
2680
+ serverPort,
2681
+ resolvePublicServerUrl({
2682
+ cwd: serverState.cwd || process.cwd(),
2683
+ configRoot: serverState.configRoot || process.cwd(),
2684
+ port: serverPort
2685
+ })
2686
+ )
2687
+ );
2202
2688
  }
2203
2689
  }
2204
2690
  };