@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
@@ -5,7 +5,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
5
5
  throw Error('Dynamic require of "' + x + '" is not supported');
6
6
  });
7
7
 
8
- // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.10_typescript@5.9.3_yaml@2.8.3/node_modules/tsup/assets/esm_shims.js
8
+ // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.7.0_postcss@8.5.14_typescript@5.9.3_yaml@2.8.4/node_modules/tsup/assets/esm_shims.js
9
9
  import path from "path";
10
10
  import { fileURLToPath } from "url";
11
11
  var getFilename = () => fileURLToPath(import.meta.url);
@@ -513,7 +513,6 @@ function dispatchPromptThroughIde(runtime, payload) {
513
513
  line: payload.line,
514
514
  column: payload.column,
515
515
  snippet: payload.snippet,
516
- ...payload.screenshotContext ? { screenshotContext: payload.screenshotContext } : {},
517
516
  overrides: runtime.overrides,
518
517
  autoSend: runtime.autoSend
519
518
  });
@@ -647,6 +646,188 @@ function assertPathWithinIdeOpenScope(file, projectRoot) {
647
646
  }
648
647
  }
649
648
 
649
+ // src/server/session-store.ts
650
+ var DEFAULT_STATUS = "pending";
651
+ function createAnnotationSessionStore(options = {}) {
652
+ const sessions = /* @__PURE__ */ new Map();
653
+ const listeners = /* @__PURE__ */ new Set();
654
+ const now = options.now ?? (() => Date.now());
655
+ const createId = options.createId ?? createRandomId;
656
+ function findNewestMatchingSession(statuses) {
657
+ return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
658
+ }
659
+ function updateSessionStatus(id, status) {
660
+ const session = sessions.get(id);
661
+ if (!session) return null;
662
+ const timestamp = now();
663
+ session.status = status;
664
+ session.updatedAt = timestamp;
665
+ if (status === "acknowledged") {
666
+ session.acknowledgedAt = timestamp;
667
+ }
668
+ if (status === "resolved") {
669
+ session.resolvedAt = timestamp;
670
+ }
671
+ emit({ type: "session-status-updated", session });
672
+ return cloneSession(session);
673
+ }
674
+ function claimSession(id, statuses) {
675
+ const session = sessions.get(id);
676
+ if (!session || statuses && !statuses.has(session.status)) return null;
677
+ if (session.status === "acknowledged") return cloneSession(session);
678
+ return updateSessionStatus(id, "acknowledged");
679
+ }
680
+ function emit(event) {
681
+ const snapshot = cloneSession(event.session);
682
+ for (const listener of listeners) {
683
+ listener({ type: event.type, session: snapshot });
684
+ }
685
+ }
686
+ const store = {
687
+ createSession(input) {
688
+ const timestamp = now();
689
+ const session = {
690
+ id: createId(),
691
+ instruction: input.instruction?.trim() ?? "",
692
+ annotations: cloneArray(input.annotations),
693
+ ...input.deliveryMode ? { deliveryMode: input.deliveryMode } : {},
694
+ status: DEFAULT_STATUS,
695
+ messages: cloneArray(input.messages ?? []),
696
+ createdAt: timestamp,
697
+ updatedAt: timestamp,
698
+ ...input.runtimeContext ? { runtimeContext: cloneValue(input.runtimeContext) } : {},
699
+ ...input.cssContextPrompt?.trim() ? { cssContextPrompt: input.cssContextPrompt.trim() } : {},
700
+ ...input.pageUrl ? { pageUrl: input.pageUrl } : {},
701
+ ...input.route ? { route: input.route } : {}
702
+ };
703
+ sessions.set(session.id, session);
704
+ emit({ type: "session-created", session });
705
+ return cloneSession(session);
706
+ },
707
+ getSession(id) {
708
+ const session = sessions.get(id);
709
+ return session ? cloneSession(session) : null;
710
+ },
711
+ listSessions(options2 = {}) {
712
+ const statuses = normalizeStatuses(options2.status);
713
+ return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt).map((session) => cloneSession(session));
714
+ },
715
+ async claimNextSession(options2 = {}) {
716
+ const statuses = normalizeStatuses(DEFAULT_STATUS);
717
+ const existingSession = findNewestMatchingSession(statuses);
718
+ if (existingSession) {
719
+ return {
720
+ session: claimSession(existingSession.id, statuses),
721
+ timedOut: false,
722
+ matchedExisting: true
723
+ };
724
+ }
725
+ const timeoutMs = normalizeTimeoutMs(options2.timeoutMs);
726
+ if (timeoutMs === 0) {
727
+ return {
728
+ session: null,
729
+ timedOut: true,
730
+ matchedExisting: false
731
+ };
732
+ }
733
+ return await new Promise((resolve2) => {
734
+ let settled = false;
735
+ let timeout = null;
736
+ const finish = (result) => {
737
+ if (settled) return;
738
+ settled = true;
739
+ unsubscribe();
740
+ if (timeout) {
741
+ clearTimeout(timeout);
742
+ }
743
+ resolve2(result);
744
+ };
745
+ const unsubscribe = this.subscribe((event) => {
746
+ const session = claimSession(event.session.id, statuses);
747
+ if (!session) return;
748
+ finish({
749
+ session,
750
+ timedOut: false,
751
+ matchedExisting: false,
752
+ event: event.type
753
+ });
754
+ });
755
+ if (timeoutMs !== null) {
756
+ timeout = setTimeout(() => {
757
+ finish({
758
+ session: null,
759
+ timedOut: true,
760
+ matchedExisting: false
761
+ });
762
+ }, timeoutMs);
763
+ }
764
+ });
765
+ },
766
+ appendMessage(id, input) {
767
+ const session = sessions.get(id);
768
+ if (!session) return null;
769
+ const timestamp = now();
770
+ session.messages.push({
771
+ id: createId(),
772
+ role: input.role,
773
+ text: input.text,
774
+ createdAt: timestamp
775
+ });
776
+ session.updatedAt = timestamp;
777
+ if (input.role === "agent" && isPendingLikeStatus(session.status)) {
778
+ session.status = "in_progress";
779
+ }
780
+ emit({ type: "session-message-appended", session });
781
+ return cloneSession(session);
782
+ },
783
+ updateStatus(id, status) {
784
+ return updateSessionStatus(id, status);
785
+ },
786
+ subscribe(listener) {
787
+ listeners.add(listener);
788
+ return () => {
789
+ listeners.delete(listener);
790
+ };
791
+ },
792
+ clear() {
793
+ sessions.clear();
794
+ listeners.clear();
795
+ }
796
+ };
797
+ return store;
798
+ }
799
+ var annotationSessionStore = createAnnotationSessionStore();
800
+ function normalizeStatuses(status) {
801
+ if (!status) return null;
802
+ return new Set(Array.isArray(status) ? status : [status]);
803
+ }
804
+ function normalizeTimeoutMs(value) {
805
+ if (value === void 0) return null;
806
+ if (!Number.isFinite(value)) return 0;
807
+ return Math.max(0, Math.floor(value));
808
+ }
809
+ function isPendingLikeStatus(status) {
810
+ return status === "pending" || status === "acknowledged";
811
+ }
812
+ function hasAgentReply(session) {
813
+ return session.messages.some((message) => message.role === "agent" && Boolean(message.text?.trim()));
814
+ }
815
+ function createRandomId() {
816
+ return `annotation-session-${Math.random().toString(36).slice(2, 10)}`;
817
+ }
818
+ function cloneSession(session) {
819
+ return cloneValue(session);
820
+ }
821
+ function cloneArray(value) {
822
+ return cloneValue(value);
823
+ }
824
+ function cloneValue(value) {
825
+ if (typeof structuredClone === "function") {
826
+ return structuredClone(value);
827
+ }
828
+ return JSON.parse(JSON.stringify(value));
829
+ }
830
+
650
831
  // src/server/annotation-dispatch.ts
