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