@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
@@ -35,21 +35,22 @@ __export(webpack4_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(webpack4_exports);
37
37
 
38
- // ../../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/cjs_shims.js
38
+ // ../../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/cjs_shims.js
39
39
  var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
40
40
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
41
41
 
42
42
  // src/injectors/webpack.ts
43
- function getWebpackHtmlScript(serverPort) {
43
+ function getWebpackHtmlScript(serverPort, publicServerUrl) {
44
44
  return `
45
45
  window.__AI_INSPECTOR_PORT__ = ${serverPort};
46
- window.addEventListener('load', () => {
47
- if (window.InspectoClient) {
48
- window.InspectoClient.mountInspector({
49
- serverUrl: 'http://127.0.0.1:' + window.__AI_INSPECTOR_PORT__,
50
- });
51
- }
52
- });
46
+ window.__AI_INSPECTOR_SERVER_URL__ = '${publicServerUrl ?? `http://127.0.0.1:${serverPort}`}';
47
+ window.addEventListener('load', () => {
48
+ if (window.InspectoClient) {
49
+ window.InspectoClient.mountInspector({
50
+ serverUrl: window.__AI_INSPECTOR_SERVER_URL__,
51
+ });
52
+ }
53
+ });
53
54
  `;
54
55
  }
55
56
 
@@ -567,7 +568,6 @@ function dispatchPromptThroughIde(runtime, payload) {
567
568
  line: payload.line,
568
569
  column: payload.column,
569
570
  snippet: payload.snippet,
570
- ...payload.screenshotContext ? { screenshotContext: payload.screenshotContext } : {},
571
571
  overrides: runtime.overrides,
572
572
  autoSend: runtime.autoSend
573
573
  });
@@ -701,6 +701,188 @@ function assertPathWithinIdeOpenScope(file, projectRoot) {
701
701
  }
702
702
  }
703
703
 
704
+ // src/server/session-store.ts
705
+ var DEFAULT_STATUS = "pending";
706
+ function createAnnotationSessionStore(options = {}) {
707
+ const sessions = /* @__PURE__ */ new Map();
708
+ const listeners = /* @__PURE__ */ new Set();
709
+ const now = options.now ?? (() => Date.now());
710
+ const createId = options.createId ?? createRandomId;
711
+ function findNewestMatchingSession(statuses) {
712
+ return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
713
+ }
714
+ function updateSessionStatus(id, status) {
715
+ const session = sessions.get(id);
716
+ if (!session) return null;
717
+ const timestamp = now();
718
+ session.status = status;
719
+ session.updatedAt = timestamp;
720
+ if (status === "acknowledged") {
721
+ session.acknowledgedAt = timestamp;
722
+ }
723
+ if (status === "resolved") {
724
+ session.resolvedAt = timestamp;
725
+ }
726
+ emit({ type: "session-status-updated", session });
727
+ return cloneSession(session);
728
+ }
729
+ function claimSession(id, statuses) {
730
+ const session = sessions.get(id);
731
+ if (!session || statuses && !statuses.has(session.status)) return null;
732
+ if (session.status === "acknowledged") return cloneSession(session);
733
+ return updateSessionStatus(id, "acknowledged");
734
+ }
735
+ function emit(event) {
736
+ const snapshot = cloneSession(event.session);
737
+ for (const listener of listeners) {
738
+ listener({ type: event.type, session: snapshot });
739
+ }
740
+ }
741
+ const store = {
742
+ createSession(input) {
743
+ const timestamp = now();
744
+ const session = {
745
+ id: createId(),
746
+ instruction: input.instruction?.trim() ?? "",
747
+ annotations: cloneArray(input.annotations),
748
+ ...input.deliveryMode ? { deliveryMode: input.deliveryMode } : {},
749
+ status: DEFAULT_STATUS,
750
+ messages: cloneArray(input.messages ?? []),
751
+ createdAt: timestamp,
752
+ updatedAt: timestamp,
753
+ ...input.runtimeContext ? { runtimeContext: cloneValue(input.runtimeContext) } : {},
754
+ ...input.cssContextPrompt?.trim() ? { cssContextPrompt: input.cssContextPrompt.trim() } : {},
755
+ ...input.pageUrl ? { pageUrl: input.pageUrl } : {},
756
+ ...input.route ? { route: input.route } : {}
757
+ };
758
+ sessions.set(session.id, session);
759
+ emit({ type: "session-created", session });
760
+ return cloneSession(session);
761
+ },
762
+ getSession(id) {
763
+ const session = sessions.get(id);
764
+ return session ? cloneSession(session) : null;
765
+ },
766
+ listSessions(options2 = {}) {
767
+ const statuses = normalizeStatuses(options2.status);
768
+ return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt).map((session) => cloneSession(session));
769
+ },
770
+ async claimNextSession(options2 = {}) {
771
+ const statuses = normalizeStatuses(DEFAULT_STATUS);
772
+ const existingSession = findNewestMatchingSession(statuses);
773
+ if (existingSession) {
774
+ return {
775
+ session: claimSession(existingSession.id, statuses),
776
+ timedOut: false,
777
+ matchedExisting: true
778
+ };
779
+ }
780
+ const timeoutMs = normalizeTimeoutMs(options2.timeoutMs);
781
+ if (timeoutMs === 0) {
782
+ return {
783
+ session: null,
784
+ timedOut: true,
785
+ matchedExisting: false
786
+ };
787
+ }
788
+ return await new Promise((resolve2) => {
789
+ let settled = false;
790
+ let timeout = null;
791
+ const finish = (result) => {
792
+ if (settled) return;
793
+ settled = true;
794
+ unsubscribe();
795
+ if (timeout) {
796
+ clearTimeout(timeout);
797
+ }
798
+ resolve2(result);
799
+ };
800
+ const unsubscribe = this.subscribe((event) => {
801
+ const session = claimSession(event.session.id, statuses);
802
+ if (!session) return;
803
+ finish({
804
+ session,
805
+ timedOut: false,
806
+ matchedExisting: false,
807
+ event: event.type
808
+ });
809
+ });
810
+ if (timeoutMs !== null) {
811
+ timeout = setTimeout(() => {
812
+ finish({
813
+ session: null,
814
+ timedOut: true,
815
+ matchedExisting: false
816
+ });
817
+ }, timeoutMs);
818
+ }
819
+ });
820
+ },
821
+ appendMessage(id, input) {
822
+ const session = sessions.get(id);
823
+ if (!session) return null;
824
+ const timestamp = now();
825
+ session.messages.push({
826
+ id: createId(),
827
+ role: input.role,
828
+ text: input.text,
829
+ createdAt: timestamp
830
+ });
831
+ session.updatedAt = timestamp;
832
+ if (input.role === "agent" && isPendingLikeStatus(session.status)) {
833
+ session.status = "in_progress";
834
+ }
835
+ emit({ type: "session-message-appended", session });
836
+ return cloneSession(session);
837
+ },
838
+ updateStatus(id, status) {
839
+ return updateSessionStatus(id, status);
840
+ },
841
+ subscribe(listener) {
842
+ listeners.add(listener);
843
+ return () => {
844
+ listeners.delete(listener);
845
+ };
846
+ },
847
+ clear() {
848
+ sessions.clear();
849
+ listeners.clear();
850
+ }
851
+ };
852
+ return store;
853
+ }
854
+ var annotationSessionStore = createAnnotationSessionStore();
855
+ function normalizeStatuses(status) {
856
+ if (!status) return null;
857
+ return new Set(Array.isArray(status) ? status : [status]);
858
+ }
859
+ function normalizeTimeoutMs(value) {
860
+ if (value === void 0) return null;
861
+ if (!Number.isFinite(value)) return 0;
862
+ return Math.max(0, Math.floor(value));
863
+ }
864
+ function isPendingLikeStatus(status) {
865
+ return status === "pending" || status === "acknowledged";
866
+ }
867
+ function hasAgentReply(session) {
868
+ return session.messages.some((message) => message.role === "agent" && Boolean(message.text?.trim()));
869
+ }
870
+ function createRandomId() {
871
+ return `annotation-session-${Math.random().toString(36).slice(2, 10)}`;
872
+ }
873
+ function cloneSession(session) {
874
+ return cloneValue(session);
875
+ }
876
+ function cloneArray(value) {
877
+ return cloneValue(value);
878
+ }
879
+ function cloneValue(value) {
880
+ if (typeof structuredClone === "function") {
881
+ return structuredClone(value);
882
+ }
883
+ return JSON.parse(JSON.stringify(value));
884
+ }
885
+
704
886
  // src/server/annotation-dispatch.ts
705
887
  var AnnotationDispatchError = class extends Error {
706
888
  constructor(message, errorCode) {
@@ -709,20 +891,30 @@ var AnnotationDispatchError = class extends Error {
709
891
  this.errorCode = errorCode;
710
892
  }
711
893
  };
712
- async function dispatchAnnotationsToAi(req, state) {
894
+ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStore) {
713
895
  try {
714
896
  validateAnnotationDispatchRequest(req, state);
715
897
  const batch = normalizeAnnotationBatch(req);
716
898
  const prompt = buildAnnotationBatchPrompt(batch);
899
+ const deliveryMode = normalizeDeliveryMode(req.deliveryMode);
900
+ const session = store.createSession({
901
+ instruction: batch.instruction,
902
+ annotations: toSessionAnnotations(batch.annotations),
903
+ deliveryMode,
904
+ ...batch.runtimeContext ? { runtimeContext: batch.runtimeContext } : {},
905
+ ...batch.cssContextPrompt ? { cssContextPrompt: batch.cssContextPrompt } : {}
906
+ });
717
907
  const representativeTarget = batch.annotations[0]?.targets[0];
718
- const runtime = resolvePromptDispatchRuntime(state);
719
- return dispatchPromptThroughIde(runtime, {
908
+ const dispatchResult = deliveryMode === "ide" ? dispatchPromptThroughIde(resolvePromptDispatchRuntime(state), {
720
909
  prompt,
721
910
  ...representativeTarget?.file ? { filePath: representativeTarget.file } : {},
722
911
  ...representativeTarget?.line ? { line: representativeTarget.line } : {},
723
- ...representativeTarget?.column ? { column: representativeTarget.column } : {},
724
- ...batch.screenshotContext ? { screenshotContext: batch.screenshotContext } : {}
725
- });
912
+ ...representativeTarget?.column ? { column: representativeTarget.column } : {}
913
+ }) : { success: true };
914
+ return {
915
+ ...dispatchResult,
916
+ session: toSessionSummary(session)
917
+ };
726
918
  } catch (error) {
727
919
  return {
728
920
  success: false,
@@ -731,6 +923,41 @@ async function dispatchAnnotationsToAi(req, state) {
731
923
  };
732
924
  }
733
925
  }
926
+ function normalizeDeliveryMode(input) {
927
+ return input === "agent" ? "agent" : "ide";
928
+ }
929
+ function toSessionAnnotations(annotations) {
930
+ return annotations.map((annotation) => ({
931
+ id: `annotation-${annotation.index}`,
932
+ note: annotation.note,
933
+ intent: annotation.intent,
934
+ targets: annotation.targets.map((target, targetIndex) => ({
935
+ id: `annotation-${annotation.index}-target-${targetIndex + 1}`,
936
+ label: target.label ?? "Unknown target",
937
+ location: {
938
+ file: target.file,
939
+ line: target.line,
940
+ column: target.column
941
+ },
942
+ ...target.selector ? { selector: target.selector } : {},
943
+ ...target.snippet ? { snippet: target.snippet } : {},
944
+ rect: {
945
+ x: 0,
946
+ y: 0,
947
+ width: 0,
948
+ height: 0
949
+ }
950
+ }))
951
+ }));
952
+ }
953
+ function toSessionSummary(session) {
954
+ return {
955
+ id: session.id,
956
+ status: session.status,
957
+ createdAt: session.createdAt,
958
+ updatedAt: session.updatedAt
959
+ };
960
+ }
734
961
  function validateAnnotationDispatchRequest(req, state) {
735
962
  if (!req.annotations.length) {
736
963
  throw new AnnotationDispatchError("At least one annotation is required.", "INVALID_REQUEST");
@@ -751,9 +978,7 @@ function validateAnnotationDispatchRequest(req, state) {
751
978
  function normalizeAnnotationBatch(req) {
752
979
  return {
753
980
  instruction: req.instruction?.trim() ?? "",
754
- responseMode: req.responseMode ?? "unified",
755
981
  ...req.runtimeContext ? { runtimeContext: req.runtimeContext } : {},
756
- ...req.screenshotContext ? { screenshotContext: req.screenshotContext } : {},
757
982
  ...req.cssContextPrompt?.trim() ? { cssContextPrompt: req.cssContextPrompt.trim() } : {},
758
983
  annotations: req.annotations.map((annotation, index) => ({
759
984
  index: index + 1,
@@ -775,12 +1000,9 @@ function buildAnnotationBatchPrompt(batch) {
775
1000
  const prompt = batch.instruction ? `${batch.instruction}
776
1001
 
777
1002
  ${body}` : body;
778
- return appendScreenshotContextSection(
779
- appendCssContextSection(
780
- appendRuntimeContextSection(prompt, batch.runtimeContext),
781
- batch.cssContextPrompt
782
- ),
783
- batch.screenshotContext
1003
+ return appendCssContextSection(
1004
+ appendRuntimeContextSection(prompt, batch.runtimeContext),
1005
+ batch.cssContextPrompt
784
1006
  );
785
1007
  }
786
1008
  function appendCssContextSection(prompt, cssContextPrompt) {
@@ -807,20 +1029,6 @@ function buildSelectedElementsPrompt(annotations) {
807
1029
  }
808
1030
  return lines.join("\n");
809
1031
  }
810
- function appendScreenshotContextSection(prompt, screenshotContext) {
811
- if (!screenshotContext || !screenshotContext.imageDataUrl && !screenshotContext.imageAssetId) {
812
- return prompt;
813
- }
814
- const lines = [
815
- "Visual screenshot context attached:",
816
- `- capturedAt=${screenshotContext.capturedAt}`,
817
- `- mimeType=${screenshotContext.mimeType}`,
818
- ...screenshotContext.imageAssetId ? [`- imageAssetId=${screenshotContext.imageAssetId}`] : []
819
- ];
820
- return `${prompt}
821
-
822
- ${lines.join("\n")}`;
823
- }
824
1032
  function appendRuntimeContextSection(prompt, runtimeContext) {
825
1033
  if (!runtimeContext?.records.length) {
826
1034
  return prompt;
@@ -868,7 +1076,7 @@ async function buildClientConfig(serverState2) {
868
1076
  ...info,
869
1077
  prompts: resolveIntents(promptsConfig),
870
1078
  hotKeys: userConfig["inspector.hotKey"] ?? "alt",
871
- theme: userConfig["inspector.theme"] ?? "auto",
1079
+ annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "both",
872
1080
  includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
873
1081
  runtimeContext: {
874
1082
  enabled: true,
@@ -876,10 +1084,6 @@ async function buildClientConfig(serverState2) {
876
1084
  maxRuntimeErrors: 3,
877
1085
  maxFailedRequests: 2
878
1086
  },
879
- screenshotContext: {
880
- enabled: false
881
- },
882
- annotationResponseMode: userConfig["prompt.annotationResponseMode"] ?? "unified",
883
1087
  autoSend: userConfig["prompt.autoSend"] ?? false
884
1088
  };
885
1089
  }
@@ -924,7 +1128,7 @@ function handleOpenFileRequest(body, serverState2) {
924
1128
  else if (rawEditorHint === "vscodium") editorHint = "codium";
925
1129
  else if (rawEditorHint === "trae-cn" || rawEditorHint === "trae") editorHint = "trae";
926
1130
  serverLogger2.debug(
927
- `IDE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
1131
+ `SOURCE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
928
1132
  );
929
1133
  if (VSCODE_FAMILY_SCHEMES.includes(rawEditorHint)) {
930
1134
  let normalizedPath = absolutePath.replace(/\\/g, "/");
@@ -933,7 +1137,7 @@ function handleOpenFileRequest(body, serverState2) {
933
1137
  }
934
1138
  const encodedPath = encodeURI(normalizedPath);
935
1139
  const uri = `${rawEditorHint}://file${encodedPath}:${body.line}:${body.column}`;
936
- serverLogger2.debug(`IDE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
1140
+ serverLogger2.debug(`SOURCE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
937
1141
  try {
938
1142
  if (process.platform === "darwin") {
939
1143
  (0, import_node_child_process2.execFileSync)("open", [uri]);
@@ -943,7 +1147,7 @@ function handleOpenFileRequest(body, serverState2) {
943
1147
  (0, import_node_child_process2.execFileSync)("xdg-open", [uri]);
944
1148
  }
945
1149
  } catch (e) {
946
- serverLogger2.error(`Failed to launch URI for IDE_OPEN (${uri}):`, e);
1150
+ serverLogger2.error(`Failed to launch URI for SOURCE_OPEN (${uri}):`, e);
947
1151
  (0, import_launch_ide2.launchIDE)({
948
1152
  file: absolutePath,
949
1153
  line: body.line,
@@ -998,8 +1202,18 @@ function resolveProjectRoot() {
998
1202
  return gitRoot;
999
1203
  }
1000
1204
 
1205
+ // src/server/server-url.ts
1206
+ function resolveServerHost(cwd, configRoot) {
1207
+ const userConfig = loadUserConfigSync(false, cwd, configRoot);
1208
+ const configuredHost = userConfig["server.host"]?.trim();
1209
+ if (configuredHost) return configuredHost;
1210
+ if (process.env["VITEST"]) return "127.0.0.1";
1211
+ return "127.0.0.1";
1212
+ }
1213
+
1001
1214
  // src/server/index.ts
1002
1215
  var serverLogger4 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
1216
+ var PORT_FILE_NAME = "inspecto.port.json";
1003
1217
  var serverState = {
1004
1218
  port: null,
1005
1219
  running: false,
@@ -1008,6 +1222,42 @@ var serverState = {
1008
1222
  cwd: process.cwd()
1009
1223
  };
1010
1224
  var serverInstance = null;
1225
+ function getPortFilePath() {
1226
+ return import_node_path4.default.join(import_node_os2.default.tmpdir(), PORT_FILE_NAME);
1227
+ }
1228
+ function getProjectRootHash() {
1229
+ if (!serverState.projectRoot) return null;
1230
+ return import_node_crypto2.default.createHash("md5").update(serverState.projectRoot).digest("hex");
1231
+ }
1232
+ function readPortData(portFile) {
1233
+ if (!import_node_fs4.default.existsSync(portFile)) return {};
1234
+ try {
1235
+ return JSON.parse(import_node_fs4.default.readFileSync(portFile, "utf-8"));
1236
+ } catch {
1237
+ return {};
1238
+ }
1239
+ }
1240
+ function writeProjectPort(port) {
1241
+ const rootHash = getProjectRootHash();
1242
+ if (!rootHash) return;
1243
+ const portFile = getPortFilePath();
1244
+ const portData = readPortData(portFile);
1245
+ portData[rootHash] = port;
1246
+ import_node_fs4.default.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1247
+ }
1248
+ function removeProjectPort() {
1249
+ const rootHash = getProjectRootHash();
1250
+ if (!rootHash) return;
1251
+ const portFile = getPortFilePath();
1252
+ if (!import_node_fs4.default.existsSync(portFile)) return;
1253
+ const portData = readPortData(portFile);
1254
+ delete portData[rootHash];
1255
+ if (Object.keys(portData).length === 0) {
1256
+ import_node_fs4.default.unlinkSync(portFile);
1257
+ } else {
1258
+ import_node_fs4.default.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1259
+ }
1260
+ }
1011
1261
  async function startServer() {
1012
1262
  if (serverState.running && serverState.port !== null) {
1013
1263
  return serverState.port;
@@ -1015,6 +1265,7 @@ async function startServer() {
1015
1265
  serverState.projectRoot = resolveProjectRoot();
1016
1266
  serverState.configRoot = serverState.projectRoot;
1017
1267
  serverState.cwd = process.cwd();
1268
+ const serverHost = resolveServerHost(serverState.cwd, serverState.configRoot);
1018
1269
  import_portfinder.default.basePort = 5678;
1019
1270
  const port = await import_portfinder.default.getPortPromise();
1020
1271
  watchConfig(
@@ -1041,7 +1292,7 @@ async function startServer() {
1041
1292
  });
1042
1293
  });
1043
1294
  await new Promise((resolve2, reject) => {
1044
- serverInstance.listen(port, "127.0.0.1", () => {
1295
+ serverInstance.listen(port, serverHost, () => {
1045
1296
  serverInstance.unref();
1046
1297
  resolve2();
1047
1298
  });
@@ -1052,37 +1303,18 @@ async function startServer() {
1052
1303
  });
1053
1304
  serverState.port = port;
1054
1305
  serverState.running = true;
1055
- const portFile = import_node_path4.default.join(import_node_os2.default.tmpdir(), "inspecto.port.json");
1056
1306
  try {
1057
- let portData = {};
1058
- if (import_node_fs4.default.existsSync(portFile)) {
1059
- try {
1060
- portData = JSON.parse(import_node_fs4.default.readFileSync(portFile, "utf-8"));
1061
- } catch (_e) {
1062
- }
1063
- }
1064
- const rootHash = import_node_crypto2.default.createHash("md5").update(serverState.projectRoot).digest("hex");
1065
- portData[rootHash] = port;
1066
- import_node_fs4.default.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1307
+ writeProjectPort(port);
1067
1308
  } catch (_e) {
1068
1309
  serverLogger4.warn("Failed to write port file:", _e);
1069
1310
  }
1070
1311
  process.once("exit", () => {
1071
1312
  try {
1072
- if (import_node_fs4.default.existsSync(portFile)) {
1073
- const portData = JSON.parse(import_node_fs4.default.readFileSync(portFile, "utf-8"));
1074
- const rootHash = import_node_crypto2.default.createHash("md5").update(serverState.projectRoot).digest("hex");
1075
- delete portData[rootHash];
1076
- if (Object.keys(portData).length === 0) {
1077
- import_node_fs4.default.unlinkSync(portFile);
1078
- } else {
1079
- import_node_fs4.default.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1080
- }
1081
- }
1313
+ removeProjectPort();
1082
1314
  } catch {
1083
1315
  }
1084
1316
  });
1085
- serverLogger4.info(`server running at http://127.0.0.1:${port}`);
1317
+ serverLogger4.info(`server running at http://${serverHost}:${port}`);
1086
1318
  return port;
1087
1319
  }
1088
1320
  async function readBody(req) {
@@ -1134,7 +1366,7 @@ async function handleRequest(url, req, res) {
1134
1366
  }
1135
1367
  return;
1136
1368
  }
1137
- if (pathname === import_types2.INSPECTO_API_PATHS.IDE_OPEN && req.method === "POST") {
1369
+ if ((pathname === import_types2.INSPECTO_API_PATHS.SOURCE_OPEN || pathname === import_types2.INSPECTO_API_PATHS.IDE_OPEN) && req.method === "POST") {
1138
1370
  let body;
1139
1371
  try {
1140
1372
  body = JSON.parse(await readBody(req));
@@ -1147,7 +1379,7 @@ async function handleRequest(url, req, res) {
1147
1379
  handleOpenFileRequest(body, serverState);
1148
1380
  } catch (err) {
1149
1381
  serverLogger4.warn(
1150
- `Security: Blocked path traversal attempt in IDE_OPEN: ${body.file}. Reason: ${err.message}`
1382
+ `Security: Blocked path traversal attempt in SOURCE_OPEN: ${body.file}. Reason: ${err.message}`
1151
1383
  );
1152
1384
  res.writeHead(403, { "Content-Type": "application/json" });
1153
1385
  res.end(JSON.stringify({ error: "Access denied: File is outside of project workspace" }));
@@ -1221,6 +1453,212 @@ async function handleRequest(url, req, res) {
1221
1453
  }
1222
1454
  return;
1223
1455
  }
1456
+ if (pathname === import_types2.INSPECTO_API_PATHS.SESSION_CLAIM_NEXT && req.method === "POST") {
1457
+ try {
1458
+ const rawBody = await readBody(req);
1459
+ const body = rawBody ? JSON.parse(rawBody) : {};
1460
+ const timeoutMs = normalizeSessionClaimTimeout(
1461
+ body.timeoutMs === void 0 ? null : String(body.timeoutMs)
1462
+ );
1463
+ const result = await annotationSessionStore.claimNextSession({
1464
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
1465
+ });
1466
+ res.writeHead(200, { "Content-Type": "application/json" });
1467
+ res.end(
1468
+ JSON.stringify({
1469
+ success: true,
1470
+ timedOut: result.timedOut,
1471
+ matchedExisting: result.matchedExisting,
1472
+ ...result.event ? { event: result.event } : {},
1473
+ ...result.session ? { session: result.session } : {}
1474
+ })
1475
+ );
1476
+ } catch (e) {
1477
+ serverLogger4.error(`Error parsing session claim request:`, e);
1478
+ res.writeHead(400, { "Content-Type": "application/json" });
1479
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1480
+ }
1481
+ return;
1482
+ }
1483
+ if (pathname === import_types2.INSPECTO_API_PATHS.SESSION_EVENTS && req.method === "GET") {
1484
+ const statusParam = url.searchParams.getAll("status");
1485
+ const statuses = statusParam.length ? new Set(statusParam) : null;
1486
+ const sessionId = url.searchParams.get("sessionId")?.trim() || null;
1487
+ res.writeHead(200, {
1488
+ "Content-Type": "text/event-stream",
1489
+ "Cache-Control": "no-cache",
1490
+ Connection: "keep-alive"
1491
+ });
1492
+ res.write(`event: ready
1493
+ data: ${JSON.stringify({ ok: true })}
1494
+
1495
+ `);
1496
+ const unsubscribe = annotationSessionStore.subscribe((event) => {
1497
+ if (sessionId && event.session.id !== sessionId) {
1498
+ return;
1499
+ }
1500
+ if (statuses && !statuses.has(event.session.status)) {
1501
+ return;
1502
+ }
1503
+ res.write(formatSessionSseEvent(event));
1504
+ });
1505
+ req.on("close", () => {
1506
+ unsubscribe();
1507
+ res.end();
1508
+ });
1509
+ return;
1510
+ }
1511
+ if (pathname === import_types2.INSPECTO_API_PATHS.SESSIONS && req.method === "GET") {
1512
+ const statusParam = url.searchParams.getAll("status");
1513
+ const sessions = annotationSessionStore.listSessions(
1514
+ statusParam.length ? {
1515
+ status: statusParam
1516
+ } : void 0
1517
+ );
1518
+ res.writeHead(200, { "Content-Type": "application/json" });
1519
+ res.end(JSON.stringify({ success: true, sessions }));
1520
+ return;
1521
+ }
1522
+ if (pathname.startsWith(`${import_types2.INSPECTO_API_PATHS.SESSIONS}/`) && req.method === "GET") {
1523
+ const sessionId = pathname.substring(import_types2.INSPECTO_API_PATHS.SESSIONS.length + 1);
1524
+ const session = annotationSessionStore.getSession(sessionId);
1525
+ if (!session) {
1526
+ res.writeHead(404, { "Content-Type": "application/json" });
1527
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1528
+ return;
1529
+ }
1530
+ res.writeHead(200, { "Content-Type": "application/json" });
1531
+ res.end(JSON.stringify({ success: true, session }));
1532
+ return;
1533
+ }
1534
+ if (pathname.startsWith(`${import_types2.INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(import_types2.INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX) && req.method === "POST") {
1535
+ const sessionId = pathname.slice(
1536
+ import_types2.INSPECTO_API_PATHS.SESSIONS.length + 1,
1537
+ -import_types2.INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX.length
1538
+ );
1539
+ try {
1540
+ const rawBody = await readBody(req);
1541
+ const body = JSON.parse(rawBody);
1542
+ if (!isAnnotationThreadRole(body.role)) {
1543
+ res.writeHead(400, { "Content-Type": "application/json" });
1544
+ res.end(JSON.stringify({ success: false, error: "Reply role is invalid." }));
1545
+ return;
1546
+ }
1547
+ if (!body.text?.trim()) {
1548
+ res.writeHead(400, { "Content-Type": "application/json" });
1549
+ res.end(JSON.stringify({ success: false, error: "Reply text is required." }));
1550
+ return;
1551
+ }
1552
+ const session = annotationSessionStore.appendMessage(sessionId, {
1553
+ role: body.role,
1554
+ text: body.text.trim()
1555
+ });
1556
+ if (!session) {
1557
+ res.writeHead(404, { "Content-Type": "application/json" });
1558
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1559
+ return;
1560
+ }
1561
+ res.writeHead(200, { "Content-Type": "application/json" });
1562
+ res.end(JSON.stringify({ success: true, session }));
1563
+ } catch (e) {
1564
+ serverLogger4.error(`Error parsing session reply request:`, e);
1565
+ res.writeHead(400, { "Content-Type": "application/json" });
1566
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1567
+ }
1568
+ return;
1569
+ }
1570
+ if (pathname.startsWith(`${import_types2.INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(import_types2.INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX) && req.method === "POST") {
1571
+ const sessionId = pathname.slice(
1572
+ import_types2.INSPECTO_API_PATHS.SESSIONS.length + 1,
1573
+ -import_types2.INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX.length
1574
+ );
1575
+ try {
1576
+ const rawBody = await readBody(req);
1577
+ const body = rawBody ? JSON.parse(rawBody) : {};
1578
+ const message = body.message?.trim();
1579
+ const existingSession = annotationSessionStore.getSession(sessionId);
1580
+ if (!existingSession) {
1581
+ res.writeHead(404, { "Content-Type": "application/json" });
1582
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1583
+ return;
1584
+ }
1585
+ if (!message && !hasAgentReply(existingSession)) {
1586
+ res.writeHead(400, { "Content-Type": "application/json" });
1587
+ res.end(
1588
+ JSON.stringify({
1589
+ success: false,
1590
+ error: "Resolve message is required until an agent reply is recorded."
1591
+ })
1592
+ );
1593
+ return;
1594
+ }
1595
+ if (message) {
1596
+ const repliedSession = annotationSessionStore.appendMessage(sessionId, {
1597
+ role: "agent",
1598
+ text: message
1599
+ });
1600
+ if (!repliedSession) {
1601
+ res.writeHead(404, { "Content-Type": "application/json" });
1602
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1603
+ return;
1604
+ }
1605
+ }
1606
+ const session = annotationSessionStore.updateStatus(sessionId, "resolved");
1607
+ if (!session) {
1608
+ res.writeHead(404, { "Content-Type": "application/json" });
1609
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1610
+ return;
1611
+ }
1612
+ res.writeHead(200, { "Content-Type": "application/json" });
1613
+ res.end(JSON.stringify({ success: true, session }));
1614
+ } catch (e) {
1615
+ serverLogger4.error(`Error parsing session resolve request:`, e);
1616
+ res.writeHead(400, { "Content-Type": "application/json" });
1617
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1618
+ }
1619
+ return;
1620
+ }
1621
+ if (pathname.startsWith(`${import_types2.INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(import_types2.INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX) && req.method === "POST") {
1622
+ const sessionId = pathname.slice(
1623
+ import_types2.INSPECTO_API_PATHS.SESSIONS.length + 1,
1624
+ -import_types2.INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX.length
1625
+ );
1626
+ try {
1627
+ const rawBody = await readBody(req);
1628
+ const body = rawBody ? JSON.parse(rawBody) : {};
1629
+ const message = body.message?.trim();
1630
+ const existingSession = annotationSessionStore.getSession(sessionId);
1631
+ if (!existingSession) {
1632
+ res.writeHead(404, { "Content-Type": "application/json" });
1633
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1634
+ return;
1635
+ }
1636
+ if (message) {
1637
+ const repliedSession = annotationSessionStore.appendMessage(sessionId, {
1638
+ role: "agent",
1639
+ text: message
1640
+ });
1641
+ if (!repliedSession) {
1642
+ res.writeHead(404, { "Content-Type": "application/json" });
1643
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1644
+ return;
1645
+ }
1646
+ }
1647
+ const session = annotationSessionStore.updateStatus(sessionId, "dismissed");
1648
+ if (!session) {
1649
+ res.writeHead(404, { "Content-Type": "application/json" });
1650
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1651
+ return;
1652
+ }
1653
+ res.writeHead(200, { "Content-Type": "application/json" });
1654
+ res.end(JSON.stringify({ success: true, session }));
1655
+ } catch (e) {
1656
+ serverLogger4.error(`Error parsing session dismiss request:`, e);
1657
+ res.writeHead(400, { "Content-Type": "application/json" });
1658
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1659
+ }
1660
+ return;
1661
+ }
1224
1662
  if (pathname.startsWith(`${import_types2.INSPECTO_API_PATHS.AI_TICKET}/`) && req.method === "GET") {
1225
1663
  const ticketId = pathname.substring(import_types2.INSPECTO_API_PATHS.AI_TICKET.length + 1);
1226
1664
  const payloadStr = readTicket(ticketId);
@@ -1237,7 +1675,7 @@ async function handleRequest(url, req, res) {
1237
1675
  res.end(JSON.stringify({ error: "not found" }));
1238
1676
  }
1239
1677
  async function dispatchToAi(req) {
1240
- const { location, snippet, prompt, screenshotContext } = req;
1678
+ const { location, snippet, prompt } = req;
1241
1679
  const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
1242
1680
 
1243
1681
  \`\`\`
@@ -1250,8 +1688,7 @@ ${snippet}
1250
1688
  filePath: location.file,
1251
1689
  line: location.line,
1252
1690
  column: location.column,
1253
- snippet,
1254
- ...screenshotContext ? { screenshotContext } : {}
1691
+ snippet
1255
1692
  });
1256
1693
  }
1257
1694
  function getBatchDispatchStatusCode(errorCode, success) {
@@ -1260,6 +1697,21 @@ function getBatchDispatchStatusCode(errorCode, success) {
1260
1697
  if (errorCode === "FORBIDDEN_PATH") return 403;
1261
1698
  return 500;
1262
1699
  }
1700
+ function isAnnotationThreadRole(value) {
1701
+ return value === "user" || value === "agent" || value === "system";
1702
+ }
1703
+ function formatSessionSseEvent(event) {
1704
+ return `event: ${event.type}
1705
+ data: ${JSON.stringify(event)}
1706
+
1707
+ `;
1708
+ }
1709
+ function normalizeSessionClaimTimeout(value) {
1710
+ if (!value?.trim()) return 3e4;
1711
+ const parsed = Number.parseInt(value, 10);
1712
+ if (!Number.isFinite(parsed)) return 3e4;
1713
+ return Math.max(0, Math.min(parsed, 3e5));
1714
+ }
1263
1715
 
1264
1716
  // src/legacy/webpack4/index.ts
1265
1717
  var import_node_path5 = __toESM(require("path"), 1);