651
832
  var AnnotationDispatchError = class extends Error {
652
833
  constructor(message, errorCode) {
@@ -655,20 +836,30 @@ var AnnotationDispatchError = class extends Error {
655
836
  this.errorCode = errorCode;
656
837
  }
657
838
  };
658
- async function dispatchAnnotationsToAi(req, state) {
839
+ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStore) {
659
840
  try {
660
841
  validateAnnotationDispatchRequest(req, state);
661
842
  const batch = normalizeAnnotationBatch(req);
662
843
  const prompt = buildAnnotationBatchPrompt(batch);
844
+ const deliveryMode = normalizeDeliveryMode(req.deliveryMode);
845
+ const session = store.createSession({
846
+ instruction: batch.instruction,
847
+ annotations: toSessionAnnotations(batch.annotations),
848
+ deliveryMode,
849
+ ...batch.runtimeContext ? { runtimeContext: batch.runtimeContext } : {},
850
+ ...batch.cssContextPrompt ? { cssContextPrompt: batch.cssContextPrompt } : {}
851
+ });
663
852
  const representativeTarget = batch.annotations[0]?.targets[0];
664
- const runtime = resolvePromptDispatchRuntime(state);
665
- return dispatchPromptThroughIde(runtime, {
853
+ const dispatchResult = deliveryMode === "ide" ? dispatchPromptThroughIde(resolvePromptDispatchRuntime(state), {
666
854
  prompt,
667
855
  ...representativeTarget?.file ? { filePath: representativeTarget.file } : {},
668
856
  ...representativeTarget?.line ? { line: representativeTarget.line } : {},
669
- ...representativeTarget?.column ? { column: representativeTarget.column } : {},
670
- ...batch.screenshotContext ? { screenshotContext: batch.screenshotContext } : {}
671
- });
857
+ ...representativeTarget?.column ? { column: representativeTarget.column } : {}
858
+ }) : { success: true };
859
+ return {
860
+ ...dispatchResult,
861
+ session: toSessionSummary(session)
862
+ };
672
863
  } catch (error) {
673
864
  return {
674
865
  success: false,
@@ -677,6 +868,41 @@ async function dispatchAnnotationsToAi(req, state) {
677
868
  };
678
869
  }
679
870
  }
871
+ function normalizeDeliveryMode(input) {
872
+ return input === "agent" ? "agent" : "ide";
873
+ }
874
+ function toSessionAnnotations(annotations) {
875
+ return annotations.map((annotation) => ({
876
+ id: `annotation-${annotation.index}`,
877
+ note: annotation.note,
878
+ intent: annotation.intent,
879
+ targets: annotation.targets.map((target, targetIndex) => ({
880
+ id: `annotation-${annotation.index}-target-${targetIndex + 1}`,
881
+ label: target.label ?? "Unknown target",
882
+ location: {
883
+ file: target.file,
884
+ line: target.line,
885
+ column: target.column
886
+ },
887
+ ...target.selector ? { selector: target.selector } : {},
888
+ ...target.snippet ? { snippet: target.snippet } : {},
889
+ rect: {
890
+ x: 0,
891
+ y: 0,
892
+ width: 0,
893
+ height: 0
894
+ }
895
+ }))
896
+ }));
897
+ }
898
+ function toSessionSummary(session) {
899
+ return {
900
+ id: session.id,
901
+ status: session.status,
902
+ createdAt: session.createdAt,
903
+ updatedAt: session.updatedAt
904
+ };
905
+ }
680
906
  function validateAnnotationDispatchRequest(req, state) {
681
907
  if (!req.annotations.length) {
682
908
  throw new AnnotationDispatchError("At least one annotation is required.", "INVALID_REQUEST");
@@ -697,9 +923,7 @@ function validateAnnotationDispatchRequest(req, state) {
697
923
  function normalizeAnnotationBatch(req) {
698
924
  return {
699
925
  instruction: req.instruction?.trim() ?? "",
700
- responseMode: req.responseMode ?? "unified",
701
926
  ...req.runtimeContext ? { runtimeContext: req.runtimeContext } : {},
702
- ...req.screenshotContext ? { screenshotContext: req.screenshotContext } : {},
703
927
  ...req.cssContextPrompt?.trim() ? { cssContextPrompt: req.cssContextPrompt.trim() } : {},
704
928
  annotations: req.annotations.map((annotation, index) => ({
705
929
  index: index + 1,
@@ -721,12 +945,9 @@ function buildAnnotationBatchPrompt(batch) {
721
945
  const prompt = batch.instruction ? `${batch.instruction}
722
946
 
723
947
  ${body}` : body;
724
- return appendScreenshotContextSection(
725
- appendCssContextSection(
726
- appendRuntimeContextSection(prompt, batch.runtimeContext),
727
- batch.cssContextPrompt
728
- ),
729
- batch.screenshotContext
948
+ return appendCssContextSection(
949
+ appendRuntimeContextSection(prompt, batch.runtimeContext),
950
+ batch.cssContextPrompt
730
951
  );
731
952
  }
732
953
  function appendCssContextSection(prompt, cssContextPrompt) {
@@ -753,20 +974,6 @@ function buildSelectedElementsPrompt(annotations) {
753
974
  }
754
975
  return lines.join("\n");
755
976
  }
756
- function appendScreenshotContextSection(prompt, screenshotContext) {
757
- if (!screenshotContext || !screenshotContext.imageDataUrl && !screenshotContext.imageAssetId) {
758
- return prompt;
759
- }
760
- const lines = [
761
- "Visual screenshot context attached:",
762
- `- capturedAt=${screenshotContext.capturedAt}`,
763
- `- mimeType=${screenshotContext.mimeType}`,
764
- ...screenshotContext.imageAssetId ? [`- imageAssetId=${screenshotContext.imageAssetId}`] : []
765
- ];
766
- return `${prompt}
767
-
768
- ${lines.join("\n")}`;
769
- }
770
977
  function appendRuntimeContextSection(prompt, runtimeContext) {
771
978
  if (!runtimeContext?.records.length) {
772
979
  return prompt;
@@ -814,7 +1021,7 @@ async function buildClientConfig(serverState2) {
814
1021
  ...info,
815
1022
  prompts: resolveIntents(promptsConfig),
816
1023
  hotKeys: userConfig["inspector.hotKey"] ?? "alt",
817
- theme: userConfig["inspector.theme"] ?? "auto",
1024
+ annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "both",
818
1025
  includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
819
1026
  runtimeContext: {
820
1027
  enabled: true,
@@ -822,10 +1029,6 @@ async function buildClientConfig(serverState2) {
822
1029
  maxRuntimeErrors: 3,
823
1030
  maxFailedRequests: 2
824
1031
  },
825
- screenshotContext: {
826
- enabled: false
827
- },
828
- annotationResponseMode: userConfig["prompt.annotationResponseMode"] ?? "unified",
829
1032
  autoSend: userConfig["prompt.autoSend"] ?? false
830
1033
  };
831
1034
  }
@@ -870,7 +1073,7 @@ function handleOpenFileRequest(body, serverState2) {
870
1073
  else if (rawEditorHint === "vscodium") editorHint = "codium";
871
1074
  else if (rawEditorHint === "trae-cn" || rawEditorHint === "trae") editorHint = "trae";
872
1075
  serverLogger2.debug(
873
- `IDE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
1076
+ `SOURCE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
874
1077
  );
875
1078
  if (VSCODE_FAMILY_SCHEMES.includes(rawEditorHint)) {
876
1079
  let normalizedPath = absolutePath.replace(/\\/g, "/");
@@ -879,7 +1082,7 @@ function handleOpenFileRequest(body, serverState2) {
879
1082
  }
880
1083
  const encodedPath = encodeURI(normalizedPath);
881
1084
  const uri = `${rawEditorHint}://file${encodedPath}:${body.line}:${body.column}`;
882
- serverLogger2.debug(`IDE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
1085
+ serverLogger2.debug(`SOURCE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
883
1086
  try {
884
1087
  if (process.platform === "darwin") {
885
1088
  execFileSync2("open", [uri]);
@@ -889,7 +1092,7 @@ function handleOpenFileRequest(body, serverState2) {
889
1092
  execFileSync2("xdg-open", [uri]);
890
1093
  }
891
1094
  } catch (e) {
892
- serverLogger2.error(`Failed to launch URI for IDE_OPEN (${uri}):`, e);
1095
+ serverLogger2.error(`Failed to launch URI for SOURCE_OPEN (${uri}):`, e);
893
1096
  launchIDE2({
894
1097
  file: absolutePath,
895
1098
  line: body.line,
@@ -944,8 +1147,18 @@ function resolveProjectRoot() {
944
1147
  return gitRoot;
945
1148
  }
946
1149
 
1150
+ // src/server/server-url.ts
1151
+ function resolveServerHost(cwd, configRoot) {
1152
+ const userConfig = loadUserConfigSync(false, cwd, configRoot);
1153
+ const configuredHost = userConfig["server.host"]?.trim();
1154
+ if (configuredHost) return configuredHost;
1155
+ if (process.env["VITEST"]) return "127.0.0.1";
1156
+ return "127.0.0.1";
1157
+ }
1158
+
947
1159
  // src/server/index.ts
948
1160
  var serverLogger4 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
1161
+ var PORT_FILE_NAME = "inspecto.port.json";
949
1162
  var serverState = {
950
1163
  port: null,
951
1164
  running: false,
@@ -954,6 +1167,42 @@ var serverState = {
954
1167
  cwd: process.cwd()
955
1168
  };
956
1169
  var serverInstance = null;
1170
+ function getPortFilePath() {
1171
+ return path6.join(os2.tmpdir(), PORT_FILE_NAME);
1172
+ }
1173
+ function getProjectRootHash() {
1174
+ if (!serverState.projectRoot) return null;
1175
+ return crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1176
+ }
1177
+ function readPortData(portFile) {
1178
+ if (!fs5.existsSync(portFile)) return {};
1179
+ try {
1180
+ return JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1181
+ } catch {
1182
+ return {};
1183
+ }
1184
+ }
1185
+ function writeProjectPort(port) {
1186
+ const rootHash = getProjectRootHash();
1187
+ if (!rootHash) return;
1188
+ const portFile = getPortFilePath();
1189
+ const portData = readPortData(portFile);
1190
+ portData[rootHash] = port;
1191
+ fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1192
+ }
1193
+ function removeProjectPort() {
1194
+ const rootHash = getProjectRootHash();
1195
+ if (!rootHash) return;
1196
+ const portFile = getPortFilePath();
1197
+ if (!fs5.existsSync(portFile)) return;
1198
+ const portData = readPortData(portFile);
1199
+ delete portData[rootHash];
1200
+ if (Object.keys(portData).length === 0) {
1201
+ fs5.unlinkSync(portFile);
1202
+ } else {
1203
+ fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1204
+ }
1205
+ }
957
1206
  async function startServer() {
958
1207
  if (serverState.running && serverState.port !== null) {
959
1208
  return serverState.port;
@@ -961,6 +1210,7 @@ async function startServer() {
961
1210
  serverState.projectRoot = resolveProjectRoot();
962
1211
  serverState.configRoot = serverState.projectRoot;
963
1212
  serverState.cwd = process.cwd();
1213
+ const serverHost = resolveServerHost(serverState.cwd, serverState.configRoot);
964
1214
  portfinder.basePort = 5678;
965
1215
  const port = await portfinder.getPortPromise();
966
1216
  watchConfig(
@@ -987,7 +1237,7 @@ async function startServer() {
987
1237
  });
988
1238
  });
989
1239
  await new Promise((resolve2, reject) => {
990
- serverInstance.listen(port, "127.0.0.1", () => {
1240
+ serverInstance.listen(port, serverHost, () => {
991
1241
  serverInstance.unref();
992
1242
  resolve2();
993
1243
  });
@@ -998,37 +1248,18 @@ async function startServer() {
998
1248
  });
999
1249
  serverState.port = port;
1000
1250
  serverState.running = true;
1001
- const portFile = path6.join(os2.tmpdir(), "inspecto.port.json");
1002
1251
  try {
1003
- let portData = {};
1004
- if (fs5.existsSync(portFile)) {
1005
- try {
1006
- portData = JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1007
- } catch (_e) {
1008
- }
1009
- }
1010
- const rootHash = crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1011
- portData[rootHash] = port;
1012
- fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1252
+ writeProjectPort(port);
1013
1253
  } catch (_e) {
1014
1254
  serverLogger4.warn("Failed to write port file:", _e);
1015
1255
  }
1016
1256
  process.once("exit", () => {
1017
1257
  try {
1018
- if (fs5.existsSync(portFile)) {
1019
- const portData = JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1020
- const rootHash = crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1021
- delete portData[rootHash];
1022
- if (Object.keys(portData).length === 0) {
1023
- fs5.unlinkSync(portFile);
1024
- } else {
1025
- fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1026
- }
1027
- }
1258
+ removeProjectPort();
1028
1259
  } catch {
1029
1260
  }
1030
1261
  });
1031
- serverLogger4.info(`server running at http://127.0.0.1:${port}`);
1262
+ serverLogger4.info(`server running at http://${serverHost}:${port}`);
1032
1263
  return port;
1033
1264
  }
1034
1265
  async function readBody(req) {
@@ -1080,7 +1311,7 @@ async function handleRequest(url, req, res) {
1080
1311
  }
1081
1312
  return;
1082
1313
  }
1083
- if (pathname === INSPECTO_API_PATHS.IDE_OPEN && req.method === "POST") {
1314
+ if ((pathname === INSPECTO_API_PATHS.SOURCE_OPEN || pathname === INSPECTO_API_PATHS.IDE_OPEN) && req.method === "POST") {
1084
1315
  let body;
1085
1316
  try {
1086
1317
  body = JSON.parse(await readBody(req));
@@ -1093,7 +1324,7 @@ async function handleRequest(url, req, res) {
1093
1324
  handleOpenFileRequest(body, serverState);
1094
1325
  } catch (err) {
1095
1326
  serverLogger4.warn(
1096
- `Security: Blocked path traversal attempt in IDE_OPEN: ${body.file}. Reason: ${err.message}`
1327
+ `Security: Blocked path traversal attempt in SOURCE_OPEN: ${body.file}. Reason: ${err.message}`
1097
1328
  );
1098
1329
  res.writeHead(403, { "Content-Type": "application/json" });
1099
1330
  res.end(JSON.stringify({ error: "Access denied: File is outside of project workspace" }));
@@ -1167,6 +1398,212 @@ async function handleRequest(url, req, res) {
1167
1398
  }
1168
1399
  return;
1169
1400
  }
1401
+ if (pathname === INSPECTO_API_PATHS.SESSION_CLAIM_NEXT && req.method === "POST") {
1402
+ try {
1403
+ const rawBody = await readBody(req);
1404
+ const body = rawBody ? JSON.parse(rawBody) : {};
1405
+ const timeoutMs = normalizeSessionClaimTimeout(
1406
+ body.timeoutMs === void 0 ? null : String(body.timeoutMs)
1407
+ );
1408
+ const result = await annotationSessionStore.claimNextSession({
1409
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
1410
+ });
1411
+ res.writeHead(200, { "Content-Type": "application/json" });
1412
+ res.end(
1413
+ JSON.stringify({
1414
+ success: true,
1415
+ timedOut: result.timedOut,
1416
+ matchedExisting: result.matchedExisting,
1417
+ ...result.event ? { event: result.event } : {},
1418
+ ...result.session ? { session: result.session } : {}
1419
+ })
1420
+ );
1421
+ } catch (e) {
1422
+ serverLogger4.error(`Error parsing session claim request:`, e);
1423
+ res.writeHead(400, { "Content-Type": "application/json" });
1424
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1425
+ }
1426
+ return;
1427
+ }
1428
+ if (pathname === INSPECTO_API_PATHS.SESSION_EVENTS && req.method === "GET") {
1429
+ const statusParam = url.searchParams.getAll("status");
1430
+ const statuses = statusParam.length ? new Set(statusParam) : null;
1431
+ const sessionId = url.searchParams.get("sessionId")?.trim() || null;
1432
+ res.writeHead(200, {
1433
+ "Content-Type": "text/event-stream",
1434
+ "Cache-Control": "no-cache",
1435
+ Connection: "keep-alive"
1436
+ });
1437
+ res.write(`event: ready
1438
+ data: ${JSON.stringify({ ok: true })}
1439
+
1440
+ `);
1441
+ const unsubscribe = annotationSessionStore.subscribe((event) => {
1442
+ if (sessionId && event.session.id !== sessionId) {
1443
+ return;
1444
+ }
1445
+ if (statuses && !statuses.has(event.session.status)) {
1446
+ return;
1447
+ }
1448
+ res.write(formatSessionSseEvent(event));
1449
+ });
1450
+ req.on("close", () => {
1451
+ unsubscribe();
1452
+ res.end();
1453
+ });
1454
+ return;
1455
+ }
1456
+ if (pathname === INSPECTO_API_PATHS.SESSIONS && req.method === "GET") {
1457
+ const statusParam = url.searchParams.getAll("status");
1458
+ const sessions = annotationSessionStore.listSessions(
1459
+ statusParam.length ? {
1460
+ status: statusParam
1461
+ } : void 0
1462
+ );
1463
+ res.writeHead(200, { "Content-Type": "application/json" });
1464
+ res.end(JSON.stringify({ success: true, sessions }));
1465
+ return;
1466
+ }
1467
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && req.method === "GET") {
1468
+ const sessionId = pathname.substring(INSPECTO_API_PATHS.SESSIONS.length + 1);
1469
+ const session = annotationSessionStore.getSession(sessionId);
1470
+ if (!session) {
1471
+ res.writeHead(404, { "Content-Type": "application/json" });
1472
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1473
+ return;
1474
+ }
1475
+ res.writeHead(200, { "Content-Type": "application/json" });
1476
+ res.end(JSON.stringify({ success: true, session }));
1477
+ return;
1478
+ }
1479
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX) && req.method === "POST") {
1480
+ const sessionId = pathname.slice(
1481
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
1482
+ -INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX.length
1483
+ );
1484
+ try {
1485
+ const rawBody = await readBody(req);
1486
+ const body = JSON.parse(rawBody);
1487
+ if (!isAnnotationThreadRole(body.role)) {
1488
+ res.writeHead(400, { "Content-Type": "application/json" });
1489
+ res.end(JSON.stringify({ success: false, error: "Reply role is invalid." }));
1490
+ return;
1491
+ }
1492
+ if (!body.text?.trim()) {
1493
+ res.writeHead(400, { "Content-Type": "application/json" });
1494
+ res.end(JSON.stringify({ success: false, error: "Reply text is required." }));
1495
+ return;
1496
+ }
1497
+ const session = annotationSessionStore.appendMessage(sessionId, {
1498
+ role: body.role,
1499
+ text: body.text.trim()
1500
+ });
1501
+ if (!session) {
1502
+ res.writeHead(404, { "Content-Type": "application/json" });
1503
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1504
+ return;
1505
+ }
1506
+ res.writeHead(200, { "Content-Type": "application/json" });
1507
+ res.end(JSON.stringify({ success: true, session }));
1508
+ } catch (e) {
1509
+ serverLogger4.error(`Error parsing session reply request:`, e);
1510
+ res.writeHead(400, { "Content-Type": "application/json" });
1511
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1512
+ }
1513
+ return;
1514
+ }
1515
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX) && req.method === "POST") {
1516
+ const sessionId = pathname.slice(
1517
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
1518
+ -INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX.length
1519
+ );
1520
+ try {
1521
+ const rawBody = await readBody(req);
1522
+ const body = rawBody ? JSON.parse(rawBody) : {};
1523
+ const message = body.message?.trim();
1524
+ const existingSession = annotationSessionStore.getSession(sessionId);
1525
+ if (!existingSession) {
1526
+ res.writeHead(404, { "Content-Type": "application/json" });
1527
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1528
+ return;
1529
+ }
1530
+ if (!message && !hasAgentReply(existingSession)) {
1531
+ res.writeHead(400, { "Content-Type": "application/json" });
1532
+ res.end(
1533
+ JSON.stringify({
1534
+ success: false,
1535
+ error: "Resolve message is required until an agent reply is recorded."
1536
+ })
1537
+ );
1538
+ return;
1539
+ }
1540
+ if (message) {
1541
+ const repliedSession = annotationSessionStore.appendMessage(sessionId, {
1542
+ role: "agent",
1543
+ text: message
1544
+ });
1545
+ if (!repliedSession) {
1546
+ res.writeHead(404, { "Content-Type": "application/json" });
1547
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1548
+ return;
1549
+ }
1550
+ }
1551
+ const session = annotationSessionStore.updateStatus(sessionId, "resolved");
1552
+ if (!session) {
1553
+ res.writeHead(404, { "Content-Type": "application/json" });
1554
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1555
+ return;
1556
+ }
1557
+ res.writeHead(200, { "Content-Type": "application/json" });
1558
+ res.end(JSON.stringify({ success: true, session }));
1559
+ } catch (e) {
1560
+ serverLogger4.error(`Error parsing session resolve request:`, e);
1561
+ res.writeHead(400, { "Content-Type": "application/json" });
1562
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1563
+ }
1564
+ return;
1565
+ }
1566
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX) && req.method === "POST") {
1567
+ const sessionId = pathname.slice(
1568
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
1569
+ -INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX.length
1570
+ );
1571
+ try {
1572
+ const rawBody = await readBody(req);
1573
+ const body = rawBody ? JSON.parse(rawBody) : {};
1574
+ const message = body.message?.trim();
1575
+ const existingSession = annotationSessionStore.getSession(sessionId);
1576
+ if (!existingSession) {
1577
+ res.writeHead(404, { "Content-Type": "application/json" });
1578
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1579
+ return;
1580
+ }
1581
+ if (message) {
1582
+ const repliedSession = annotationSessionStore.appendMessage(sessionId, {
1583
+ role: "agent",
1584
+ text: message
1585
+ });
1586
+ if (!repliedSession) {
1587
+ res.writeHead(404, { "Content-Type": "application/json" });
1588
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1589
+ return;
1590
+ }
1591
+ }
1592
+ const session = annotationSessionStore.updateStatus(sessionId, "dismissed");
1593
+ if (!session) {
1594
+ res.writeHead(404, { "Content-Type": "application/json" });
1595
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1596
+ return;
1597
+ }
1598
+ res.writeHead(200, { "Content-Type": "application/json" });
1599
+ res.end(JSON.stringify({ success: true, session }));
1600
+ } catch (e) {
1601
+ serverLogger4.error(`Error parsing session dismiss request:`, e);
1602
+ res.writeHead(400, { "Content-Type": "application/json" });
1603
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1604
+ }
1605
+ return;
1606
+ }
1170
1607
  if (pathname.startsWith(`${INSPECTO_API_PATHS.AI_TICKET}/`) && req.method === "GET") {
1171
1608
  const ticketId = pathname.substring(INSPECTO_API_PATHS.AI_TICKET.length + 1);
1172
1609
  const payloadStr = readTicket(ticketId);
@@ -1183,7 +1620,7 @@ async function handleRequest(url, req, res) {
1183
1620
  res.end(JSON.stringify({ error: "not found" }));
1184
1621
  }
1185
1622
  async function dispatchToAi(req) {
1186
- const { location, snippet, prompt, screenshotContext } = req;
1623
+ const { location, snippet, prompt } = req;
1187
1624
  const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
1188
1625
 
1189
1626
  \`\`\`
@@ -1196,8 +1633,7 @@ ${snippet}
1196
1633
  filePath: location.file,
1197
1634
  line: location.line,
1198
1635
  column: location.column,
1199
- snippet,
1200
- ...screenshotContext ? { screenshotContext } : {}
1636
+ snippet
1201
1637
  });
1202
1638
  }
1203
1639
  function getBatchDispatchStatusCode(errorCode, success) {
@@ -1206,6 +1642,21 @@ function getBatchDispatchStatusCode(errorCode, success) {
1206
1642
  if (errorCode === "FORBIDDEN_PATH") return 403;
1207
1643
  return 500;
1208
1644
  }
1645
+ function isAnnotationThreadRole(value) {
1646
+ return value === "user" || value === "agent" || value === "system";
1647
+ }
1648
+ function formatSessionSseEvent(event) {
1649
+ return `event: ${event.type}
1650
+ data: ${JSON.stringify(event)}
1651
+
1652
+ `;
1653
+ }
1654
+ function normalizeSessionClaimTimeout(value) {
1655
+ if (!value?.trim()) return 3e4;
1656
+ const parsed = Number.parseInt(value, 10);
1657
+ if (!Number.isFinite(parsed)) return 3e4;
1658
+ return Math.max(0, Math.min(parsed, 3e5));
1659
+ }
1209
1660
 
1210
1661
  // src/injectors/utils.ts
1211
1662
  import { createRequire } from "module";
@@ -1225,16 +1676,17 @@ var resolveClientModule = () => {
1225
1676
  };
1226
1677
 
1227
1678
  // src/injectors/webpack.ts
1228
- function getWebpackHtmlScript(serverPort2) {
1679
+ function getWebpackHtmlScript(serverPort2, publicServerUrl) {
1229
1680
  return `
1230
1681
  window.__AI_INSPECTOR_PORT__ = ${serverPort2};
1231
- window.addEventListener('load', () => {
1232
- if (window.InspectoClient) {
1233
- window.InspectoClient.mountInspector({
1234
- serverUrl: 'http://127.0.0.1:' + window.__AI_INSPECTOR_PORT__,
1235
- });
1236
- }
1237
- });
1682
+ window.__AI_INSPECTOR_SERVER_URL__ = '${publicServerUrl ?? `http://127.0.0.1:${serverPort2}`}';
1683
+ window.addEventListener('load', () => {
1684
+ if (window.InspectoClient) {
1685
+ window.InspectoClient.mountInspector({
1686
+ serverUrl: window.__AI_INSPECTOR_SERVER_URL__,
1687
+ });
1688
+ }
1689
+ });
1238
1690
  `;
1239
1691
  }
1240
1692