@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);
@@ -13,16 +13,17 @@ var getDirname = () => path.dirname(getFilename());
13
13
  var __dirname = /* @__PURE__ */ getDirname();
14
14
 
15
15
  // src/injectors/webpack.ts
16
- function getWebpackHtmlScript(serverPort) {
16
+ function getWebpackHtmlScript(serverPort, publicServerUrl) {
17
17
  return `
18
18
  window.__AI_INSPECTOR_PORT__ = ${serverPort};
19
- window.addEventListener('load', () => {
20
- if (window.InspectoClient) {
21
- window.InspectoClient.mountInspector({
22
- serverUrl: 'http://127.0.0.1:' + window.__AI_INSPECTOR_PORT__,
23
- });
24
- }
25
- });
19
+ window.__AI_INSPECTOR_SERVER_URL__ = '${publicServerUrl ?? `http://127.0.0.1:${serverPort}`}';
20
+ window.addEventListener('load', () => {
21
+ if (window.InspectoClient) {
22
+ window.InspectoClient.mountInspector({
23
+ serverUrl: window.__AI_INSPECTOR_SERVER_URL__,
24
+ });
25
+ }
26
+ });
26
27
  `;
27
28
  }
28
29
 
@@ -544,7 +545,6 @@ function dispatchPromptThroughIde(runtime, payload) {
544
545
  line: payload.line,
545
546
  column: payload.column,
546
547
  snippet: payload.snippet,
547
- ...payload.screenshotContext ? { screenshotContext: payload.screenshotContext } : {},
548
548
  overrides: runtime.overrides,
549
549
  autoSend: runtime.autoSend
550
550
  });
@@ -678,6 +678,188 @@ function assertPathWithinIdeOpenScope(file, projectRoot) {
678
678
  }
679
679
  }
680
680
 
681
+ // src/server/session-store.ts
682
+ var DEFAULT_STATUS = "pending";
683
+ function createAnnotationSessionStore(options = {}) {
684
+ const sessions = /* @__PURE__ */ new Map();
685
+ const listeners = /* @__PURE__ */ new Set();
686
+ const now = options.now ?? (() => Date.now());
687
+ const createId = options.createId ?? createRandomId;
688
+ function findNewestMatchingSession(statuses) {
689
+ return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
690
+ }
691
+ function updateSessionStatus(id, status) {
692
+ const session = sessions.get(id);
693
+ if (!session) return null;
694
+ const timestamp = now();
695
+ session.status = status;
696
+ session.updatedAt = timestamp;
697
+ if (status === "acknowledged") {
698
+ session.acknowledgedAt = timestamp;
699
+ }
700
+ if (status === "resolved") {
701
+ session.resolvedAt = timestamp;
702
+ }
703
+ emit({ type: "session-status-updated", session });
704
+ return cloneSession(session);
705
+ }
706
+ function claimSession(id, statuses) {
707
+ const session = sessions.get(id);
708
+ if (!session || statuses && !statuses.has(session.status)) return null;
709
+ if (session.status === "acknowledged") return cloneSession(session);
710
+ return updateSessionStatus(id, "acknowledged");
711
+ }
712
+ function emit(event) {
713
+ const snapshot = cloneSession(event.session);
714
+ for (const listener of listeners) {
715
+ listener({ type: event.type, session: snapshot });
716
+ }
717
+ }
718
+ const store = {
719
+ createSession(input) {
720
+ const timestamp = now();
721
+ const session = {
722
+ id: createId(),
723
+ instruction: input.instruction?.trim() ?? "",
724
+ annotations: cloneArray(input.annotations),
725
+ ...input.deliveryMode ? { deliveryMode: input.deliveryMode } : {},
726
+ status: DEFAULT_STATUS,
727
+ messages: cloneArray(input.messages ?? []),
728
+ createdAt: timestamp,
729
+ updatedAt: timestamp,
730
+ ...input.runtimeContext ? { runtimeContext: cloneValue(input.runtimeContext) } : {},
731
+ ...input.cssContextPrompt?.trim() ? { cssContextPrompt: input.cssContextPrompt.trim() } : {},
732
+ ...input.pageUrl ? { pageUrl: input.pageUrl } : {},
733
+ ...input.route ? { route: input.route } : {}
734
+ };
735
+ sessions.set(session.id, session);
736
+ emit({ type: "session-created", session });
737
+ return cloneSession(session);
738
+ },
739
+ getSession(id) {
740
+ const session = sessions.get(id);
741
+ return session ? cloneSession(session) : null;
742
+ },
743
+ listSessions(options2 = {}) {
744
+ const statuses = normalizeStatuses(options2.status);
745
+ return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt).map((session) => cloneSession(session));
746
+ },
747
+ async claimNextSession(options2 = {}) {
748
+ const statuses = normalizeStatuses(DEFAULT_STATUS);
749
+ const existingSession = findNewestMatchingSession(statuses);
750
+ if (existingSession) {
751
+ return {
752
+ session: claimSession(existingSession.id, statuses),
753
+ timedOut: false,
754
+ matchedExisting: true
755
+ };
756
+ }
757
+ const timeoutMs = normalizeTimeoutMs(options2.timeoutMs);
758
+ if (timeoutMs === 0) {
759
+ return {
760
+ session: null,
761
+ timedOut: true,
762
+ matchedExisting: false
763
+ };
764
+ }
765
+ return await new Promise((resolve2) => {
766
+ let settled = false;
767
+ let timeout = null;
768
+ const finish = (result) => {
769
+ if (settled) return;
770
+ settled = true;
771
+ unsubscribe();
772
+ if (timeout) {
773
+ clearTimeout(timeout);
774
+ }
775
+ resolve2(result);
776
+ };
777
+ const unsubscribe = this.subscribe((event) => {
778
+ const session = claimSession(event.session.id, statuses);
779
+ if (!session) return;
780
+ finish({
781
+ session,
782
+ timedOut: false,
783
+ matchedExisting: false,
784
+ event: event.type
785
+ });
786
+ });
787
+ if (timeoutMs !== null) {
788
+ timeout = setTimeout(() => {
789
+ finish({
790
+ session: null,
791
+ timedOut: true,
792
+ matchedExisting: false
793
+ });
794
+ }, timeoutMs);
795
+ }
796
+ });
797
+ },
798
+ appendMessage(id, input) {
799
+ const session = sessions.get(id);
800
+ if (!session) return null;
801
+ const timestamp = now();
802
+ session.messages.push({
803
+ id: createId(),
804
+ role: input.role,
805
+ text: input.text,
806
+ createdAt: timestamp
807
+ });
808
+ session.updatedAt = timestamp;
809
+ if (input.role === "agent" && isPendingLikeStatus(session.status)) {
810
+ session.status = "in_progress";
811
+ }
812
+ emit({ type: "session-message-appended", session });
813
+ return cloneSession(session);
814
+ },
815
+ updateStatus(id, status) {
816
+ return updateSessionStatus(id, status);
817
+ },
818
+ subscribe(listener) {
819
+ listeners.add(listener);
820
+ return () => {
821
+ listeners.delete(listener);
822
+ };
823
+ },
824
+ clear() {
825
+ sessions.clear();
826
+ listeners.clear();
827
+ }
828
+ };
829
+ return store;
830
+ }
831
+ var annotationSessionStore = createAnnotationSessionStore();
832
+ function normalizeStatuses(status) {
833
+ if (!status) return null;
834
+ return new Set(Array.isArray(status) ? status : [status]);
835
+ }
836
+ function normalizeTimeoutMs(value) {
837
+ if (value === void 0) return null;
838
+ if (!Number.isFinite(value)) return 0;
839
+ return Math.max(0, Math.floor(value));
840
+ }
841
+ function isPendingLikeStatus(status) {
842
+ return status === "pending" || status === "acknowledged";
843
+ }
844
+ function hasAgentReply(session) {
845
+ return session.messages.some((message) => message.role === "agent" && Boolean(message.text?.trim()));
846
+ }
847
+ function createRandomId() {
848
+ return `annotation-session-${Math.random().toString(36).slice(2, 10)}`;
849
+ }
850
+ function cloneSession(session) {
851
+ return cloneValue(session);
852
+ }
853
+ function cloneArray(value) {
854
+ return cloneValue(value);
855
+ }
856
+ function cloneValue(value) {
857
+ if (typeof structuredClone === "function") {
858
+ return structuredClone(value);
859
+ }
860
+ return JSON.parse(JSON.stringify(value));
861
+ }
862
+
681
863
  // src/server/annotation-dispatch.ts
682
864
  var AnnotationDispatchError = class extends Error {
683
865
  constructor(message, errorCode) {
@@ -686,20 +868,30 @@ var AnnotationDispatchError = class extends Error {
686
868
  this.errorCode = errorCode;
687
869
  }
688
870
  };
689
- async function dispatchAnnotationsToAi(req, state) {
871
+ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStore) {
690
872
  try {
691
873
  validateAnnotationDispatchRequest(req, state);
692
874
  const batch = normalizeAnnotationBatch(req);
693
875
  const prompt = buildAnnotationBatchPrompt(batch);
876
+ const deliveryMode = normalizeDeliveryMode(req.deliveryMode);
877
+ const session = store.createSession({
878
+ instruction: batch.instruction,
879
+ annotations: toSessionAnnotations(batch.annotations),
880
+ deliveryMode,
881
+ ...batch.runtimeContext ? { runtimeContext: batch.runtimeContext } : {},
882
+ ...batch.cssContextPrompt ? { cssContextPrompt: batch.cssContextPrompt } : {}
883
+ });
694
884
  const representativeTarget = batch.annotations[0]?.targets[0];
695
- const runtime = resolvePromptDispatchRuntime(state);
696
- return dispatchPromptThroughIde(runtime, {
885
+ const dispatchResult = deliveryMode === "ide" ? dispatchPromptThroughIde(resolvePromptDispatchRuntime(state), {
697
886
  prompt,
698
887
  ...representativeTarget?.file ? { filePath: representativeTarget.file } : {},
699
888
  ...representativeTarget?.line ? { line: representativeTarget.line } : {},
700
- ...representativeTarget?.column ? { column: representativeTarget.column } : {},
701
- ...batch.screenshotContext ? { screenshotContext: batch.screenshotContext } : {}
702
- });
889
+ ...representativeTarget?.column ? { column: representativeTarget.column } : {}
890
+ }) : { success: true };
891
+ return {
892
+ ...dispatchResult,
893
+ session: toSessionSummary(session)
894
+ };
703
895
  } catch (error) {
704
896
  return {
705
897
  success: false,
@@ -708,6 +900,41 @@ async function dispatchAnnotationsToAi(req, state) {
708
900
  };
709
901
  }
710
902
  }
903
+ function normalizeDeliveryMode(input) {
904
+ return input === "agent" ? "agent" : "ide";
905
+ }
906
+ function toSessionAnnotations(annotations) {
907
+ return annotations.map((annotation) => ({
908
+ id: `annotation-${annotation.index}`,
909
+ note: annotation.note,
910
+ intent: annotation.intent,
911
+ targets: annotation.targets.map((target, targetIndex) => ({
912
+ id: `annotation-${annotation.index}-target-${targetIndex + 1}`,
913
+ label: target.label ?? "Unknown target",
914
+ location: {
915
+ file: target.file,
916
+ line: target.line,
917
+ column: target.column
918
+ },
919
+ ...target.selector ? { selector: target.selector } : {},
920
+ ...target.snippet ? { snippet: target.snippet } : {},
921
+ rect: {
922
+ x: 0,
923
+ y: 0,
924
+ width: 0,
925
+ height: 0
926
+ }
927
+ }))
928
+ }));
929
+ }
930
+ function toSessionSummary(session) {
931
+ return {
932
+ id: session.id,
933
+ status: session.status,
934
+ createdAt: session.createdAt,
935
+ updatedAt: session.updatedAt
936
+ };
937
+ }
711
938
  function validateAnnotationDispatchRequest(req, state) {
712
939
  if (!req.annotations.length) {
713
940
  throw new AnnotationDispatchError("At least one annotation is required.", "INVALID_REQUEST");
@@ -728,9 +955,7 @@ function validateAnnotationDispatchRequest(req, state) {
728
955
  function normalizeAnnotationBatch(req) {
729
956
  return {
730
957
  instruction: req.instruction?.trim() ?? "",
731
- responseMode: req.responseMode ?? "unified",
732
958
  ...req.runtimeContext ? { runtimeContext: req.runtimeContext } : {},
733
- ...req.screenshotContext ? { screenshotContext: req.screenshotContext } : {},
734
959
  ...req.cssContextPrompt?.trim() ? { cssContextPrompt: req.cssContextPrompt.trim() } : {},
735
960
  annotations: req.annotations.map((annotation, index) => ({
736
961
  index: index + 1,
@@ -752,12 +977,9 @@ function buildAnnotationBatchPrompt(batch) {
752
977
  const prompt = batch.instruction ? `${batch.instruction}
753
978
 
754
979
  ${body}` : body;
755
- return appendScreenshotContextSection(
756
- appendCssContextSection(
757
- appendRuntimeContextSection(prompt, batch.runtimeContext),
758
- batch.cssContextPrompt
759
- ),
760
- batch.screenshotContext
980
+ return appendCssContextSection(
981
+ appendRuntimeContextSection(prompt, batch.runtimeContext),
982
+ batch.cssContextPrompt
761
983
  );
762
984
  }
763
985
  function appendCssContextSection(prompt, cssContextPrompt) {
@@ -784,20 +1006,6 @@ function buildSelectedElementsPrompt(annotations) {
784
1006
  }
785
1007
  return lines.join("\n");
786
1008
  }
787
- function appendScreenshotContextSection(prompt, screenshotContext) {
788
- if (!screenshotContext || !screenshotContext.imageDataUrl && !screenshotContext.imageAssetId) {
789
- return prompt;
790
- }
791
- const lines = [
792
- "Visual screenshot context attached:",
793
- `- capturedAt=${screenshotContext.capturedAt}`,
794
- `- mimeType=${screenshotContext.mimeType}`,
795
- ...screenshotContext.imageAssetId ? [`- imageAssetId=${screenshotContext.imageAssetId}`] : []
796
- ];
797
- return `${prompt}
798
-
799
- ${lines.join("\n")}`;
800
- }
801
1009
  function appendRuntimeContextSection(prompt, runtimeContext) {
802
1010
  if (!runtimeContext?.records.length) {
803
1011
  return prompt;
@@ -845,7 +1053,7 @@ async function buildClientConfig(serverState2) {
845
1053
  ...info,
846
1054
  prompts: resolveIntents(promptsConfig),
847
1055
  hotKeys: userConfig["inspector.hotKey"] ?? "alt",
848
- theme: userConfig["inspector.theme"] ?? "auto",
1056
+ annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "both",
849
1057
  includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
850
1058
  runtimeContext: {
851
1059
  enabled: true,
@@ -853,10 +1061,6 @@ async function buildClientConfig(serverState2) {
853
1061
  maxRuntimeErrors: 3,
854
1062
  maxFailedRequests: 2
855
1063
  },
856
- screenshotContext: {
857
- enabled: false
858
- },
859
- annotationResponseMode: userConfig["prompt.annotationResponseMode"] ?? "unified",
860
1064
  autoSend: userConfig["prompt.autoSend"] ?? false
861
1065
  };
862
1066
  }
@@ -901,7 +1105,7 @@ function handleOpenFileRequest(body, serverState2) {
901
1105
  else if (rawEditorHint === "vscodium") editorHint = "codium";
902
1106
  else if (rawEditorHint === "trae-cn" || rawEditorHint === "trae") editorHint = "trae";
903
1107
  serverLogger2.debug(
904
- `IDE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
1108
+ `SOURCE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
905
1109
  );
906
1110
  if (VSCODE_FAMILY_SCHEMES.includes(rawEditorHint)) {
907
1111
  let normalizedPath = absolutePath.replace(/\\/g, "/");
@@ -910,7 +1114,7 @@ function handleOpenFileRequest(body, serverState2) {
910
1114
  }
911
1115
  const encodedPath = encodeURI(normalizedPath);
912
1116
  const uri = `${rawEditorHint}://file${encodedPath}:${body.line}:${body.column}`;
913
- serverLogger2.debug(`IDE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
1117
+ serverLogger2.debug(`SOURCE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
914
1118
  try {
915
1119
  if (process.platform === "darwin") {
916
1120
  execFileSync2("open", [uri]);
@@ -920,7 +1124,7 @@ function handleOpenFileRequest(body, serverState2) {
920
1124
  execFileSync2("xdg-open", [uri]);
921
1125
  }
922
1126
  } catch (e) {
923
- serverLogger2.error(`Failed to launch URI for IDE_OPEN (${uri}):`, e);
1127
+ serverLogger2.error(`Failed to launch URI for SOURCE_OPEN (${uri}):`, e);
924
1128
  launchIDE2({
925
1129
  file: absolutePath,
926
1130
  line: body.line,
@@ -975,8 +1179,18 @@ function resolveProjectRoot() {
975
1179
  return gitRoot;
976
1180
  }
977
1181
 
1182
+ // src/server/server-url.ts
1183
+ function resolveServerHost(cwd, configRoot) {
1184
+ const userConfig = loadUserConfigSync(false, cwd, configRoot);
1185
+ const configuredHost = userConfig["server.host"]?.trim();
1186
+ if (configuredHost) return configuredHost;
1187
+ if (process.env["VITEST"]) return "127.0.0.1";
1188
+ return "127.0.0.1";
1189
+ }
1190
+
978
1191
  // src/server/index.ts
979
1192
  var serverLogger4 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
1193
+ var PORT_FILE_NAME = "inspecto.port.json";
980
1194
  var serverState = {
981
1195
  port: null,
982
1196
  running: false,
@@ -985,6 +1199,42 @@ var serverState = {
985
1199
  cwd: process.cwd()
986
1200
  };
987
1201
  var serverInstance = null;
1202
+ function getPortFilePath() {
1203
+ return path6.join(os2.tmpdir(), PORT_FILE_NAME);
1204
+ }
1205
+ function getProjectRootHash() {
1206
+ if (!serverState.projectRoot) return null;
1207
+ return crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1208
+ }
1209
+ function readPortData(portFile) {
1210
+ if (!fs5.existsSync(portFile)) return {};
1211
+ try {
1212
+ return JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1213
+ } catch {
1214
+ return {};
1215
+ }
1216
+ }
1217
+ function writeProjectPort(port) {
1218
+ const rootHash = getProjectRootHash();
1219
+ if (!rootHash) return;
1220
+ const portFile = getPortFilePath();
1221
+ const portData = readPortData(portFile);
1222
+ portData[rootHash] = port;
1223
+ fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1224
+ }
1225
+ function removeProjectPort() {
1226
+ const rootHash = getProjectRootHash();
1227
+ if (!rootHash) return;
1228
+ const portFile = getPortFilePath();
1229
+ if (!fs5.existsSync(portFile)) return;
1230
+ const portData = readPortData(portFile);
1231
+ delete portData[rootHash];
1232
+ if (Object.keys(portData).length === 0) {
1233
+ fs5.unlinkSync(portFile);
1234
+ } else {
1235
+ fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1236
+ }
1237
+ }
988
1238
  async function startServer() {
989
1239
  if (serverState.running && serverState.port !== null) {
990
1240
  return serverState.port;
@@ -992,6 +1242,7 @@ async function startServer() {
992
1242
  serverState.projectRoot = resolveProjectRoot();
993
1243
  serverState.configRoot = serverState.projectRoot;
994
1244
  serverState.cwd = process.cwd();
1245
+ const serverHost = resolveServerHost(serverState.cwd, serverState.configRoot);
995
1246
  portfinder.basePort = 5678;
996
1247
  const port = await portfinder.getPortPromise();
997
1248
  watchConfig(
@@ -1018,7 +1269,7 @@ async function startServer() {
1018
1269
  });
1019
1270
  });
1020
1271
  await new Promise((resolve2, reject) => {
1021
- serverInstance.listen(port, "127.0.0.1", () => {
1272
+ serverInstance.listen(port, serverHost, () => {
1022
1273
  serverInstance.unref();
1023
1274
  resolve2();
1024
1275
  });
@@ -1029,37 +1280,18 @@ async function startServer() {
1029
1280
  });
1030
1281
  serverState.port = port;
1031
1282
  serverState.running = true;
1032
- const portFile = path6.join(os2.tmpdir(), "inspecto.port.json");
1033
1283
  try {
1034
- let portData = {};
1035
- if (fs5.existsSync(portFile)) {
1036
- try {
1037
- portData = JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1038
- } catch (_e) {
1039
- }
1040
- }
1041
- const rootHash = crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1042
- portData[rootHash] = port;
1043
- fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1284
+ writeProjectPort(port);
1044
1285
  } catch (_e) {
1045
1286
  serverLogger4.warn("Failed to write port file:", _e);
1046
1287
  }
1047
1288
  process.once("exit", () => {
1048
1289
  try {
1049
- if (fs5.existsSync(portFile)) {
1050
- const portData = JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1051
- const rootHash = crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1052
- delete portData[rootHash];
1053
- if (Object.keys(portData).length === 0) {
1054
- fs5.unlinkSync(portFile);
1055
- } else {
1056
- fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1057
- }
1058
- }
1290
+ removeProjectPort();
1059
1291
  } catch {
1060
1292
  }
1061
1293
  });
1062
- serverLogger4.info(`server running at http://127.0.0.1:${port}`);
1294
+ serverLogger4.info(`server running at http://${serverHost}:${port}`);
1063
1295
  return port;
1064
1296
  }
1065
1297
  async function readBody(req) {
@@ -1111,7 +1343,7 @@ async function handleRequest(url, req, res) {
1111
1343
  }
1112
1344
  return;
1113
1345
  }
1114
- if (pathname === INSPECTO_API_PATHS.IDE_OPEN && req.method === "POST") {
1346
+ if ((pathname === INSPECTO_API_PATHS.SOURCE_OPEN || pathname === INSPECTO_API_PATHS.IDE_OPEN) && req.method === "POST") {
1115
1347
  let body;
1116
1348
  try {
1117
1349
  body = JSON.parse(await readBody(req));
@@ -1124,7 +1356,7 @@ async function handleRequest(url, req, res) {
1124
1356
  handleOpenFileRequest(body, serverState);
1125
1357
  } catch (err) {
1126
1358
  serverLogger4.warn(
1127
- `Security: Blocked path traversal attempt in IDE_OPEN: ${body.file}. Reason: ${err.message}`
1359
+ `Security: Blocked path traversal attempt in SOURCE_OPEN: ${body.file}. Reason: ${err.message}`
1128
1360
  );
1129
1361
  res.writeHead(403, { "Content-Type": "application/json" });
1130
1362
  res.end(JSON.stringify({ error: "Access denied: File is outside of project workspace" }));
@@ -1198,6 +1430,212 @@ async function handleRequest(url, req, res) {
1198
1430
  }
1199
1431
  return;
1200
1432
  }
1433
+ if (pathname === INSPECTO_API_PATHS.SESSION_CLAIM_NEXT && req.method === "POST") {
1434
+ try {
1435
+ const rawBody = await readBody(req);
1436
+ const body = rawBody ? JSON.parse(rawBody) : {};
1437
+ const timeoutMs = normalizeSessionClaimTimeout(
1438
+ body.timeoutMs === void 0 ? null : String(body.timeoutMs)
1439
+ );
1440
+ const result = await annotationSessionStore.claimNextSession({
1441
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
1442
+ });
1443
+ res.writeHead(200, { "Content-Type": "application/json" });
1444
+ res.end(
1445
+ JSON.stringify({
1446
+ success: true,
1447
+ timedOut: result.timedOut,
1448
+ matchedExisting: result.matchedExisting,
1449
+ ...result.event ? { event: result.event } : {},
1450
+ ...result.session ? { session: result.session } : {}
1451
+ })
1452
+ );
1453
+ } catch (e) {
1454
+ serverLogger4.error(`Error parsing session claim request:`, e);
1455
+ res.writeHead(400, { "Content-Type": "application/json" });
1456
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1457
+ }
1458
+ return;
1459
+ }
1460
+ if (pathname === INSPECTO_API_PATHS.SESSION_EVENTS && req.method === "GET") {
1461
+ const statusParam = url.searchParams.getAll("status");
1462
+ const statuses = statusParam.length ? new Set(statusParam) : null;
1463
+ const sessionId = url.searchParams.get("sessionId")?.trim() || null;
1464
+ res.writeHead(200, {
1465
+ "Content-Type": "text/event-stream",
1466
+ "Cache-Control": "no-cache",
1467
+ Connection: "keep-alive"
1468
+ });
1469
+ res.write(`event: ready
1470
+ data: ${JSON.stringify({ ok: true })}
1471
+
1472
+ `);
1473
+ const unsubscribe = annotationSessionStore.subscribe((event) => {
1474
+ if (sessionId && event.session.id !== sessionId) {
1475
+ return;
1476
+ }
1477
+ if (statuses && !statuses.has(event.session.status)) {
1478
+ return;
1479
+ }
1480
+ res.write(formatSessionSseEvent(event));
1481
+ });
1482
+ req.on("close", () => {
1483
+ unsubscribe();
1484
+ res.end();
1485
+ });
1486
+ return;
1487
+ }
1488
+ if (pathname === INSPECTO_API_PATHS.SESSIONS && req.method === "GET") {
1489
+ const statusParam = url.searchParams.getAll("status");
1490
+ const sessions = annotationSessionStore.listSessions(
1491
+ statusParam.length ? {
1492
+ status: statusParam
1493
+ } : void 0
1494
+ );
1495
+ res.writeHead(200, { "Content-Type": "application/json" });
1496
+ res.end(JSON.stringify({ success: true, sessions }));
1497
+ return;
1498
+ }
1499
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && req.method === "GET") {
1500
+ const sessionId = pathname.substring(INSPECTO_API_PATHS.SESSIONS.length + 1);
1501
+ const session = annotationSessionStore.getSession(sessionId);
1502
+ if (!session) {
1503
+ res.writeHead(404, { "Content-Type": "application/json" });
1504
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1505
+ return;
1506
+ }
1507
+ res.writeHead(200, { "Content-Type": "application/json" });
1508
+ res.end(JSON.stringify({ success: true, session }));
1509
+ return;
1510
+ }
1511
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX) && req.method === "POST") {
1512
+ const sessionId = pathname.slice(
1513
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
1514
+ -INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX.length
1515
+ );
1516
+ try {
1517
+ const rawBody = await readBody(req);
1518
+ const body = JSON.parse(rawBody);
1519
+ if (!isAnnotationThreadRole(body.role)) {
1520
+ res.writeHead(400, { "Content-Type": "application/json" });
1521
+ res.end(JSON.stringify({ success: false, error: "Reply role is invalid." }));
1522
+ return;
1523
+ }
1524
+ if (!body.text?.trim()) {
1525
+ res.writeHead(400, { "Content-Type": "application/json" });
1526
+ res.end(JSON.stringify({ success: false, error: "Reply text is required." }));
1527
+ return;
1528
+ }
1529
+ const session = annotationSessionStore.appendMessage(sessionId, {
1530
+ role: body.role,
1531
+ text: body.text.trim()
1532
+ });
1533
+ if (!session) {
1534
+ res.writeHead(404, { "Content-Type": "application/json" });
1535
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1536
+ return;
1537
+ }
1538
+ res.writeHead(200, { "Content-Type": "application/json" });
1539
+ res.end(JSON.stringify({ success: true, session }));
1540
+ } catch (e) {
1541
+ serverLogger4.error(`Error parsing session reply request:`, e);
1542
+ res.writeHead(400, { "Content-Type": "application/json" });
1543
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1544
+ }
1545
+ return;
1546
+ }
1547
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX) && req.method === "POST") {
1548
+ const sessionId = pathname.slice(
1549
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
1550
+ -INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX.length
1551
+ );
1552
+ try {
1553
+ const rawBody = await readBody(req);
1554
+ const body = rawBody ? JSON.parse(rawBody) : {};
1555
+ const message = body.message?.trim();
1556
+ const existingSession = annotationSessionStore.getSession(sessionId);
1557
+ if (!existingSession) {
1558
+ res.writeHead(404, { "Content-Type": "application/json" });
1559
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1560
+ return;
1561
+ }
1562
+ if (!message && !hasAgentReply(existingSession)) {
1563
+ res.writeHead(400, { "Content-Type": "application/json" });
1564
+ res.end(
1565
+ JSON.stringify({
1566
+ success: false,
1567
+ error: "Resolve message is required until an agent reply is recorded."
1568
+ })
1569
+ );
1570
+ return;
1571
+ }
1572
+ if (message) {
1573
+ const repliedSession = annotationSessionStore.appendMessage(sessionId, {
1574
+ role: "agent",
1575
+ text: message
1576
+ });
1577
+ if (!repliedSession) {
1578
+ res.writeHead(404, { "Content-Type": "application/json" });
1579
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1580
+ return;
1581
+ }
1582
+ }
1583
+ const session = annotationSessionStore.updateStatus(sessionId, "resolved");
1584
+ if (!session) {
1585
+ res.writeHead(404, { "Content-Type": "application/json" });
1586
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1587
+ return;
1588
+ }
1589
+ res.writeHead(200, { "Content-Type": "application/json" });
1590
+ res.end(JSON.stringify({ success: true, session }));
1591
+ } catch (e) {
1592
+ serverLogger4.error(`Error parsing session resolve request:`, e);
1593
+ res.writeHead(400, { "Content-Type": "application/json" });
1594
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1595
+ }
1596
+ return;
1597
+ }
1598
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX) && req.method === "POST") {
1599
+ const sessionId = pathname.slice(
1600
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
1601
+ -INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX.length
1602
+ );
1603
+ try {
1604
+ const rawBody = await readBody(req);
1605
+ const body = rawBody ? JSON.parse(rawBody) : {};
1606
+ const message = body.message?.trim();
1607
+ const existingSession = annotationSessionStore.getSession(sessionId);
1608
+ if (!existingSession) {
1609
+ res.writeHead(404, { "Content-Type": "application/json" });
1610
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1611
+ return;
1612
+ }
1613
+ if (message) {
1614
+ const repliedSession = annotationSessionStore.appendMessage(sessionId, {
1615
+ role: "agent",
1616
+ text: message
1617
+ });
1618
+ if (!repliedSession) {
1619
+ res.writeHead(404, { "Content-Type": "application/json" });
1620
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1621
+ return;
1622
+ }
1623
+ }
1624
+ const session = annotationSessionStore.updateStatus(sessionId, "dismissed");
1625
+ if (!session) {
1626
+ res.writeHead(404, { "Content-Type": "application/json" });
1627
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
1628
+ return;
1629
+ }
1630
+ res.writeHead(200, { "Content-Type": "application/json" });
1631
+ res.end(JSON.stringify({ success: true, session }));
1632
+ } catch (e) {
1633
+ serverLogger4.error(`Error parsing session dismiss request:`, e);
1634
+ res.writeHead(400, { "Content-Type": "application/json" });
1635
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
1636
+ }
1637
+ return;
1638
+ }
1201
1639
  if (pathname.startsWith(`${INSPECTO_API_PATHS.AI_TICKET}/`) && req.method === "GET") {
1202
1640
  const ticketId = pathname.substring(INSPECTO_API_PATHS.AI_TICKET.length + 1);
1203
1641
  const payloadStr = readTicket(ticketId);
@@ -1214,7 +1652,7 @@ async function handleRequest(url, req, res) {
1214
1652
  res.end(JSON.stringify({ error: "not found" }));
1215
1653
  }
1216
1654
  async function dispatchToAi(req) {
1217
- const { location, snippet, prompt, screenshotContext } = req;
1655
+ const { location, snippet, prompt } = req;
1218
1656
  const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
1219
1657
 
1220
1658
  \`\`\`
@@ -1227,8 +1665,7 @@ ${snippet}
1227
1665
  filePath: location.file,
1228
1666
  line: location.line,
1229
1667
  column: location.column,
1230
- snippet,
1231
- ...screenshotContext ? { screenshotContext } : {}
1668
+ snippet
1232
1669
  });
1233
1670
  }
1234
1671
  function getBatchDispatchStatusCode(errorCode, success) {
@@ -1237,6 +1674,21 @@ function getBatchDispatchStatusCode(errorCode, success) {
1237
1674
  if (errorCode === "FORBIDDEN_PATH") return 403;
1238
1675
  return 500;
1239
1676
  }
1677
+ function isAnnotationThreadRole(value) {
1678
+ return value === "user" || value === "agent" || value === "system";
1679
+ }
1680
+ function formatSessionSseEvent(event) {
1681
+ return `event: ${event.type}
1682
+ data: ${JSON.stringify(event)}
1683
+
1684
+ `;
1685
+ }
1686
+ function normalizeSessionClaimTimeout(value) {
1687
+ if (!value?.trim()) return 3e4;
1688
+ const parsed = Number.parseInt(value, 10);
1689
+ if (!Number.isFinite(parsed)) return 3e4;
1690
+ return Math.max(0, Math.min(parsed, 3e5));
1691
+ }
1240
1692
 
1241
1693
  // src/legacy/webpack4/index.ts
1242
1694
  import path7 from "path";