@inspecto-dev/plugin 0.2.0-alpha.4 → 0.3.0

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.
@@ -42,13 +42,11 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
42
42
 
43
43
  // src/server/index.ts
44
44
  var import_node_http = __toESM(require("http"), 1);
45
- var import_node_fs2 = __toESM(require("fs"), 1);
46
- var import_node_path2 = __toESM(require("path"), 1);
45
+ var import_node_fs3 = __toESM(require("fs"), 1);
46
+ var import_node_path4 = __toESM(require("path"), 1);
47
47
  var import_node_os2 = __toESM(require("os"), 1);
48
- var import_node_crypto = __toESM(require("crypto"), 1);
49
- var import_node_child_process = require("child_process");
48
+ var import_node_crypto2 = __toESM(require("crypto"), 1);
50
49
  var import_portfinder = __toESM(require("portfinder"), 1);
51
- var import_launch_ide = require("launch-ide");
52
50
  var import_types2 = require("@inspecto-dev/types");
53
51
 
54
52
  // src/server/snippet.ts
@@ -368,9 +366,9 @@ function extractToolOverrides(ide, config) {
368
366
  function resolveIntents(serverPrompts) {
369
367
  const baseMap = /* @__PURE__ */ new Map();
370
368
  for (const intent of import_types.DEFAULT_INTENTS) {
371
- if (intent.id) baseMap.set(intent.id, { ...intent });
369
+ baseMap.set(intent.id, { ...intent });
372
370
  }
373
- const defaults = () => ensureOpenInEditorLast(Array.from(baseMap.values()));
371
+ const defaults = () => Array.from(baseMap.values());
374
372
  if (!serverPrompts) return defaults();
375
373
  const isReplace = !Array.isArray(serverPrompts) && typeof serverPrompts === "object" && serverPrompts.$replace === true;
376
374
  const promptsArray = Array.isArray(serverPrompts) ? serverPrompts : isReplace ? serverPrompts.items : [];
@@ -397,16 +395,18 @@ function resolveIntents(serverPrompts) {
397
395
  );
398
396
  continue;
399
397
  }
400
- if (item.isAction && item.id !== "open-in-editor") {
398
+ if (!item.aiIntent) {
401
399
  configLogger.warn(
402
- `isAction is reserved for built-in actions. Ignoring intent "${item.id}".`
400
+ `Intent "${item.id}" is missing required "aiIntent".`
403
401
  );
404
402
  continue;
405
403
  }
406
- result.push(baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item);
404
+ result.push(
405
+ baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item
406
+ );
407
407
  }
408
408
  }
409
- return ensureOpenInEditorLast(result);
409
+ return result;
410
410
  }
411
411
  const merged = Array.from(baseMap.values());
412
412
  for (const item of promptsArray) {
@@ -423,9 +423,9 @@ function resolveIntents(serverPrompts) {
423
423
  configLogger.warn('Intent object missing required "id" field, skipping.');
424
424
  continue;
425
425
  }
426
- if (item.isAction && item.id !== "open-in-editor") {
426
+ if (!item.aiIntent) {
427
427
  configLogger.warn(
428
- `isAction is reserved for built-in actions. Ignoring intent "${item.id}".`
428
+ `Intent "${item.id}" is missing required "aiIntent".`
429
429
  );
430
430
  continue;
431
431
  }
@@ -443,15 +443,7 @@ function resolveIntents(serverPrompts) {
443
443
  }
444
444
  }
445
445
  }
446
- return ensureOpenInEditorLast(merged);
447
- }
448
- function ensureOpenInEditorLast(intents) {
449
- const idx = intents.findIndex((i) => i.id === "open-in-editor");
450
- if (idx === -1 || idx === intents.length - 1) return intents;
451
- const result = [...intents];
452
- const item = result.splice(idx, 1)[0];
453
- result.push(item);
454
- return result;
446
+ return merged;
455
447
  }
456
448
  var watchers = [];
457
449
  function watchConfig(onReload, cwd = process.cwd(), gitRoot) {
@@ -486,7 +478,10 @@ function watchConfig(onReload, cwd = process.cwd(), gitRoot) {
486
478
  }
487
479
  }
488
480
 
489
- // src/server/index.ts
481
+ // src/server/dispatch-transport.ts
482
+ var import_node_crypto = __toESM(require("crypto"), 1);
483
+ var import_node_child_process = require("child_process");
484
+ var import_launch_ide = require("launch-ide");
490
485
  var serverLogger = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
491
486
  var payloadTickets = /* @__PURE__ */ new Map();
492
487
  function createTicket(payload) {
@@ -500,21 +495,363 @@ function createTicket(payload) {
500
495
  );
501
496
  return ticketId;
502
497
  }
