@inspecto-dev/plugin 0.3.7 → 0.3.9

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 (54) hide show
  1. package/README.md +2 -1
  2. package/dist/astro.cjs +2687 -0
  3. package/dist/astro.cjs.map +1 -0
  4. package/dist/astro.d.cts +17 -0
  5. package/dist/astro.d.ts +17 -0
  6. package/dist/astro.js +2656 -0
  7. package/dist/astro.js.map +1 -0
  8. package/dist/index.cjs +932 -87
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +22 -1
  11. package/dist/index.d.ts +22 -1
  12. package/dist/index.js +930 -87
  13. package/dist/index.js.map +1 -1
  14. package/dist/legacy/rspack/index.cjs +530 -89
  15. package/dist/legacy/rspack/index.cjs.map +1 -1
  16. package/dist/legacy/rspack/index.js +530 -89
  17. package/dist/legacy/rspack/index.js.map +1 -1
  18. package/dist/legacy/rspack/loader.cjs +407 -3
  19. package/dist/legacy/rspack/loader.cjs.map +1 -1
  20. package/dist/legacy/rspack/loader.js +403 -3
  21. package/dist/legacy/rspack/loader.js.map +1 -1
  22. package/dist/legacy/webpack4/index.cjs +519 -78
  23. package/dist/legacy/webpack4/index.cjs.map +1 -1
  24. package/dist/legacy/webpack4/index.js +519 -78
  25. package/dist/legacy/webpack4/index.js.map +1 -1
  26. package/dist/legacy/webpack4/loader.cjs +407 -3
  27. package/dist/legacy/webpack4/loader.cjs.map +1 -1
  28. package/dist/legacy/webpack4/loader.js +403 -3
  29. package/dist/legacy/webpack4/loader.js.map +1 -1
  30. package/dist/rollup.cjs +932 -87
  31. package/dist/rollup.cjs.map +1 -1
  32. package/dist/rollup.d.cts +1 -1
  33. package/dist/rollup.d.ts +1 -1
  34. package/dist/rollup.js +930 -87
  35. package/dist/rollup.js.map +1 -1
  36. package/dist/rspack.cjs +932 -87
  37. package/dist/rspack.cjs.map +1 -1
  38. package/dist/rspack.d.cts +1 -1
  39. package/dist/rspack.d.ts +1 -1
  40. package/dist/rspack.js +930 -87
  41. package/dist/rspack.js.map +1 -1
  42. package/dist/vite.cjs +932 -87
  43. package/dist/vite.cjs.map +1 -1
  44. package/dist/vite.d.cts +1 -1
  45. package/dist/vite.d.ts +1 -1
  46. package/dist/vite.js +930 -87
  47. package/dist/vite.js.map +1 -1
  48. package/dist/webpack.cjs +932 -87
  49. package/dist/webpack.cjs.map +1 -1
  50. package/dist/webpack.d.cts +1 -1
  51. package/dist/webpack.d.ts +1 -1
  52. package/dist/webpack.js +930 -87
  53. package/dist/webpack.js.map +1 -1
  54. package/package.json +13 -10
@@ -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.9_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.6.1_postcss@8.5.10_typescript@5.9.3_yaml@2.8.3/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);
@@ -293,7 +293,7 @@ function readJsonSafely(filePath) {
293
293
  }
294
294
  return null;
295
295
  }
296
- function resolveTargetTool(config, ide = "vscode") {
296
+ function resolveTargetTool(config, _ide = "vscode") {
297
297
  const defaultProvider = config["provider.default"];
298
298
  if (defaultProvider) {
299
299
  const tool = defaultProvider.split(".")[0];
@@ -445,7 +445,7 @@ function watchConfig(onReload, cwd = process.cwd(), gitRoot) {
445
445
  });
446
446
  watcher.unref();
447
447
  watchers.push(watcher);
448
- } catch (e) {
448
+ } catch (_e) {
449
449
  }
450
450
  }