503
- var serverState = {
504
- port: null,
505
- running: false,
506
- projectRoot: "",
507
- configRoot: "",
508
- cwd: process.cwd()
498
+ function readTicket(ticketId) {
499
+ return payloadTickets.get(ticketId);
500
+ }
501
+ function launchURI(uri) {
502
+ try {
503
+ if (process.platform === "darwin") {
504
+ (0, import_node_child_process.execFileSync)("open", [uri]);
505
+ } else if (process.platform === "win32") {
506
+ (0, import_node_child_process.execFileSync)("cmd", ["/c", "start", '""', uri]);
507
+ } else {
508
+ (0, import_node_child_process.execFileSync)("xdg-open", [uri]);
509
+ }
510
+ } catch (e) {
511
+ serverLogger.error("Failed to launch URI via execFileSync, falling back to launchIDE:", e);
512
+ (0, import_launch_ide.launchIDE)({ file: uri });
513
+ }
514
+ }
515
+
516
+ // src/server/dispatch-runtime.ts
517
+ function resolvePromptDispatchRuntime(state) {
518
+ const userConfig = loadUserConfigSync(false, state.cwd, state.projectRoot);
519
+ const resolvedTarget = resolveTargetTool(userConfig);
520
+ const finalIde = resolveFinalIde(userConfig.ide, state.ideInfo?.ide, state.ideInfo?.scheme);
521
+ const mode = resolveProviderMode(resolvedTarget, finalIde, userConfig);
522
+ const overrides = extractToolOverrides(finalIde, userConfig)[resolvedTarget] || void 0;
523
+ return {
524
+ resolvedTarget,
525
+ finalIde,
526
+ mode,
527
+ ...hasOverrides(overrides) ? { overrides } : {},
528
+ ...userConfig["prompt.autoSend"] !== void 0 ? { autoSend: Boolean(userConfig["prompt.autoSend"]) } : {}
529
+ };
530
+ }
531
+ function dispatchPromptThroughIde(runtime, payload) {
532
+ const ticketId = createTicket({
533
+ ide: runtime.finalIde,
534
+ target: runtime.resolvedTarget,
535
+ targetType: runtime.mode,
536
+ prompt: payload.prompt,
537
+ filePath: payload.filePath,
538
+ line: payload.line,
539
+ column: payload.column,
540
+ snippet: payload.snippet,
541
+ ...payload.screenshotContext ? { screenshotContext: payload.screenshotContext } : {},
542
+ overrides: runtime.overrides,
543
+ autoSend: runtime.autoSend
544
+ });
545
+ const params = new URLSearchParams();
546
+ params.set("ticket", ticketId);
547
+ params.set("target", runtime.resolvedTarget);
548
+ launchURI(`${runtime.finalIde}://inspecto.inspecto/send?${params.toString()}`);
549
+ return {
550
+ success: true,
551
+ fallbackPayload: {
552
+ prompt: payload.prompt,
553
+ ...payload.filePath ? { file: payload.filePath } : {}
554
+ }
555
+ };
556
+ }
557
+ function resolveFinalIde(configuredIde, activeIde, activeIdeScheme) {
558
+ if (configuredIde && activeIdeScheme && !activeIdeScheme.includes(configuredIde)) {
559
+ return configuredIde;
560
+ }
561
+ return configuredIde || activeIdeScheme || activeIde || "vscode";
562
+ }
563
+ function hasOverrides(overrides) {
564
+ return Boolean(overrides && Object.keys(overrides).length > 0);
565
+ }
566
+
567
+ // src/server/path-guards.ts
568
+ var import_node_path2 = __toESM(require("path"), 1);
569
+ function isWindowsAbsolutePath(file) {
570
+ return /^[a-zA-Z]:[\\/]/.test(file) || /^\\\\[^\\]+\\[^\\]+/.test(file);
571
+ }
572
+ function resolveWorkspacePath(file, cwd) {
573
+ if (isWindowsAbsolutePath(file)) {
574
+ return import_node_path2.default.win32.normalize(file);
575
+ }
576
+ return import_node_path2.default.isAbsolute(file) ? import_node_path2.default.resolve(file) : import_node_path2.default.resolve(cwd, file);
577
+ }
578
+ function assertPathWithinProject(file, projectRoot) {
579
+ const relativeToRoot = isWindowsAbsolutePath(file) || isWindowsAbsolutePath(projectRoot) ? import_node_path2.default.win32.relative(import_node_path2.default.win32.normalize(projectRoot), import_node_path2.default.win32.normalize(file)) : import_node_path2.default.relative(projectRoot, file);
580
+ if (relativeToRoot.startsWith("..") || import_node_path2.default.isAbsolute(relativeToRoot)) {
581
+ throw new Error("Access denied: File is outside of project workspace");
582
+ }
583
+ }
584
+
585
+ // src/server/annotation-dispatch.ts
586
+ var AnnotationDispatchError = class extends Error {
587
+ constructor(message, errorCode) {
588
+ super(message);
589
+ this.name = "AnnotationDispatchError";
590
+ this.errorCode = errorCode;
591
+ }
509
592
  };
510
- var serverInstance = null;
593
+ async function dispatchAnnotationsToAi(req, state) {
594
+ try {
595
+ validateAnnotationDispatchRequest(req, state);
596
+ const batch = normalizeAnnotationBatch(req);
597
+ const prompt = buildAnnotationBatchPrompt(batch);
598
+ const representativeTarget = batch.annotations[0]?.targets[0];
599
+ const runtime = resolvePromptDispatchRuntime(state);
600
+ return dispatchPromptThroughIde(runtime, {
601
+ prompt,
602
+ ...representativeTarget?.file ? { filePath: representativeTarget.file } : {},
603
+ ...representativeTarget?.line ? { line: representativeTarget.line } : {},
604
+ ...representativeTarget?.column ? { column: representativeTarget.column } : {},
605
+ ...batch.screenshotContext ? { screenshotContext: batch.screenshotContext } : {}
606
+ });
607
+ } catch (error) {
608
+ return {
609
+ success: false,
610
+ error: error instanceof Error ? error.message : String(error),
611
+ errorCode: getAnnotationDispatchErrorCode(error)
612
+ };
613
+ }
614
+ }
615
+ function validateAnnotationDispatchRequest(req, state) {
616
+ if (!req.annotations.length) {
617
+ throw new AnnotationDispatchError("At least one annotation is required.", "INVALID_REQUEST");
618
+ }
619
+ for (const annotation of req.annotations) {
620
+ if (!annotation.targets.length) {
621
+ throw new AnnotationDispatchError(
622
+ "Each annotation must include at least one target.",
623
+ "INVALID_REQUEST"
624
+ );
625
+ }
626
+ for (const target of annotation.targets) {
627
+ const absolutePath = resolveWorkspacePath(target.location.file, state.cwd);
628
+ assertPathWithinProject(absolutePath, state.projectRoot);
629
+ }
630
+ }
631
+ }
632
+ function normalizeAnnotationBatch(req) {
633
+ return {
634
+ instruction: req.instruction?.trim() ?? "",
635
+ responseMode: req.responseMode ?? "unified",
636
+ ...req.runtimeContext ? { runtimeContext: req.runtimeContext } : {},
637
+ ...req.screenshotContext ? { screenshotContext: req.screenshotContext } : {},
638
+ ...req.cssContextPrompt?.trim() ? { cssContextPrompt: req.cssContextPrompt.trim() } : {},
639
+ annotations: req.annotations.map((annotation, index) => ({
640
+ index: index + 1,
641
+ note: annotation.note.trim(),
642
+ intent: annotation.intent,
643
+ targets: annotation.targets.map((target) => ({
644
+ file: target.location.file,
645
+ line: target.location.line,
646
+ column: target.location.column,
647
+ ...target.label ? { label: target.label } : {},
648
+ ...target.selector ? { selector: target.selector } : {},
649
+ ...target.snippet ? { snippet: target.snippet } : {}
650
+ }))
651
+ }))
652
+ };
653
+ }
654
+ function buildAnnotationBatchPrompt(batch) {
655
+ const body = buildSelectedElementsPrompt(batch.annotations);
656
+ const prompt = batch.instruction ? `${batch.instruction}
657
+
658
+ ${body}` : body;
659
+ return appendScreenshotContextSection(
660
+ appendCssContextSection(
661
+ appendRuntimeContextSection(prompt, batch.runtimeContext),
662
+ batch.cssContextPrompt
663
+ ),
664
+ batch.screenshotContext
665
+ );
666
+ }
667
+ function appendCssContextSection(prompt, cssContextPrompt) {
668
+ if (!cssContextPrompt) return prompt;
669
+ return `${prompt}
670
+
671
+ ${cssContextPrompt}`;
672
+ }
673
+ function buildSelectedElementsPrompt(annotations) {
674
+ const lines = ["Selected elements:"];
675
+ for (const annotation of annotations) {
676
+ const trimmedNote = annotation.note.trim();
677
+ for (const target of annotation.targets) {
678
+ const targetLabel = (target.label || "Unknown target").trim() || "Unknown target";
679
+ lines.push(`- ${targetLabel}`);
680
+ lines.push(`file=${target.file}:${target.line}:${target.column}`);
681
+ if (trimmedNote) {
682
+ lines.push(`note=${trimmedNote}`);
683
+ }
684
+ }
685
+ }
686
+ if (lines.length === 1) {
687
+ lines.push("- None");
688
+ }
689
+ return lines.join("\n");
690
+ }
691
+ function appendScreenshotContextSection(prompt, screenshotContext) {
692
+ if (!screenshotContext || !screenshotContext.imageDataUrl && !screenshotContext.imageAssetId) {
693
+ return prompt;
694
+ }
695
+ const lines = [
696
+ "Visual screenshot context attached:",
697
+ `- capturedAt=${screenshotContext.capturedAt}`,
698
+ `- mimeType=${screenshotContext.mimeType}`,
699
+ ...screenshotContext.imageAssetId ? [`- imageAssetId=${screenshotContext.imageAssetId}`] : []
700
+ ];
701
+ return `${prompt}
702
+
703
+ ${lines.join("\n")}`;
704
+ }
705
+ function appendRuntimeContextSection(prompt, runtimeContext) {
706
+ if (!runtimeContext?.records.length) {
707
+ return prompt;
708
+ }
709
+ return `${prompt}
710
+
711
+ ${buildRuntimeContextSection(runtimeContext.records)}`;
712
+ }
713
+ function buildRuntimeContextSection(records) {
714
+ return ["Relevant runtime context:", ...records.map(formatRuntimeRecord)].join("\n");
715
+ }
716
+ function formatRuntimeRecord(record) {
717
+ const requestSummary = record.kind === "failed-request" ? `request=${record.request?.method ?? "GET"} ${record.request?.pathname ?? record.request?.url ?? "unknown"} status=${record.request?.status ?? "unknown"}` : `occurrences=${record.occurrenceCount}`;
718
+ const reasonSummary = record.relevanceReasons.length ? record.relevanceReasons.join("; ") : "timing-based";
719
+ const stackSummary = record.stack ? `
720
+ stack=${record.stack.split("\n").slice(0, 5).join(" | ")}` : "";
721
+ return [
722
+ `- [${record.kind}] ${record.message}`,
723
+ ` relevance=${record.relevanceLevel} (${reasonSummary})`,
724
+ ` ${requestSummary}`,
725
+ stackSummary
726
+ ].filter(Boolean).join("\n");
727
+ }
728
+ function getAnnotationDispatchErrorCode(error) {
729
+ if (error instanceof AnnotationDispatchError) return error.errorCode;
730
+ if (error instanceof Error && error.message.includes("outside of project workspace")) {
731
+ return "FORBIDDEN_PATH";
732
+ }
733
+ return "UNKNOWN";
734
+ }
735
+
736
+ // src/server/client-config.ts
737
+ async function buildClientConfig(serverState3) {
738
+ const userConfig = loadUserConfigSync(false, serverState3.cwd, serverState3.configRoot);
739
+ const promptsConfig = await loadPromptsConfig(false, serverState3.cwd, serverState3.configRoot);
740
+ const effectiveIde = userConfig.ide ?? "vscode";
741
+ let info;
742
+ if (!serverState3.ideInfo) {
743
+ info = { ide: effectiveIde };
744
+ } else {
745
+ const { scheme: _scheme, ...rest } = serverState3.ideInfo;
746
+ info = rest;
747
+ }
748
+ return {
749
+ ...info,
750
+ prompts: resolveIntents(promptsConfig),
751
+ hotKeys: userConfig["inspector.hotKey"] ?? "alt",
752
+ theme: userConfig["inspector.theme"] ?? "auto",
753
+ includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
754
+ runtimeContext: {
755
+ enabled: true,
756
+ preview: true,
757
+ maxRuntimeErrors: 3,
758
+ maxFailedRequests: 2
759
+ },
760
+ screenshotContext: {
761
+ enabled: false
762
+ },
763
+ annotationResponseMode: userConfig["prompt.annotationResponseMode"] ?? "unified",
764
+ autoSend: userConfig["prompt.autoSend"] ?? false
765
+ };
766
+ }
767
+
768
+ // src/server/open-file.ts
769
+ var import_node_child_process2 = require("child_process");
770
+ var import_launch_ide2 = require("launch-ide");
771
+ var serverLogger2 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
772
+ var VSCODE_FAMILY_SCHEMES = [
773
+ "vscode",
774
+ "vscode-insiders",
775
+ "cursor",
776
+ "windsurf",
777
+ "trae",
778
+ "trae-cn",
779
+ "vscodium",
780
+ "codebuddy",
781
+ "codebuddy-cn",
782
+ "antigravity"
783
+ ];
784
+ function handleOpenFileRequest(body, serverState3) {
785
+ const absolutePath = resolveWorkspacePath(body.file, serverState3.cwd);
786
+ assertPathWithinProject(absolutePath, serverState3.projectRoot);
787
+ const userConfig = loadUserConfigSync(false, serverState3.cwd, serverState3.configRoot);
788
+ const configuredIde = userConfig.ide;
789
+ const activeIde = serverState3.ideInfo?.ide;
790
+ const activeIdeScheme = serverState3.ideInfo?.scheme;
791
+ const rawEditorHint = configuredIde || activeIde || activeIdeScheme || "code";
792
+ if (configuredIde && activeIdeScheme && !activeIdeScheme.includes(configuredIde)) {
793
+ serverLogger2.warn(
794
+ `Active IDE is ${activeIdeScheme}, but config forces ${configuredIde}. Using configured IDE.`
795
+ );
796
+ }
797
+ let editorHint = rawEditorHint;
798
+ if (rawEditorHint === "vscode") editorHint = "code";
799
+ else if (rawEditorHint === "vscode-insiders") editorHint = "code-insiders";
800
+ else if (rawEditorHint === "vscodium") editorHint = "codium";
801
+ else if (rawEditorHint === "trae-cn" || rawEditorHint === "trae") editorHint = "trae";
802
+ serverLogger2.debug(
803
+ `IDE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
804
+ );
805
+ if (VSCODE_FAMILY_SCHEMES.includes(rawEditorHint)) {
806
+ let normalizedPath = absolutePath.replace(/\\/g, "/");
807
+ if (!normalizedPath.startsWith("/")) {
808
+ normalizedPath = "/" + normalizedPath;
809
+ }
810
+ const encodedPath = encodeURI(normalizedPath);
811
+ const uri = `${rawEditorHint}://file${encodedPath}:${body.line}:${body.column}`;
812
+ serverLogger2.debug(`IDE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
813
+ try {
814
+ if (process.platform === "darwin") {
815
+ (0, import_node_child_process2.execFileSync)("open", [uri]);
816
+ } else if (process.platform === "win32") {
817
+ (0, import_node_child_process2.execFileSync)("cmd", ["/c", "start", '""', uri]);
818
+ } else {
819
+ (0, import_node_child_process2.execFileSync)("xdg-open", [uri]);
820
+ }
821
+ } catch (e) {
822
+ serverLogger2.error(`Failed to launch URI for IDE_OPEN (${uri}):`, e);
823
+ (0, import_launch_ide2.launchIDE)({
824
+ file: absolutePath,
825
+ line: body.line,
826
+ column: body.column,
827
+ editor: editorHint,
828
+ type: process.platform === "darwin" ? "open" : "exec"
829
+ });
830
+ }
831
+ } else {
832
+ (0, import_launch_ide2.launchIDE)({
833
+ file: absolutePath,
834
+ line: body.line,
835
+ column: body.column,
836
+ editor: editorHint,
837
+ type: process.platform === "darwin" ? "open" : "exec"
838
+ });
839
+ }
840
+ return { success: true };
841
+ }
842
+
843
+ // src/server/project-root.ts
844
+ var import_node_fs2 = __toESM(require("fs"), 1);
845
+ var import_node_path3 = __toESM(require("path"), 1);
846
+ var import_node_child_process3 = require("child_process");
847
+ var serverLogger3 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
511
848
  function resolveProjectRoot() {
512
849
  const cwd = process.cwd();
513
850
  let gitRoot;
514
851
  try {
515
- gitRoot = (0, import_node_child_process.execSync)("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
852
+ gitRoot = (0, import_node_child_process3.execSync)("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
516
853
  } catch (e) {
517
- serverLogger.warn("Failed to resolve git root via git rev-parse:", e);
854
+ serverLogger3.warn("Failed to resolve git root via git rev-parse:", e);
518
855
  gitRoot = cwd;
519
856
  }
520
857
  const visited = /* @__PURE__ */ new Set();
@@ -522,34 +859,31 @@ function resolveProjectRoot() {
522
859
  let current = start;
523
860
  while (!visited.has(current)) {
524
861
  visited.add(current);
525
- if (import_node_fs2.default.existsSync(import_node_path2.default.join(current, ".inspecto"))) return current;
862
+ if (import_node_fs2.default.existsSync(import_node_path3.default.join(current, ".inspecto"))) return current;
526
863
  if (current === stop) break;
527
- const parent = import_node_path2.default.dirname(current);
864
+ const parent = import_node_path3.default.dirname(current);
528
865
  if (parent === current) break;
529
866
  current = parent;
530
867
  }
531
868
  return null;
532
869
  };
533
- const cwdMatch = search(cwd, import_node_path2.default.parse(cwd).root);
870
+ const cwdMatch = search(cwd, import_node_path3.default.parse(cwd).root);
534
871
  if (cwdMatch) return cwdMatch;
535
- const repoMatch = search(gitRoot, import_node_path2.default.parse(gitRoot).root);
872
+ const repoMatch = search(gitRoot, import_node_path3.default.parse(gitRoot).root);
536
873
  if (repoMatch) return repoMatch;
537
874
  return gitRoot;
538
875
  }
539
- function launchURI(uri) {
540
- try {
541
- if (process.platform === "darwin") {
542
- (0, import_node_child_process.execFileSync)("open", [uri]);
543
- } else if (process.platform === "win32") {
544
- (0, import_node_child_process.execFileSync)("cmd", ["/c", "start", '""', uri]);
545
- } else {
546
- (0, import_node_child_process.execFileSync)("xdg-open", [uri]);
547
- }
548
- } catch (e) {
549
- serverLogger.error("Failed to launch URI via execFileSync, falling back to launchIDE:", e);
550
- (0, import_launch_ide.launchIDE)({ file: uri });
551
- }
552
- }
876
+
877
+ // src/server/index.ts
878
+ var serverLogger4 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
879
+ var serverState = {
880
+ port: null,
881
+ running: false,
882
+ projectRoot: "",
883
+ configRoot: "",
884
+ cwd: process.cwd()
885
+ };
886
+ var serverInstance = null;
553
887
  async function startServer() {
554
888
  if (serverState.running && serverState.port !== null) {
555
889
  return serverState.port;
@@ -561,7 +895,7 @@ async function startServer() {
561
895
  const port = await import_portfinder.default.getPortPromise();
562
896
  watchConfig(
563
897
  () => {
564
- serverLogger.info("user config reloaded.");
898
+ serverLogger4.info("user config reloaded.");
565
899
  },
566
900
  serverState.cwd,
567
901
  serverState.configRoot
@@ -577,7 +911,7 @@ async function startServer() {
577
911
  }
578
912
  const url = new URL(req.url ?? "/", `http://localhost:${port}`);
579
913
  handleRequest(url, req, res).catch((err) => {
580
- serverLogger.error("server error:", err);
914
+ serverLogger4.error("server error:", err);
581
915
  res.writeHead(500, { "Content-Type": "application/json" });
582
916
  res.end(JSON.stringify({ success: false, error: String(err) }));
583
917
  });
@@ -590,41 +924,41 @@ async function startServer() {
590
924
  serverInstance.once("error", reject);
591
925
  });
592
926
  serverInstance.on("error", (err) => {
593
- serverLogger.error("persistent server error:", err);
927
+ serverLogger4.error("persistent server error:", err);
594
928
  });
595
929
  serverState.port = port;
596
930
  serverState.running = true;
597
- const portFile = import_node_path2.default.join(import_node_os2.default.tmpdir(), "inspecto.port.json");
931
+ const portFile = import_node_path4.default.join(import_node_os2.default.tmpdir(), "inspecto.port.json");
598
932
  try {
599
933
  let portData = {};
600
- if (import_node_fs2.default.existsSync(portFile)) {
934
+ if (import_node_fs3.default.existsSync(portFile)) {
601
935
  try {
602
- portData = JSON.parse(import_node_fs2.default.readFileSync(portFile, "utf-8"));
936
+ portData = JSON.parse(import_node_fs3.default.readFileSync(portFile, "utf-8"));
603
937
  } catch (e) {
604
938
  }
605
939
  }
606
- const rootHash = import_node_crypto.default.createHash("md5").update(serverState.projectRoot).digest("hex");
940
+ const rootHash = import_node_crypto2.default.createHash("md5").update(serverState.projectRoot).digest("hex");
607
941
  portData[rootHash] = port;
608
- import_node_fs2.default.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
942
+ import_node_fs3.default.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
609
943
  } catch (e) {
610
- serverLogger.warn("Failed to write port file:", e);
944
+ serverLogger4.warn("Failed to write port file:", e);
611
945
  }
612
946
  process.once("exit", () => {
613
947
  try {
614
- if (import_node_fs2.default.existsSync(portFile)) {
615
- const portData = JSON.parse(import_node_fs2.default.readFileSync(portFile, "utf-8"));
616
- const rootHash = import_node_crypto.default.createHash("md5").update(serverState.projectRoot).digest("hex");
948
+ if (import_node_fs3.default.existsSync(portFile)) {
949
+ const portData = JSON.parse(import_node_fs3.default.readFileSync(portFile, "utf-8"));
950
+ const rootHash = import_node_crypto2.default.createHash("md5").update(serverState.projectRoot).digest("hex");
617
951
  delete portData[rootHash];
618
952
  if (Object.keys(portData).length === 0) {
619
- import_node_fs2.default.unlinkSync(portFile);
953
+ import_node_fs3.default.unlinkSync(portFile);
620
954
  } else {
621
- import_node_fs2.default.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
955
+ import_node_fs3.default.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
622
956
  }
623
957
  }
624
958
  } catch {
625
959
  }
626
960
  });
627
- serverLogger.info(`server running at http://127.0.0.1:${port}`);
961
+ serverLogger4.info(`server running at http://127.0.0.1:${port}`);
628
962
  return port;
629
963
  }
630
964
  async function readBody(req) {
@@ -643,26 +977,7 @@ async function handleRequest(url, req, res) {
643
977
  return;
644
978
  }
645
979
  if (pathname === import_types2.INSPECTO_API_PATHS.CLIENT_CONFIG && req.method === "GET") {
646
- const userConfig = loadUserConfigSync(false, serverState.cwd, serverState.configRoot);
647
- const promptsConfig = await loadPromptsConfig(false, serverState.cwd, serverState.configRoot);
648
- const effectiveIde = userConfig.ide ?? "vscode";
649
- let info;
650
- if (!serverState.ideInfo) {
651
- info = {
652
- ide: effectiveIde
653
- };
654
- } else {
655
- const { scheme: _scheme, ...rest } = serverState.ideInfo;
656
- info = rest;
657
- }
658
- const config = {
659
- ...info,
660
- prompts: resolveIntents(promptsConfig),
661
- hotKeys: userConfig["inspector.hotKey"] ?? "alt",
662
- theme: userConfig["inspector.theme"] ?? "auto",
663
- includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
664
- autoSend: userConfig["prompt.autoSend"] ?? false
665
- };
980
+ const config = await buildClientConfig(serverState);
666
981
  delete config.providers;
667
982
  res.writeHead(200, { "Content-Type": "application/json" });
668
983
  res.end(JSON.stringify(config));
@@ -673,23 +988,23 @@ async function handleRequest(url, req, res) {
673
988
  const body = JSON.parse(await readBody(req));
674
989
  const ideWorkspace = body.workspaceRoot || "";
675
990
  const serverProjectRoot = serverState.projectRoot || "";
676
- const normalizedIdeRoot = ideWorkspace ? import_node_path2.default.resolve(ideWorkspace) : "";
677
- const normalizedServerRoot = serverProjectRoot ? import_node_path2.default.resolve(serverProjectRoot) : "";
678
- const isSameProject = !normalizedIdeRoot || !normalizedServerRoot || normalizedIdeRoot === normalizedServerRoot || normalizedServerRoot.startsWith(normalizedIdeRoot + import_node_path2.default.sep) || normalizedIdeRoot.startsWith(normalizedServerRoot + import_node_path2.default.sep);
991
+ const normalizedIdeRoot = ideWorkspace ? import_node_path4.default.resolve(ideWorkspace) : "";
992
+ const normalizedServerRoot = serverProjectRoot ? import_node_path4.default.resolve(serverProjectRoot) : "";
993
+ const isSameProject = !normalizedIdeRoot || !normalizedServerRoot || normalizedIdeRoot === normalizedServerRoot || normalizedServerRoot.startsWith(normalizedIdeRoot + import_node_path4.default.sep) || normalizedIdeRoot.startsWith(normalizedServerRoot + import_node_path4.default.sep);
679
994
  if (isSameProject) {
680
995
  serverState.ideInfo = body;
681
- serverLogger.debug(
996
+ serverLogger4.debug(
682
997
  `Accepted IDE info from matched workspace (ide-${body.ide} / schema-${body.scheme})`
683
998
  );
684
999
  } else {
685
- serverLogger.debug(
1000
+ serverLogger4.debug(
686
1001
  `Ignored IDE info from unrelated workspace (IDE Workspace: ${ideWorkspace}, Server: ${serverProjectRoot}, Scheme: ${body.scheme}, IDE: ${body.ide})`
687
1002
  );
688
1003
  }
689
1004
  res.writeHead(200, { "Content-Type": "application/json" });
690
1005
  res.end(JSON.stringify({ success: true }));
691
1006
  } catch (e) {
692
- serverLogger.error(`Error parsing ${import_types2.INSPECTO_API_PATHS.IDE_INFO} POST request:`, e);
1007
+ serverLogger4.error(`Error parsing ${import_types2.INSPECTO_API_PATHS.IDE_INFO} POST request:`, e);
693
1008
  res.writeHead(400, { "Content-Type": "application/json" });
694
1009
  res.end(JSON.stringify({ error: "Invalid JSON body" }));
695
1010
  }
@@ -704,79 +1019,14 @@ async function handleRequest(url, req, res) {
704
1019
  res.end(JSON.stringify({ error: "Invalid JSON body" }));
705
1020
  return;
706
1021
  }
707
- const absolutePath = import_node_path2.default.isAbsolute(body.file) ? import_node_path2.default.resolve(body.file) : import_node_path2.default.resolve(serverState.cwd, body.file);
708
- const relativeToRoot = import_node_path2.default.relative(serverState.projectRoot, absolutePath);
709
- if (relativeToRoot.startsWith("..") || import_node_path2.default.isAbsolute(relativeToRoot)) {
710
- serverLogger.warn(`Security: Blocked path traversal attempt in IDE_OPEN: ${body.file}`);
1022
+ try {
1023
+ handleOpenFileRequest(body, serverState);
1024
+ } catch {
1025
+ serverLogger4.warn(`Security: Blocked path traversal attempt in IDE_OPEN: ${body.file}`);
711
1026
  res.writeHead(403, { "Content-Type": "application/json" });
712
1027
  res.end(JSON.stringify({ error: "Access denied: File is outside of project workspace" }));
713
1028
  return;
714
1029
  }
715
- const userConfig = loadUserConfigSync(false, serverState.cwd, serverState.configRoot);
716
- const configuredIde = userConfig.ide;
717
- const activeIde = serverState.ideInfo?.ide;
718
- const activeIdeScheme = serverState.ideInfo?.scheme;
719
- const rawEditorHint = configuredIde || activeIde || activeIdeScheme || "code";
720
- if (configuredIde && activeIdeScheme && !activeIdeScheme.includes(configuredIde)) {
721
- serverLogger.warn(
722
- `Active IDE is ${activeIdeScheme}, but config forces ${configuredIde}. Using configured IDE.`
723
- );
724
- }
725
- let editorHint = rawEditorHint;
726
- if (rawEditorHint === "vscode") editorHint = "code";
727
- else if (rawEditorHint === "vscode-insiders") editorHint = "code-insiders";
728
- else if (rawEditorHint === "vscodium") editorHint = "codium";
729
- else if (rawEditorHint === "trae-cn" || rawEditorHint === "trae") editorHint = "trae";
730
- serverLogger.debug(
731
- `IDE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
732
- );
733
- const VSCODE_FAMILY_SCHEMES = [
734
- "vscode",
735
- "vscode-insiders",
736
- "cursor",
737
- "windsurf",
738
- "trae",
739
- "trae-cn",
740
- "vscodium",
741
- "codebuddy",
742
- "codebuddy-cn",
743
- "antigravity"
744
- ];
745
- if (VSCODE_FAMILY_SCHEMES.includes(rawEditorHint)) {
746
- let normalizedPath = absolutePath.replace(/\\/g, "/");
747
- if (!normalizedPath.startsWith("/")) {
748
- normalizedPath = "/" + normalizedPath;
749
- }
750
- const encodedPath = encodeURI(normalizedPath);
751
- const uri = `${rawEditorHint}://file${encodedPath}:${body.line}:${body.column}`;
752
- serverLogger.debug(`IDE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
753
- try {
754
- if (process.platform === "darwin") {
755
- (0, import_node_child_process.execFileSync)("open", [uri]);
756
- } else if (process.platform === "win32") {
757
- (0, import_node_child_process.execFileSync)("cmd", ["/c", "start", '""', uri]);
758
- } else {
759
- (0, import_node_child_process.execFileSync)("xdg-open", [uri]);
760
- }
761
- } catch (e) {
762
- serverLogger.error(`Failed to launch URI for IDE_OPEN (${uri}):`, e);
763
- (0, import_launch_ide.launchIDE)({
764
- file: absolutePath,
765
- line: body.line,
766
- column: body.column,
767
- editor: editorHint,
768
- type: process.platform === "darwin" ? "open" : "exec"
769
- });
770
- }
771
- } else {
772
- (0, import_launch_ide.launchIDE)({
773
- file: absolutePath,
774
- line: body.line,
775
- column: body.column,
776
- editor: editorHint,
777
- type: process.platform === "darwin" ? "open" : "exec"
778
- });
779
- }
780
1030
  res.writeHead(200, { "Content-Type": "application/json" });
781
1031
  res.end(JSON.stringify({ success: true }));
782
1032
  return;
@@ -787,10 +1037,11 @@ async function handleRequest(url, req, res) {
787
1037
  const column = parseInt(url.searchParams.get("column") ?? "1", 10);
788
1038
  const maxLines = parseInt(url.searchParams.get("maxLines") ?? "100", 10);
789
1039
  try {
790
- const absolutePath = import_node_path2.default.isAbsolute(file) ? import_node_path2.default.resolve(file) : import_node_path2.default.resolve(serverState.cwd, file);
791
- const relativeToRoot = import_node_path2.default.relative(serverState.projectRoot, absolutePath);
792
- if (relativeToRoot.startsWith("..") || import_node_path2.default.isAbsolute(relativeToRoot)) {
793
- serverLogger.warn(`Security: Blocked path traversal attempt in PROJECT_SNIPPET: ${file}`);
1040
+ const absolutePath = resolveWorkspacePath(file, serverState.cwd);
1041
+ try {
1042
+ assertPathWithinProject(absolutePath, serverState.projectRoot);
1043
+ } catch {
1044
+ serverLogger4.warn(`Security: Blocked path traversal attempt in PROJECT_SNIPPET: ${file}`);
794
1045
  res.writeHead(403, { "Content-Type": "application/json" });
795
1046
  res.end(
796
1047
  JSON.stringify({
@@ -820,7 +1071,23 @@ async function handleRequest(url, req, res) {
820
1071
  res.writeHead(result.success ? 200 : 500, { "Content-Type": "application/json" });
821
1072
  res.end(JSON.stringify(result));
822
1073
  } catch (e) {
823
- serverLogger.error(`Error parsing ${import_types2.INSPECTO_API_PATHS.AI_DISPATCH} request:`, e);
1074
+ serverLogger4.error(`Error parsing ${import_types2.INSPECTO_API_PATHS.AI_DISPATCH} request:`, e);
1075
+ res.writeHead(500, { "Content-Type": "application/json" });
1076
+ res.end(JSON.stringify({ success: false, error: String(e), errorCode: "INTERNAL_ERROR" }));
1077
+ }
1078
+ return;
1079
+ }
1080
+ if (pathname === import_types2.INSPECTO_API_PATHS.AI_BATCH_DISPATCH && req.method === "POST") {
1081
+ try {
1082
+ const rawBody = await readBody(req);
1083
+ const body = JSON.parse(rawBody);
1084
+ const result = await dispatchAnnotationsToAi(body, serverState);
1085
+ res.writeHead(getBatchDispatchStatusCode(result.errorCode, result.success), {
1086
+ "Content-Type": "application/json"
1087
+ });
1088
+ res.end(JSON.stringify(result));
1089
+ } catch (e) {
1090
+ serverLogger4.error(`Error parsing ${import_types2.INSPECTO_API_PATHS.AI_BATCH_DISPATCH} request:`, e);
824
1091
  res.writeHead(500, { "Content-Type": "application/json" });
825
1092
  res.end(JSON.stringify({ success: false, error: String(e), errorCode: "INTERNAL_ERROR" }));
826
1093
  }
@@ -828,7 +1095,7 @@ async function handleRequest(url, req, res) {
828
1095
  }
829
1096
  if (pathname.startsWith(`${import_types2.INSPECTO_API_PATHS.AI_TICKET}/`) && req.method === "GET") {
830
1097
  const ticketId = pathname.substring(import_types2.INSPECTO_API_PATHS.AI_TICKET.length + 1);
831
- const payloadStr = payloadTickets.get(ticketId);
1098
+ const payloadStr = readTicket(ticketId);
832
1099
  if (!payloadStr) {
833
1100
  res.writeHead(404, { "Content-Type": "application/json" });
834
1101
  res.end(JSON.stringify({ success: false, error: "Ticket not found or expired" }));
@@ -842,54 +1109,28 @@ async function handleRequest(url, req, res) {
842
1109
  res.end(JSON.stringify({ error: "not found" }));
843
1110
  }
844
1111
  async function dispatchToAi(req) {
845
- const { location, snippet, prompt } = req;
846
- const userConfig = loadUserConfigSync(false, serverState.cwd, serverState.configRoot);
847
- const resolvedTarget = resolveTargetTool(userConfig);
1112
+ const { location, snippet, prompt, screenshotContext } = req;
848
1113
  const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
849
1114
 
850
1115
  \`\`\`
851
1116
  ${snippet}
852
1117
  \`\`\`
853
1118
  `;
854
- const ideReportedMode = serverState.ideInfo?.providers[resolvedTarget]?.mode;
855
- const configuredIde = userConfig.ide;
856
- const activeIde = serverState.ideInfo?.ide;
857
- const activeIdeScheme = serverState.ideInfo?.scheme;
858
- const finalIde = configuredIde || activeIdeScheme || activeIde || "vscode";
859
- if (configuredIde && activeIdeScheme && !activeIdeScheme.includes(configuredIde)) {
860
- serverLogger.warn(
861
- `dispatchToAi: Active IDE is ${activeIdeScheme}, but config forces ${configuredIde}. Using configured IDE.`
862
- );
863
- }
864
- const mode = resolveProviderMode(resolvedTarget, finalIde, userConfig);
865
- const overrides = extractToolOverrides(finalIde, userConfig)[resolvedTarget] || {};
866
- overrides.type = mode;
867
- const fullPayload = {
868
- ide: finalIde,
869
- target: resolvedTarget,
870
- targetType: mode,
1119
+ const runtime = resolvePromptDispatchRuntime(serverState);
1120
+ return dispatchPromptThroughIde(runtime, {
871
1121
  prompt: formattedPrompt,
872
1122
  filePath: location.file,
873
1123
  line: location.line,
874
1124
  column: location.column,
875
1125
  snippet,
876
- overrides: Object.keys(overrides).length > 0 ? overrides : void 0,
877
- autoSend: userConfig["prompt.autoSend"] !== void 0 ? Boolean(userConfig["prompt.autoSend"]) : void 0
878
- };
879
- const ticketId = createTicket(fullPayload);
880
- const params = new URLSearchParams();
881
- params.set("ticket", ticketId);
882
- params.set("target", resolvedTarget);
883
- const uri = `${finalIde}://inspecto.inspecto/send?${params.toString()}`;
884
- serverLogger.debug(`dispatchToAi: Generated URI: ${uri}`);
885
- launchURI(uri);
886
- return {
887
- success: true,
888
- fallbackPayload: {
889
- prompt: formattedPrompt,
890
- file: location.file
891
- }
892
- };
1126
+ ...screenshotContext ? { screenshotContext } : {}
1127
+ });
1128
+ }
1129
+ function getBatchDispatchStatusCode(errorCode, success) {
1130
+ if (success) return 200;
1131
+ if (errorCode === "INVALID_REQUEST") return 400;
1132
+ if (errorCode === "FORBIDDEN_PATH") return 403;
1133
+ return 500;
893
1134
  }
894
1135
 
895
1136
  // src/injectors/utils.ts
@@ -924,7 +1165,7 @@ window.addEventListener('load', () => {
924
1165
  }
925
1166
 
926
1167
  // src/legacy/rspack/index.ts
927
- var import_node_path3 = __toESM(require("path"), 1);
1168
+ var import_node_path5 = __toESM(require("path"), 1);
928
1169
  var _dirname = typeof __dirname !== "undefined" ? __dirname : __dirname;
929
1170
  var DEFAULT_OPTIONS = {
930
1171
  include: [],
@@ -994,7 +1235,7 @@ var InspectoRspackLegacyPlugin = class {
994
1235
  );
995
1236
  }
996
1237
  });
997
- const loaderPath = import_node_path3.default.resolve(_dirname, "./loader.cjs");
1238
+ const loaderPath = import_node_path5.default.resolve(_dirname, "./loader.cjs");
998
1239
  compiler.options.module.rules.push({
999
1240
  test: /\.[jt]sx?$/,
1000
1241
  use: [