451
451
  }
@@ -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;
@@ -799,22 +1006,22 @@ function getAnnotationDispatchErrorCode(error) {
799
1006
  }
800
1007
 
801
1008
  // src/server/client-config.ts
802
- async function buildClientConfig(serverState3) {
803
- const userConfig = loadUserConfigSync(false, serverState3.cwd, serverState3.configRoot);
804
- const promptsConfig = await loadPromptsConfig(false, serverState3.cwd, serverState3.configRoot);
1009
+ async function buildClientConfig(serverState2) {
1010
+ const userConfig = loadUserConfigSync(false, serverState2.cwd, serverState2.configRoot);
1011
+ const promptsConfig = await loadPromptsConfig(false, serverState2.cwd, serverState2.configRoot);
805
1012
  const effectiveIde = userConfig.ide ?? "vscode";
806
1013
  let info;
807
- if (!serverState3.ideInfo) {
1014
+ if (!serverState2.ideInfo) {
808
1015
  info = { ide: effectiveIde };
809
1016
  } else {
810
- const { scheme: _scheme, ...rest } = serverState3.ideInfo;
1017
+ const { scheme: _scheme, ...rest } = serverState2.ideInfo;
811
1018
  info = rest;
812
1019
  }
813
1020
  return {
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(serverState3) {
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
  }
@@ -850,13 +1053,13 @@ var VSCODE_FAMILY_SCHEMES = [
850
1053
  function normalizeIdeToken2(value) {
851
1054
  return (value ?? "").toLowerCase().replace(/[^a-z0-9]/g, "");
852
1055
  }
853
- function handleOpenFileRequest(body, serverState3) {
854
- const absolutePath = resolveWorkspacePath(body.file, serverState3.cwd);
855
- assertPathWithinIdeOpenScope(absolutePath, serverState3.projectRoot);
856
- const userConfig = loadUserConfigSync(false, serverState3.cwd, serverState3.configRoot);
1056
+ function handleOpenFileRequest(body, serverState2) {
1057
+ const absolutePath = resolveWorkspacePath(body.file, serverState2.cwd);
1058
+ assertPathWithinIdeOpenScope(absolutePath, serverState2.projectRoot);
1059
+ const userConfig = loadUserConfigSync(false, serverState2.cwd, serverState2.configRoot);
857
1060
  const configuredIde = userConfig.ide;
858
- const activeIde = serverState3.ideInfo?.ide;
859
- const activeIdeScheme = serverState3.ideInfo?.scheme;
1061
+ const activeIde = serverState2.ideInfo?.ide;
1062
+ const activeIdeScheme = serverState2.ideInfo?.scheme;
860
1063
  const configuredIdeMatchesActiveScheme = Boolean(configuredIde) && Boolean(activeIdeScheme) && normalizeIdeToken2(configuredIde) === normalizeIdeToken2(activeIdeScheme);
861
1064
  const rawEditorHint = configuredIdeMatchesActiveScheme ? activeIdeScheme : configuredIde || activeIde || activeIdeScheme || "code";
862
1065
  if (configuredIde && activeIdeScheme && normalizeIdeToken2(activeIdeScheme).includes(normalizeIdeToken2(configuredIde)) === false) {
@@ -870,7 +1073,7 @@ function handleOpenFileRequest(body, serverState3) {
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, serverState3) {
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, serverState3) {
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,
@@ -946,6 +1149,7 @@ function resolveProjectRoot() {
946
1149
 
947
1150
  // src/server/index.ts
948
1151
  var serverLogger4 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
1152
+ var PORT_FILE_NAME = "inspecto.port.json";
949
1153
  var serverState = {
950
1154
  port: null,
951
1155
  running: false,
@@ -954,6 +1158,42 @@ var serverState = {
954
1158
  cwd: process.cwd()
955
1159
  };
956
1160
  var serverInstance = null;
1161
+ function getPortFilePath() {
1162
+ return path6.join(os2.tmpdir(), PORT_FILE_NAME);
1163
+ }
1164
+ function getProjectRootHash() {
1165
+ if (!serverState.projectRoot) return null;
1166
+ return crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1167
+ }
1168
+ function readPortData(portFile) {
1169
+ if (!fs5.existsSync(portFile)) return {};
1170
+ try {
1171
+ return JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1172
+ } catch {
1173
+ return {};
1174
+ }
1175
+ }
1176
+ function writeProjectPort(port) {
1177
+ const rootHash = getProjectRootHash();
1178
+ if (!rootHash) return;
1179
+ const portFile = getPortFilePath();
1180
+ const portData = readPortData(portFile);
1181
+ portData[rootHash] = port;
1182
+ fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1183
+ }
1184
+ function removeProjectPort() {
1185
+ const rootHash = getProjectRootHash();
1186
+ if (!rootHash) return;
1187
+ const portFile = getPortFilePath();
1188
+ if (!fs5.existsSync(portFile)) return;
1189
+ const portData = readPortData(portFile);
1190
+ delete portData[rootHash];
1191
+ if (Object.keys(portData).length === 0) {
1192
+ fs5.unlinkSync(portFile);
1193
+ } else {
1194
+ fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1195
+ }
1196
+ }
957
1197
  async function startServer() {
958
1198
  if (serverState.running && serverState.port !== null) {
959
1199
  return serverState.port;
@@ -987,7 +1227,7 @@ async function startServer() {
987
1227
  });
988
1228
  });
989
1229
  await new Promise((resolve2, reject) => {
990
- serverInstance.listen(port, "127.0.0.1", () => {
1230
+ serverInstance.listen(port, "0.0.0.0", () => {
991
1231
  serverInstance.unref();
992
1232
  resolve2();
993
1233
  });
@@ -998,37 +1238,18 @@ async function startServer() {
998
1238
  });
999
1239
  serverState.port = port;
1000
1240
  serverState.running = true;
1001
- const portFile = path6.join(os2.tmpdir(), "inspecto.port.json");
1002
1241
  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");
1013
- } catch (e) {
1014
- serverLogger4.warn("Failed to write port file:", e);
1242
+ writeProjectPort(port);
1243
+ } catch (_e) {
1244
+ serverLogger4.warn("Failed to write port file:", _e);
1015
1245
  }
1016
1246
  process.once("exit", () => {
1017
1247
  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
- }
1248
+ removeProjectPort();
1028
1249
  } catch {
1029
1250
  }
1030
1251
  });
1031
- serverLogger4.info(`server running at http://127.0.0.1:${port}`);
1252
+ serverLogger4.info(`server running at http://0.0.0.0:${port}`);
1032
1253
  return port;
1033
1254
  }
1034
1255
  async function readBody(req) {
@@ -1080,11 +1301,11 @@ async function handleRequest(url, req, res) {
1080
1301
  }
1081
1302
  return;
1082
1303
  }
1083
- if (pathname === INSPECTO_API_PATHS.IDE_OPEN && req.method === "POST") {
1304
+ if ((pathname === INSPECTO_API_PATHS.SOURCE_OPEN || pathname === INSPECTO_API_PATHS.IDE_OPEN) && req.method === "POST") {
1084
1305
  let body;
1085
1306
  try {
1086
1307
  body = JSON.parse(await readBody(req));
1087
- } catch (e) {
1308
+ } catch (_e) {
1088
1309
  res.writeHead(400, { "Content-Type": "application/json" });
1089
1310
  res.end(JSON.stringify({ error: "Invalid JSON body" }));
1090
1311
  return;
@@ -1093,7 +1314,7 @@ async function handleRequest(url, req, res) {
1093
1314
  handleOpenFileRequest(body, serverState);
1094
1315
  } catch (err) {
1095
1316
  serverLogger4.warn(
1096
- `Security: Blocked path traversal attempt in IDE_OPEN: ${body.file}. Reason: ${err.message}`
1317
+ `Security: Blocked path traversal attempt in SOURCE_OPEN: ${body.file}. Reason: ${err.message}`
1097
1318
  );
1098
1319
  res.writeHead(403, { "Content-Type": "application/json" });
1099
1320
  res.end(JSON.stringify({ error: "Access denied: File is outside of project workspace" }));
@@ -1167,6 +1388,212 @@ async function handleRequest(url, req, res) {
1167
1388
  }
1168
1389
  return;
1169
1390
  }
1391
+ if (pathname === INSPECTO_API_PATHS.SESSION_CLAIM_NEXT && req.method === "POST") {
1392
+ try {
1393
+ const rawBody = await readBody(req);
1394
+ const body = rawBody ? JSON.parse(rawBody) : {};
1395
+ const timeoutMs = normalizeSessionClaimTimeout(
1396
+ body.timeoutMs === void 0 ? null : String(body.timeoutMs)
1397
+ );
1398
+ const result = await annotationSessionStore.claimNextSession({
1399
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
1400
+ });
1401
+ res.writeHead(200, { "Content-Type": "application/json" });
1402
+ res.end(
1403
+ JSON.stringify({
1404
+ success: true,
1405
+ timedOut: result.timedOut,
1406
+ matchedExisting: result.matchedExisting,
1407
+ ...result.event ? { event: result.event } : {},
1408
+ ...result.session ? { session: result.session } : {}
1409
+ })
1410
+ );
1411
+ } catch (e) {
1412
+ serverLogger4.error(`Error parsing session claim request:`, e);
1413
+ res.writeHead(400, { "Content-Type": "application/json" });
1414
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1415
+ }
1416
+ return;
1417
+ }
1418
+ if (pathname === INSPECTO_API_PATHS.SESSION_EVENTS && req.method === "GET") {
1419
+ const statusParam = url.searchParams.getAll("status");
1420
+ const statuses = statusParam.length ? new Set(statusParam) : null;
1421
+ const sessionId = url.searchParams.get("sessionId")?.trim() || null;
1422
+ res.writeHead(200, {
1423
+ "Content-Type": "text/event-stream",
1424
+ "Cache-Control": "no-cache",
1425
+ Connection: "keep-alive"
1426
+ });
1427
+ res.write(`event: ready
1428
+ data: ${JSON.stringify({ ok: true })}
1429
+
1430
+ `);
1431
+ const unsubscribe = annotationSessionStore.subscribe((event) => {
1432
+ if (sessionId && event.session.id !== sessionId) {
1433
+ return;
1434
+ }
1435
+ if (statuses && !statuses.has(event.session.status)) {
1436
+ return;
1437
+ }
1438
+ res.write(formatSessionSseEvent(event));
1439
+ });
1440
+ req.on("close", () => {
1441
+ unsubscribe();
1442
+ res.end();
1443
+ });
1444
+ return;
1445
+ }
1446
+ if (pathname === INSPECTO_API_PATHS.SESSIONS && req.method === "GET") {
1447
+ const statusParam = url.searchParams.getAll("status");
1448
+ const sessions = annotationSessionStore.listSessions(
1449
+ statusParam.length ? {
1450
+ status: statusParam
1451
+ } : void 0
1452
+ );
1453
+ res.writeHead(200, { "Content-Type": "application/json" });
1454
+ res.end(JSON.stringify({ success: true, sessions }));
1455
+ return;
1456
+ }
1457
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && req.method === "GET") {
1458
+ const sessionId = pathname.substring(INSPECTO_API_PATHS.SESSIONS.length + 1);
1459
+ const session = annotationSessionStore.getSession(sessionId);
1460
+ if (!session) {
1461
+ res.writeHead(404, { "Content-Type": "application/json" });
1462
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1463
+ return;
1464
+ }
1465
+ res.writeHead(200, { "Content-Type": "application/json" });
1466
+ res.end(JSON.stringify({ success: true, session }));
1467
+ return;
1468
+ }
1469
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX) && req.method === "POST") {
1470
+ const sessionId = pathname.slice(
1471
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
1472
+ -INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX.length
1473
+ );
1474
+ try {
1475
+ const rawBody = await readBody(req);
1476
+ const body = JSON.parse(rawBody);
1477
+ if (!isAnnotationThreadRole(body.role)) {
1478
+ res.writeHead(400, { "Content-Type": "application/json" });
1479
+ res.end(JSON.stringify({ success: false, error: "Reply role is invalid." }));
1480
+ return;
1481
+ }
1482
+ if (!body.text?.trim()) {
1483
+ res.writeHead(400, { "Content-Type": "application/json" });
1484
+ res.end(JSON.stringify({ success: false, error: "Reply text is required." }));
1485
+ return;
1486
+ }
1487
+ const session = annotationSessionStore.appendMessage(sessionId, {
1488
+ role: body.role,
1489
+ text: body.text.trim()
1490
+ });
1491
+ if (!session) {
1492
+ res.writeHead(404, { "Content-Type": "application/json" });
1493
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1494
+ return;
1495
+ }
1496
+ res.writeHead(200, { "Content-Type": "application/json" });
1497
+ res.end(JSON.stringify({ success: true, session }));
1498
+ } catch (e) {
1499
+ serverLogger4.error(`Error parsing session reply request:`, e);
1500
+ res.writeHead(400, { "Content-Type": "application/json" });
1501
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1502
+ }
1503
+ return;
1504
+ }
1505
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX) && req.method === "POST") {
1506
+ const sessionId = pathname.slice(
1507
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
1508
+ -INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX.length
1509
+ );
1510
+ try {
1511
+ const rawBody = await readBody(req);
1512
+ const body = rawBody ? JSON.parse(rawBody) : {};
1513
+ const message = body.message?.trim();
1514
+ const existingSession = annotationSessionStore.getSession(sessionId);
1515
+ if (!existingSession) {
1516
+ res.writeHead(404, { "Content-Type": "application/json" });
1517
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1518
+ return;
1519
+ }
1520
+ if (!message && !hasAgentReply(existingSession)) {
1521
+ res.writeHead(400, { "Content-Type": "application/json" });
1522
+ res.end(
1523
+ JSON.stringify({
1524
+ success: false,
1525
+ error: "Resolve message is required until an agent reply is recorded."
1526
+ })
1527
+ );
1528
+ return;
1529
+ }
1530
+ if (message) {
1531
+ const repliedSession = annotationSessionStore.appendMessage(sessionId, {
1532
+ role: "agent",
1533
+ text: message
1534
+ });
1535
+ if (!repliedSession) {
1536
+ res.writeHead(404, { "Content-Type": "application/json" });
1537
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1538
+ return;
1539
+ }
1540
+ }
1541
+ const session = annotationSessionStore.updateStatus(sessionId, "resolved");
1542
+ if (!session) {
1543
+ res.writeHead(404, { "Content-Type": "application/json" });
1544
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1545
+ return;
1546
+ }
1547
+ res.writeHead(200, { "Content-Type": "application/json" });
1548
+ res.end(JSON.stringify({ success: true, session }));
1549
+ } catch (e) {
1550
+ serverLogger4.error(`Error parsing session resolve request:`, e);
1551
+ res.writeHead(400, { "Content-Type": "application/json" });
1552
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1553
+ }
1554
+ return;
1555
+ }
1556
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX) && req.method === "POST") {
1557
+ const sessionId = pathname.slice(
1558
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
1559
+ -INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX.length
1560
+ );
1561
+ try {
1562
+ const rawBody = await readBody(req);
1563
+ const body = rawBody ? JSON.parse(rawBody) : {};
1564
+ const message = body.message?.trim();
1565
+ const existingSession = annotationSessionStore.getSession(sessionId);
1566
+ if (!existingSession) {
1567
+ res.writeHead(404, { "Content-Type": "application/json" });
1568
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1569
+ return;
1570
+ }
1571
+ if (message) {
1572
+ const repliedSession = annotationSessionStore.appendMessage(sessionId, {
1573
+ role: "agent",
1574
+ text: message
1575
+ });
1576
+ if (!repliedSession) {
1577
+ res.writeHead(404, { "Content-Type": "application/json" });
1578
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1579
+ return;
1580
+ }
1581
+ }
1582
+ const session = annotationSessionStore.updateStatus(sessionId, "dismissed");
1583
+ if (!session) {
1584
+ res.writeHead(404, { "Content-Type": "application/json" });
1585
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1586
+ return;
1587
+ }
1588
+ res.writeHead(200, { "Content-Type": "application/json" });
1589
+ res.end(JSON.stringify({ success: true, session }));
1590
+ } catch (e) {
1591
+ serverLogger4.error(`Error parsing session dismiss request:`, e);
1592
+ res.writeHead(400, { "Content-Type": "application/json" });
1593
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1594
+ }
1595
+ return;
1596
+ }
1170
1597
  if (pathname.startsWith(`${INSPECTO_API_PATHS.AI_TICKET}/`) && req.method === "GET") {
1171
1598
  const ticketId = pathname.substring(INSPECTO_API_PATHS.AI_TICKET.length + 1);
1172
1599
  const payloadStr = readTicket(ticketId);
@@ -1183,7 +1610,7 @@ async function handleRequest(url, req, res) {
1183
1610
  res.end(JSON.stringify({ error: "not found" }));
1184
1611
  }
1185
1612
  async function dispatchToAi(req) {
1186
- const { location, snippet, prompt, screenshotContext } = req;
1613
+ const { location, snippet, prompt } = req;
1187
1614
  const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
1188
1615
 
1189
1616
  \`\`\`
@@ -1196,8 +1623,7 @@ ${snippet}
1196
1623
  filePath: location.file,
1197
1624
  line: location.line,
1198
1625
  column: location.column,
1199
- snippet,
1200
- ...screenshotContext ? { screenshotContext } : {}
1626
+ snippet
1201
1627
  });
1202
1628
  }
1203
1629
  function getBatchDispatchStatusCode(errorCode, success) {
@@ -1206,6 +1632,21 @@ function getBatchDispatchStatusCode(errorCode, success) {
1206
1632
  if (errorCode === "FORBIDDEN_PATH") return 403;
1207
1633
  return 500;
1208
1634
  }
1635
+ function isAnnotationThreadRole(value) {
1636
+ return value === "user" || value === "agent" || value === "system";
1637
+ }
1638
+ function formatSessionSseEvent(event) {
1639
+ return `event: ${event.type}
1640
+ data: ${JSON.stringify(event)}
1641
+
1642
+ `;
1643
+ }
1644
+ function normalizeSessionClaimTimeout(value) {
1645
+ if (!value?.trim()) return 3e4;
1646
+ const parsed = Number.parseInt(value, 10);
1647
+ if (!Number.isFinite(parsed)) return 3e4;
1648
+ return Math.max(0, Math.min(parsed, 3e5));
1649
+ }
1209
1650
 
1210
1651
  // src/injectors/utils.ts
1211
1652
  import { createRequire } from "module";
@@ -1228,13 +1669,13 @@ var resolveClientModule = () => {
1228
1669
  function getWebpackHtmlScript(serverPort2) {
1229
1670
  return `
1230
1671
  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
- });
1672
+ window.addEventListener('load', () => {
1673
+ if (window.InspectoClient) {
1674
+ window.InspectoClient.mountInspector({
1675
+ serverUrl: 'http://0.0.0.0:' + window.__AI_INSPECTOR_PORT__,
1676
+ });
1677
+ }
1678
+ });
1238
1679
  `;
1239
1680
  }
1240
1681