@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
package/dist/index.js CHANGED
@@ -694,7 +694,9 @@ function transformRouter(options) {
694
694
  return transformSvelte({
695
695
  filePath,
696
696
  source,
697
+ projectRoot,
697
698
  escapeTags: pluginOptions.escapeTags,
699
+ pathType: pluginOptions.pathType,
698
700
  attributeName: pluginOptions.attributeName
699
701
  });
700
702
  }
@@ -703,6 +705,7 @@ function transformRouter(options) {
703
705
  filePath,
704
706
  source,
705
707
  escapeTags: pluginOptions.escapeTags,
708
+ pathType: pluginOptions.pathType,
706
709
  attributeName: pluginOptions.attributeName
707
710
  });
708
711
  }
@@ -1224,7 +1227,6 @@ function dispatchPromptThroughIde(runtime, payload) {
1224
1227
  line: payload.line,
1225
1228
  column: payload.column,
1226
1229
  snippet: payload.snippet,
1227
- ...payload.screenshotContext ? { screenshotContext: payload.screenshotContext } : {},
1228
1230
  overrides: runtime.overrides,
1229
1231
  autoSend: runtime.autoSend
1230
1232
  });
@@ -1358,6 +1360,188 @@ function assertPathWithinIdeOpenScope(file, projectRoot) {
1358
1360
  }
1359
1361
  }
1360
1362
 
1363
+ // src/server/session-store.ts
1364
+ var DEFAULT_STATUS = "pending";
1365
+ function createAnnotationSessionStore(options = {}) {
1366
+ const sessions = /* @__PURE__ */ new Map();
1367
+ const listeners = /* @__PURE__ */ new Set();
1368
+ const now = options.now ?? (() => Date.now());
1369
+ const createId = options.createId ?? createRandomId;
1370
+ function findNewestMatchingSession(statuses) {
1371
+ return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
1372
+ }
1373
+ function updateSessionStatus(id, status) {
1374
+ const session = sessions.get(id);
1375
+ if (!session) return null;
1376
+ const timestamp = now();
1377
+ session.status = status;
1378
+ session.updatedAt = timestamp;
1379
+ if (status === "acknowledged") {
1380
+ session.acknowledgedAt = timestamp;
1381
+ }
1382
+ if (status === "resolved") {
1383
+ session.resolvedAt = timestamp;
1384
+ }
1385
+ emit({ type: "session-status-updated", session });
1386
+ return cloneSession(session);
1387
+ }
1388
+ function claimSession(id, statuses) {
1389
+ const session = sessions.get(id);
1390
+ if (!session || statuses && !statuses.has(session.status)) return null;
1391
+ if (session.status === "acknowledged") return cloneSession(session);
1392
+ return updateSessionStatus(id, "acknowledged");
1393
+ }
1394
+ function emit(event) {
1395
+ const snapshot = cloneSession(event.session);
1396
+ for (const listener of listeners) {
1397
+ listener({ type: event.type, session: snapshot });
1398
+ }
1399
+ }
1400
+ const store = {
1401
+ createSession(input) {
1402
+ const timestamp = now();
1403
+ const session = {
1404
+ id: createId(),
1405
+ instruction: input.instruction?.trim() ?? "",
1406
+ annotations: cloneArray(input.annotations),
1407
+ ...input.deliveryMode ? { deliveryMode: input.deliveryMode } : {},
1408
+ status: DEFAULT_STATUS,
1409
+ messages: cloneArray(input.messages ?? []),
1410
+ createdAt: timestamp,
1411
+ updatedAt: timestamp,
1412
+ ...input.runtimeContext ? { runtimeContext: cloneValue(input.runtimeContext) } : {},
1413
+ ...input.cssContextPrompt?.trim() ? { cssContextPrompt: input.cssContextPrompt.trim() } : {},
1414
+ ...input.pageUrl ? { pageUrl: input.pageUrl } : {},
1415
+ ...input.route ? { route: input.route } : {}
1416
+ };
1417
+ sessions.set(session.id, session);
1418
+ emit({ type: "session-created", session });
1419
+ return cloneSession(session);
1420
+ },
1421
+ getSession(id) {
1422
+ const session = sessions.get(id);
1423
+ return session ? cloneSession(session) : null;
1424
+ },
1425
+ listSessions(options2 = {}) {
1426
+ const statuses = normalizeStatuses(options2.status);
1427
+ return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt).map((session) => cloneSession(session));
1428
+ },
1429
+ async claimNextSession(options2 = {}) {
1430
+ const statuses = normalizeStatuses(DEFAULT_STATUS);
1431
+ const existingSession = findNewestMatchingSession(statuses);
1432
+ if (existingSession) {
1433
+ return {
1434
+ session: claimSession(existingSession.id, statuses),
1435
+ timedOut: false,
1436
+ matchedExisting: true
1437
+ };
1438
+ }
1439
+ const timeoutMs = normalizeTimeoutMs(options2.timeoutMs);
1440
+ if (timeoutMs === 0) {
1441
+ return {
1442
+ session: null,
1443
+ timedOut: true,
1444
+ matchedExisting: false
1445
+ };
1446
+ }
1447
+ return await new Promise((resolve2) => {
1448
+ let settled = false;
1449
+ let timeout = null;
1450
+ const finish = (result) => {
1451
+ if (settled) return;
1452
+ settled = true;
1453
+ unsubscribe();
1454
+ if (timeout) {
1455
+ clearTimeout(timeout);
1456
+ }
1457
+ resolve2(result);
1458
+ };
1459
+ const unsubscribe = this.subscribe((event) => {
1460
+ const session = claimSession(event.session.id, statuses);
1461
+ if (!session) return;
1462
+ finish({
1463
+ session,
1464
+ timedOut: false,
1465
+ matchedExisting: false,
1466
+ event: event.type
1467
+ });
1468
+ });
1469
+ if (timeoutMs !== null) {
1470
+ timeout = setTimeout(() => {
1471
+ finish({
1472
+ session: null,
1473
+ timedOut: true,
1474
+ matchedExisting: false
1475
+ });
1476
+ }, timeoutMs);
1477
+ }
1478
+ });
1479
+ },
1480
+ appendMessage(id, input) {
1481
+ const session = sessions.get(id);
1482
+ if (!session) return null;
1483
+ const timestamp = now();
1484
+ session.messages.push({
1485
+ id: createId(),
1486
+ role: input.role,
1487
+ text: input.text,
1488
+ createdAt: timestamp
1489
+ });
1490
+ session.updatedAt = timestamp;
1491
+ if (input.role === "agent" && isPendingLikeStatus(session.status)) {
1492
+ session.status = "in_progress";
1493
+ }
1494
+ emit({ type: "session-message-appended", session });
1495
+ return cloneSession(session);
1496
+ },
1497
+ updateStatus(id, status) {
1498
+ return updateSessionStatus(id, status);
1499
+ },
1500
+ subscribe(listener) {
1501
+ listeners.add(listener);
1502
+ return () => {
1503
+ listeners.delete(listener);
1504
+ };
1505
+ },
1506
+ clear() {
1507
+ sessions.clear();
1508
+ listeners.clear();
1509
+ }
1510
+ };
1511
+ return store;
1512
+ }
1513
+ var annotationSessionStore = createAnnotationSessionStore();
1514
+ function normalizeStatuses(status) {
1515
+ if (!status) return null;
1516
+ return new Set(Array.isArray(status) ? status : [status]);
1517
+ }
1518
+ function normalizeTimeoutMs(value) {
1519
+ if (value === void 0) return null;
1520
+ if (!Number.isFinite(value)) return 0;
1521
+ return Math.max(0, Math.floor(value));
1522
+ }
1523
+ function isPendingLikeStatus(status) {
1524
+ return status === "pending" || status === "acknowledged";
1525
+ }
1526
+ function hasAgentReply(session) {
1527
+ return session.messages.some((message) => message.role === "agent" && Boolean(message.text?.trim()));
1528
+ }
1529
+ function createRandomId() {
1530
+ return `annotation-session-${Math.random().toString(36).slice(2, 10)}`;
1531
+ }
1532
+ function cloneSession(session) {
1533
+ return cloneValue(session);
1534
+ }
1535
+ function cloneArray(value) {
1536
+ return cloneValue(value);
1537
+ }
1538
+ function cloneValue(value) {
1539
+ if (typeof structuredClone === "function") {
1540
+ return structuredClone(value);
1541
+ }
1542
+ return JSON.parse(JSON.stringify(value));
1543
+ }
1544
+
1361
1545
  // src/server/annotation-dispatch.ts
1362
1546
  var AnnotationDispatchError = class extends Error {
1363
1547
  constructor(message, errorCode) {
@@ -1366,20 +1550,30 @@ var AnnotationDispatchError = class extends Error {
1366
1550
  this.errorCode = errorCode;
1367
1551
  }
1368
1552
  };
1369
- async function dispatchAnnotationsToAi(req, state) {
1553
+ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStore) {
1370
1554
  try {
1371
1555
  validateAnnotationDispatchRequest(req, state);
1372
1556
  const batch = normalizeAnnotationBatch(req);
1373
1557
  const prompt = buildAnnotationBatchPrompt(batch);
1558
+ const deliveryMode = normalizeDeliveryMode(req.deliveryMode);
1559
+ const session = store.createSession({
1560
+ instruction: batch.instruction,
1561
+ annotations: toSessionAnnotations(batch.annotations),
1562
+ deliveryMode,
1563
+ ...batch.runtimeContext ? { runtimeContext: batch.runtimeContext } : {},
1564
+ ...batch.cssContextPrompt ? { cssContextPrompt: batch.cssContextPrompt } : {}
1565
+ });
1374
1566
  const representativeTarget = batch.annotations[0]?.targets[0];
1375
- const runtime = resolvePromptDispatchRuntime(state);
1376
- return dispatchPromptThroughIde(runtime, {
1567
+ const dispatchResult = deliveryMode === "ide" ? dispatchPromptThroughIde(resolvePromptDispatchRuntime(state), {
1377
1568
  prompt,
1378
1569
  ...representativeTarget?.file ? { filePath: representativeTarget.file } : {},
1379
1570
  ...representativeTarget?.line ? { line: representativeTarget.line } : {},
1380
- ...representativeTarget?.column ? { column: representativeTarget.column } : {},
1381
- ...batch.screenshotContext ? { screenshotContext: batch.screenshotContext } : {}
1382
- });
1571
+ ...representativeTarget?.column ? { column: representativeTarget.column } : {}
1572
+ }) : { success: true };
1573
+ return {
1574
+ ...dispatchResult,
1575
+ session: toSessionSummary(session)
1576
+ };
1383
1577
  } catch (error) {
1384
1578
  return {
1385
1579
  success: false,
@@ -1388,6 +1582,41 @@ async function dispatchAnnotationsToAi(req, state) {
1388
1582
  };
1389
1583
  }
1390
1584
  }
1585
+ function normalizeDeliveryMode(input) {
1586
+ return input === "agent" ? "agent" : "ide";
1587
+ }
1588
+ function toSessionAnnotations(annotations) {
1589
+ return annotations.map((annotation) => ({
1590
+ id: `annotation-${annotation.index}`,
1591
+ note: annotation.note,
1592
+ intent: annotation.intent,
1593
+ targets: annotation.targets.map((target, targetIndex) => ({
1594
+ id: `annotation-${annotation.index}-target-${targetIndex + 1}`,
1595
+ label: target.label ?? "Unknown target",
1596
+ location: {
1597
+ file: target.file,
1598
+ line: target.line,
1599
+ column: target.column
1600
+ },
1601
+ ...target.selector ? { selector: target.selector } : {},
1602
+ ...target.snippet ? { snippet: target.snippet } : {},
1603
+ rect: {
1604
+ x: 0,
1605
+ y: 0,
1606
+ width: 0,
1607
+ height: 0
1608
+ }
1609
+ }))
1610
+ }));
1611
+ }
1612
+ function toSessionSummary(session) {
1613
+ return {
1614
+ id: session.id,
1615
+ status: session.status,
1616
+ createdAt: session.createdAt,
1617
+ updatedAt: session.updatedAt
1618
+ };
1619
+ }
1391
1620
  function validateAnnotationDispatchRequest(req, state) {
1392
1621
  if (!req.annotations.length) {
1393
1622
  throw new AnnotationDispatchError("At least one annotation is required.", "INVALID_REQUEST");
@@ -1408,9 +1637,7 @@ function validateAnnotationDispatchRequest(req, state) {
1408
1637
  function normalizeAnnotationBatch(req) {
1409
1638
  return {
1410
1639
  instruction: req.instruction?.trim() ?? "",
1411
- responseMode: req.responseMode ?? "unified",
1412
1640
  ...req.runtimeContext ? { runtimeContext: req.runtimeContext } : {},
1413
- ...req.screenshotContext ? { screenshotContext: req.screenshotContext } : {},
1414
1641
  ...req.cssContextPrompt?.trim() ? { cssContextPrompt: req.cssContextPrompt.trim() } : {},
1415
1642
  annotations: req.annotations.map((annotation, index) => ({
1416
1643
  index: index + 1,
@@ -1432,12 +1659,9 @@ function buildAnnotationBatchPrompt(batch) {
1432
1659
  const prompt = batch.instruction ? `${batch.instruction}
1433
1660
 
1434
1661
  ${body}` : body;
1435
- return appendScreenshotContextSection(
1436
- appendCssContextSection(
1437
- appendRuntimeContextSection(prompt, batch.runtimeContext),
1438
- batch.cssContextPrompt
1439
- ),
1440
- batch.screenshotContext
1662
+ return appendCssContextSection(
1663
+ appendRuntimeContextSection(prompt, batch.runtimeContext),
1664
+ batch.cssContextPrompt
1441
1665
  );
1442
1666
  }
1443
1667
  function appendCssContextSection(prompt, cssContextPrompt) {
@@ -1464,20 +1688,6 @@ function buildSelectedElementsPrompt(annotations) {
1464
1688
  }
1465
1689
  return lines.join("\n");
1466
1690
  }
1467
- function appendScreenshotContextSection(prompt, screenshotContext) {
1468
- if (!screenshotContext || !screenshotContext.imageDataUrl && !screenshotContext.imageAssetId) {
1469
- return prompt;
1470
- }
1471
- const lines = [
1472
- "Visual screenshot context attached:",
1473
- `- capturedAt=${screenshotContext.capturedAt}`,
1474
- `- mimeType=${screenshotContext.mimeType}`,
1475
- ...screenshotContext.imageAssetId ? [`- imageAssetId=${screenshotContext.imageAssetId}`] : []
1476
- ];
1477
- return `${prompt}
1478
-
1479
- ${lines.join("\n")}`;
1480
- }
1481
1691
  function appendRuntimeContextSection(prompt, runtimeContext) {
1482
1692
  if (!runtimeContext?.records.length) {
1483
1693
  return prompt;
@@ -1525,7 +1735,7 @@ async function buildClientConfig(serverState2) {
1525
1735
  ...info,
1526
1736
  prompts: resolveIntents(promptsConfig),
1527
1737
  hotKeys: userConfig["inspector.hotKey"] ?? "alt",
1528
- theme: userConfig["inspector.theme"] ?? "auto",
1738
+ annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "both",
1529
1739
  includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
1530
1740
  runtimeContext: {
1531
1741
  enabled: true,
@@ -1533,10 +1743,6 @@ async function buildClientConfig(serverState2) {
1533
1743
  maxRuntimeErrors: 3,
1534
1744
  maxFailedRequests: 2
1535
1745
  },
1536
- screenshotContext: {
1537
- enabled: false
1538
- },
1539
- annotationResponseMode: userConfig["prompt.annotationResponseMode"] ?? "unified",
1540
1746
  autoSend: userConfig["prompt.autoSend"] ?? false
1541
1747
  };
1542
1748
  }
@@ -1581,7 +1787,7 @@ function handleOpenFileRequest(body, serverState2) {
1581
1787
  else if (rawEditorHint === "vscodium") editorHint = "codium";
1582
1788
  else if (rawEditorHint === "trae-cn" || rawEditorHint === "trae") editorHint = "trae";
1583
1789
  serverLogger2.debug(
1584
- `IDE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
1790
+ `SOURCE_OPEN: activeIde=${activeIde}, activeIdeScheme=${activeIdeScheme}, configuredIde=${configuredIde} -> rawEditorHint=${rawEditorHint}, finalEditorHint=${editorHint}`
1585
1791
  );
1586
1792
  if (VSCODE_FAMILY_SCHEMES.includes(rawEditorHint)) {
1587
1793
  let normalizedPath = absolutePath.replace(/\\/g, "/");
@@ -1590,7 +1796,7 @@ function handleOpenFileRequest(body, serverState2) {
1590
1796
  }
1591
1797
  const encodedPath = encodeURI(normalizedPath);
1592
1798
  const uri = `${rawEditorHint}://file${encodedPath}:${body.line}:${body.column}`;
1593
- serverLogger2.debug(`IDE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
1799
+ serverLogger2.debug(`SOURCE_OPEN: Bypassing launchIDE, using URI scheme directly: ${uri}`);
1594
1800
  try {
1595
1801
  if (process.platform === "darwin") {
1596
1802
  execFileSync2("open", [uri]);
@@ -1600,7 +1806,7 @@ function handleOpenFileRequest(body, serverState2) {
1600
1806
  execFileSync2("xdg-open", [uri]);
1601
1807
  }
1602
1808
  } catch (e) {
1603
- serverLogger2.error(`Failed to launch URI for IDE_OPEN (${uri}):`, e);
1809
+ serverLogger2.error(`Failed to launch URI for SOURCE_OPEN (${uri}):`, e);
1604
1810
  launchIDE2({
1605
1811
  file: absolutePath,
1606
1812
  line: body.line,
@@ -1655,8 +1861,27 @@ function resolveProjectRoot() {
1655
1861
  return gitRoot;
1656
1862
  }
1657
1863
 
1864
+ // src/server/server-url.ts
1865
+ function resolveServerHost(cwd, configRoot) {
1866
+ const userConfig = loadUserConfigSync(false, cwd, configRoot);
1867
+ const configuredHost = userConfig["server.host"]?.trim();
1868
+ if (configuredHost) return configuredHost;
1869
+ if (process.env["VITEST"]) return "127.0.0.1";
1870
+ return "127.0.0.1";
1871
+ }
1872
+ function resolvePublicServerUrl(args) {
1873
+ const userConfig = loadUserConfigSync(false, args.cwd, args.configRoot);
1874
+ const configuredPublicUrl = userConfig["server.publicUrl"]?.trim();
1875
+ if (configuredPublicUrl) {
1876
+ return configuredPublicUrl.replace(/\/$/, "");
1877
+ }
1878
+ const host = resolveServerHost(args.cwd, args.configRoot);
1879
+ return `http://${host}:${args.port}`;
1880
+ }
1881
+
1658
1882
  // src/server/index.ts
1659
1883
  var serverLogger4 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
1884
+ var PORT_FILE_NAME = "inspecto.port.json";
1660
1885
  var serverState = {
1661
1886
  port: null,
1662
1887
  running: false,
@@ -1665,6 +1890,42 @@ var serverState = {
1665
1890
  cwd: process.cwd()
1666
1891
  };
1667
1892
  var serverInstance = null;
1893
+ function getPortFilePath() {
1894
+ return path8.join(os2.tmpdir(), PORT_FILE_NAME);
1895
+ }
1896
+ function getProjectRootHash() {
1897
+ if (!serverState.projectRoot) return null;
1898
+ return crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1899
+ }
1900
+ function readPortData(portFile) {
1901
+ if (!fs5.existsSync(portFile)) return {};
1902
+ try {
1903
+ return JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1904
+ } catch {
1905
+ return {};
1906
+ }
1907
+ }
1908
+ function writeProjectPort(port) {
1909
+ const rootHash = getProjectRootHash();
1910
+ if (!rootHash) return;
1911
+ const portFile = getPortFilePath();
1912
+ const portData = readPortData(portFile);
1913
+ portData[rootHash] = port;
1914
+ fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1915
+ }
1916
+ function removeProjectPort() {
1917
+ const rootHash = getProjectRootHash();
1918
+ if (!rootHash) return;
1919
+ const portFile = getPortFilePath();
1920
+ if (!fs5.existsSync(portFile)) return;
1921
+ const portData = readPortData(portFile);
1922
+ delete portData[rootHash];
1923
+ if (Object.keys(portData).length === 0) {
1924
+ fs5.unlinkSync(portFile);
1925
+ } else {
1926
+ fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1927
+ }
1928
+ }
1668
1929
  async function startServer() {
1669
1930
  if (serverState.running && serverState.port !== null) {
1670
1931
  return serverState.port;
@@ -1672,6 +1933,7 @@ async function startServer() {
1672
1933
  serverState.projectRoot = resolveProjectRoot();
1673
1934
  serverState.configRoot = serverState.projectRoot;
1674
1935
  serverState.cwd = process.cwd();
1936
+ const serverHost = resolveServerHost(serverState.cwd, serverState.configRoot);
1675
1937
  portfinder.basePort = 5678;
1676
1938
  const port = await portfinder.getPortPromise();
1677
1939
  watchConfig(
@@ -1698,7 +1960,7 @@ async function startServer() {
1698
1960
  });
1699
1961
  });
1700
1962
  await new Promise((resolve2, reject) => {
1701
- serverInstance.listen(port, "127.0.0.1", () => {
1963
+ serverInstance.listen(port, serverHost, () => {
1702
1964
  serverInstance.unref();
1703
1965
  resolve2();
1704
1966
  });
@@ -1709,37 +1971,18 @@ async function startServer() {
1709
1971
  });
1710
1972
  serverState.port = port;
1711
1973
  serverState.running = true;
1712
- const portFile = path8.join(os2.tmpdir(), "inspecto.port.json");
1713
1974
  try {
1714
- let portData = {};
1715
- if (fs5.existsSync(portFile)) {
1716
- try {
1717
- portData = JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1718
- } catch (_e) {
1719
- }
1720
- }
1721
- const rootHash = crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1722
- portData[rootHash] = port;
1723
- fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1975
+ writeProjectPort(port);
1724
1976
  } catch (_e) {
1725
1977
  serverLogger4.warn("Failed to write port file:", _e);
1726
1978
  }
1727
1979
  process.once("exit", () => {
1728
1980
  try {
1729
- if (fs5.existsSync(portFile)) {
1730
- const portData = JSON.parse(fs5.readFileSync(portFile, "utf-8"));
1731
- const rootHash = crypto2.createHash("md5").update(serverState.projectRoot).digest("hex");
1732
- delete portData[rootHash];
1733
- if (Object.keys(portData).length === 0) {
1734
- fs5.unlinkSync(portFile);
1735
- } else {
1736
- fs5.writeFileSync(portFile, JSON.stringify(portData, null, 2), "utf-8");
1737
- }
1738
- }
1981
+ removeProjectPort();
1739
1982
  } catch {
1740
1983
  }
1741
1984
  });
1742
- serverLogger4.info(`server running at http://127.0.0.1:${port}`);
1985
+ serverLogger4.info(`server running at http://${serverHost}:${port}`);
1743
1986
  return port;
1744
1987
  }
1745
1988
  async function readBody(req) {
@@ -1791,7 +2034,7 @@ async function handleRequest(url, req, res) {
1791
2034
  }
1792
2035
  return;
1793
2036
  }
1794
- if (pathname === INSPECTO_API_PATHS.IDE_OPEN && req.method === "POST") {
2037
+ if ((pathname === INSPECTO_API_PATHS.SOURCE_OPEN || pathname === INSPECTO_API_PATHS.IDE_OPEN) && req.method === "POST") {
1795
2038
  let body;
1796
2039
  try {
1797
2040
  body = JSON.parse(await readBody(req));
@@ -1804,7 +2047,7 @@ async function handleRequest(url, req, res) {
1804
2047
  handleOpenFileRequest(body, serverState);
1805
2048
  } catch (err) {
1806
2049
  serverLogger4.warn(
1807
- `Security: Blocked path traversal attempt in IDE_OPEN: ${body.file}. Reason: ${err.message}`
2050
+ `Security: Blocked path traversal attempt in SOURCE_OPEN: ${body.file}. Reason: ${err.message}`
1808
2051
  );
1809
2052
  res.writeHead(403, { "Content-Type": "application/json" });
1810
2053
  res.end(JSON.stringify({ error: "Access denied: File is outside of project workspace" }));
@@ -1878,6 +2121,212 @@ async function handleRequest(url, req, res) {
1878
2121
  }
1879
2122
  return;
1880
2123
  }
2124
+ if (pathname === INSPECTO_API_PATHS.SESSION_CLAIM_NEXT && req.method === "POST") {
2125
+ try {
2126
+ const rawBody = await readBody(req);
2127
+ const body = rawBody ? JSON.parse(rawBody) : {};
2128
+ const timeoutMs = normalizeSessionClaimTimeout(
2129
+ body.timeoutMs === void 0 ? null : String(body.timeoutMs)
2130
+ );
2131
+ const result = await annotationSessionStore.claimNextSession({
2132
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
2133
+ });
2134
+ res.writeHead(200, { "Content-Type": "application/json" });
2135
+ res.end(
2136
+ JSON.stringify({
2137
+ success: true,
2138
+ timedOut: result.timedOut,
2139
+ matchedExisting: result.matchedExisting,
2140
+ ...result.event ? { event: result.event } : {},
2141
+ ...result.session ? { session: result.session } : {}
2142
+ })
2143
+ );
2144
+ } catch (e) {
2145
+ serverLogger4.error(`Error parsing session claim request:`, e);
2146
+ res.writeHead(400, { "Content-Type": "application/json" });
2147
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
2148
+ }
2149
+ return;
2150
+ }
2151
+ if (pathname === INSPECTO_API_PATHS.SESSION_EVENTS && req.method === "GET") {
2152
+ const statusParam = url.searchParams.getAll("status");
2153
+ const statuses = statusParam.length ? new Set(statusParam) : null;
2154
+ const sessionId = url.searchParams.get("sessionId")?.trim() || null;
2155
+ res.writeHead(200, {
2156
+ "Content-Type": "text/event-stream",
2157
+ "Cache-Control": "no-cache",
2158
+ Connection: "keep-alive"
2159
+ });
2160
+ res.write(`event: ready
2161
+ data: ${JSON.stringify({ ok: true })}
2162
+
2163
+ `);
2164
+ const unsubscribe = annotationSessionStore.subscribe((event) => {
2165
+ if (sessionId && event.session.id !== sessionId) {
2166
+ return;
2167
+ }
2168
+ if (statuses && !statuses.has(event.session.status)) {
2169
+ return;
2170
+ }
2171
+ res.write(formatSessionSseEvent(event));
2172
+ });
2173
+ req.on("close", () => {
2174
+ unsubscribe();
2175
+ res.end();
2176
+ });
2177
+ return;
2178
+ }
2179
+ if (pathname === INSPECTO_API_PATHS.SESSIONS && req.method === "GET") {
2180
+ const statusParam = url.searchParams.getAll("status");
2181
+ const sessions = annotationSessionStore.listSessions(
2182
+ statusParam.length ? {
2183
+ status: statusParam
2184
+ } : void 0
2185
+ );
2186
+ res.writeHead(200, { "Content-Type": "application/json" });
2187
+ res.end(JSON.stringify({ success: true, sessions }));
2188
+ return;
2189
+ }
2190
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && req.method === "GET") {
2191
+ const sessionId = pathname.substring(INSPECTO_API_PATHS.SESSIONS.length + 1);
2192
+ const session = annotationSessionStore.getSession(sessionId);
2193
+ if (!session) {
2194
+ res.writeHead(404, { "Content-Type": "application/json" });
2195
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
2196
+ return;
2197
+ }
2198
+ res.writeHead(200, { "Content-Type": "application/json" });
2199
+ res.end(JSON.stringify({ success: true, session }));
2200
+ return;
2201
+ }
2202
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX) && req.method === "POST") {
2203
+ const sessionId = pathname.slice(
2204
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
2205
+ -INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX.length
2206
+ );
2207
+ try {
2208
+ const rawBody = await readBody(req);
2209
+ const body = JSON.parse(rawBody);
2210
+ if (!isAnnotationThreadRole(body.role)) {
2211
+ res.writeHead(400, { "Content-Type": "application/json" });
2212
+ res.end(JSON.stringify({ success: false, error: "Reply role is invalid." }));
2213
+ return;
2214
+ }
2215
+ if (!body.text?.trim()) {
2216
+ res.writeHead(400, { "Content-Type": "application/json" });
2217
+ res.end(JSON.stringify({ success: false, error: "Reply text is required." }));
2218
+ return;
2219
+ }
2220
+ const session = annotationSessionStore.appendMessage(sessionId, {
2221
+ role: body.role,
2222
+ text: body.text.trim()
2223
+ });
2224
+ if (!session) {
2225
+ res.writeHead(404, { "Content-Type": "application/json" });
2226
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
2227
+ return;
2228
+ }
2229
+ res.writeHead(200, { "Content-Type": "application/json" });
2230
+ res.end(JSON.stringify({ success: true, session }));
2231
+ } catch (e) {
2232
+ serverLogger4.error(`Error parsing session reply request:`, e);
2233
+ res.writeHead(400, { "Content-Type": "application/json" });
2234
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
2235
+ }
2236
+ return;
2237
+ }
2238
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX) && req.method === "POST") {
2239
+ const sessionId = pathname.slice(
2240
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
2241
+ -INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX.length
2242
+ );
2243
+ try {
2244
+ const rawBody = await readBody(req);
2245
+ const body = rawBody ? JSON.parse(rawBody) : {};
2246
+ const message = body.message?.trim();
2247
+ const existingSession = annotationSessionStore.getSession(sessionId);
2248
+ if (!existingSession) {
2249
+ res.writeHead(404, { "Content-Type": "application/json" });
2250
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
2251
+ return;
2252
+ }
2253
+ if (!message && !hasAgentReply(existingSession)) {
2254
+ res.writeHead(400, { "Content-Type": "application/json" });
2255
+ res.end(
2256
+ JSON.stringify({
2257
+ success: false,
2258
+ error: "Resolve message is required until an agent reply is recorded."
2259
+ })
2260
+ );
2261
+ return;
2262
+ }
2263
+ if (message) {
2264
+ const repliedSession = annotationSessionStore.appendMessage(sessionId, {
2265
+ role: "agent",
2266
+ text: message
2267
+ });
2268
+ if (!repliedSession) {
2269
+ res.writeHead(404, { "Content-Type": "application/json" });
2270
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
2271
+ return;
2272
+ }
2273
+ }
2274
+ const session = annotationSessionStore.updateStatus(sessionId, "resolved");
2275
+ if (!session) {
2276
+ res.writeHead(404, { "Content-Type": "application/json" });
2277
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
2278
+ return;
2279
+ }
2280
+ res.writeHead(200, { "Content-Type": "application/json" });
2281
+ res.end(JSON.stringify({ success: true, session }));
2282
+ } catch (e) {
2283
+ serverLogger4.error(`Error parsing session resolve request:`, e);
2284
+ res.writeHead(400, { "Content-Type": "application/json" });
2285
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
2286
+ }
2287
+ return;
2288
+ }
2289
+ if (pathname.startsWith(`${INSPECTO_API_PATHS.SESSIONS}/`) && pathname.endsWith(INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX) && req.method === "POST") {
2290
+ const sessionId = pathname.slice(
2291
+ INSPECTO_API_PATHS.SESSIONS.length + 1,
2292
+ -INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX.length
2293
+ );
2294
+ try {
2295
+ const rawBody = await readBody(req);
2296
+ const body = rawBody ? JSON.parse(rawBody) : {};
2297
+ const message = body.message?.trim();
2298
+ const existingSession = annotationSessionStore.getSession(sessionId);
2299
+ if (!existingSession) {
2300
+ res.writeHead(404, { "Content-Type": "application/json" });
2301
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
2302
+ return;
2303
+ }
2304
+ if (message) {
2305
+ const repliedSession = annotationSessionStore.appendMessage(sessionId, {
2306
+ role: "agent",
2307
+ text: message
2308
+ });
2309
+ if (!repliedSession) {
2310
+ res.writeHead(404, { "Content-Type": "application/json" });
2311
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
2312
+ return;
2313
+ }
2314
+ }
2315
+ const session = annotationSessionStore.updateStatus(sessionId, "dismissed");
2316
+ if (!session) {
2317
+ res.writeHead(404, { "Content-Type": "application/json" });
2318
+ res.end(JSON.stringify({ success: false, error: "Session not found" }));
2319
+ return;
2320
+ }
2321
+ res.writeHead(200, { "Content-Type": "application/json" });
2322
+ res.end(JSON.stringify({ success: true, session }));
2323
+ } catch (e) {
2324
+ serverLogger4.error(`Error parsing session dismiss request:`, e);
2325
+ res.writeHead(400, { "Content-Type": "application/json" });
2326
+ res.end(JSON.stringify({ success: false, error: "Invalid JSON body" }));
2327
+ }
2328
+ return;
2329
+ }
1881
2330
  if (pathname.startsWith(`${INSPECTO_API_PATHS.AI_TICKET}/`) && req.method === "GET") {
1882
2331
  const ticketId = pathname.substring(INSPECTO_API_PATHS.AI_TICKET.length + 1);
1883
2332
  const payloadStr = readTicket(ticketId);
@@ -1894,7 +2343,7 @@ async function handleRequest(url, req, res) {
1894
2343
  res.end(JSON.stringify({ error: "not found" }));
1895
2344
  }
1896
2345
  async function dispatchToAi(req) {
1897
- const { location, snippet, prompt, screenshotContext } = req;
2346
+ const { location, snippet, prompt } = req;
1898
2347
  const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
1899
2348
 
1900
2349
  \`\`\`
@@ -1907,8 +2356,7 @@ ${snippet}
1907
2356
  filePath: location.file,
1908
2357
  line: location.line,
1909
2358
  column: location.column,
1910
- snippet,
1911
- ...screenshotContext ? { screenshotContext } : {}
2359
+ snippet
1912
2360
  });
1913
2361
  }
1914
2362
  function getBatchDispatchStatusCode(errorCode, success) {
@@ -1917,6 +2365,21 @@ function getBatchDispatchStatusCode(errorCode, success) {
1917
2365
  if (errorCode === "FORBIDDEN_PATH") return 403;
1918
2366
  return 500;
1919
2367
  }
2368
+ function isAnnotationThreadRole(value) {
2369
+ return value === "user" || value === "agent" || value === "system";
2370
+ }
2371
+ function formatSessionSseEvent(event) {
2372
+ return `event: ${event.type}
2373
+ data: ${JSON.stringify(event)}
2374
+
2375
+ `;
2376
+ }
2377
+ function normalizeSessionClaimTimeout(value) {
2378
+ if (!value?.trim()) return 3e4;
2379
+ const parsed = Number.parseInt(value, 10);
2380
+ if (!Number.isFinite(parsed)) return 3e4;
2381
+ return Math.max(0, Math.min(parsed, 3e5));
2382
+ }
1920
2383
 
1921
2384
  // src/injectors/utils.ts
1922
2385
  import { createRequire } from "module";
@@ -1936,26 +2399,28 @@ var resolveClientModule = () => {
1936
2399
  };
1937
2400
 
1938
2401
  // src/injectors/webpack.ts
1939
- function getWebpackHtmlScript(serverPort) {
2402
+ function getWebpackHtmlScript(serverPort, publicServerUrl) {
1940
2403
  return `
1941
2404
  window.__AI_INSPECTOR_PORT__ = ${serverPort};
1942
- window.addEventListener('load', () => {
1943
- if (window.InspectoClient) {
1944
- window.InspectoClient.mountInspector({
1945
- serverUrl: 'http://127.0.0.1:' + window.__AI_INSPECTOR_PORT__,
1946
- });
1947
- }
1948
- });
2405
+ window.__AI_INSPECTOR_SERVER_URL__ = '${publicServerUrl ?? `http://127.0.0.1:${serverPort}`}';
2406
+ window.addEventListener('load', () => {
2407
+ if (window.InspectoClient) {
2408
+ window.InspectoClient.mountInspector({
2409
+ serverUrl: window.__AI_INSPECTOR_SERVER_URL__,
2410
+ });
2411
+ }
2412
+ });
1949
2413
  `;
1950
2414
  }
1951
- function getWebpackAssetScript(serverPort) {
2415
+ function getWebpackAssetScript(serverPort, publicServerUrl) {
1952
2416
  return `
1953
2417
  if (typeof window !== 'undefined') {
1954
2418
  window.__AI_INSPECTOR_PORT__ = ${serverPort};
2419
+ window.__AI_INSPECTOR_SERVER_URL__ = '${publicServerUrl ?? `http://127.0.0.1:${serverPort}`}';
1955
2420
  const _initInspecto = () => {
1956
2421
  if (window.InspectoClient) {
1957
2422
  window.InspectoClient.mountInspector({
1958
- serverUrl: 'http://127.0.0.1:' + window.__AI_INSPECTOR_PORT__,
2423
+ serverUrl: window.__AI_INSPECTOR_SERVER_URL__,
1959
2424
  });
1960
2425
  } else {
1961
2426
  setTimeout(_initInspecto, 100);
@@ -1969,7 +2434,7 @@ if (typeof window !== 'undefined') {
1969
2434
  }
1970
2435
  `;
1971
2436
  }
1972
- function injectWebpack(compiler, serverPortFn, resolveClientModule2) {
2437
+ function injectWebpack(compiler, serverPortFn, publicServerUrlFn, resolveClientModule2) {
1973
2438
  const inspectoClientPath = resolveClientModule2();
1974
2439
  if (compiler.webpack && compiler.webpack.EntryPlugin) {
1975
2440
  new compiler.webpack.EntryPlugin(compiler.context, inspectoClientPath, {
@@ -1984,11 +2449,12 @@ function injectWebpack(compiler, serverPortFn, resolveClientModule2) {
1984
2449
  const hooks = HtmlWebpackPlugin.constructor.getHooks(compilation);
1985
2450
  hooks.alterAssetTagGroups.tapPromise("inspecto-overlay", async (data) => {
1986
2451
  const port = await serverPortFn();
2452
+ const publicServerUrl = publicServerUrlFn(port);
1987
2453
  data.headTags.unshift({
1988
2454
  tagName: "script",
1989
2455
  voidTag: false,
1990
2456
  meta: { plugin: "inspecto-overlay" },
1991
- innerHTML: getWebpackHtmlScript(port)
2457
+ innerHTML: getWebpackHtmlScript(port, publicServerUrl)
1992
2458
  });
1993
2459
  return data;
1994
2460
  });
@@ -2001,13 +2467,14 @@ function injectWebpack(compiler, serverPortFn, resolveClientModule2) {
2001
2467
  },
2002
2468
  async (assets) => {
2003
2469
  const port = await serverPortFn();
2470
+ const publicServerUrl = publicServerUrlFn(port);
2004
2471
  const mainAssetKey = Object.keys(assets).find(
2005
2472
  (key) => key.endsWith(".js") && (key.includes("main") || key.includes("app") || key.includes("umi"))
2006
2473
  );
2007
2474
  if (!mainAssetKey) return;
2008
2475
  const originalSource = assets[mainAssetKey].source();
2009
2476
  assets[mainAssetKey] = new compiler.webpack.sources.RawSource(
2010
- getWebpackAssetScript(port) + "\n" + originalSource
2477
+ getWebpackAssetScript(port, publicServerUrl) + "\n" + originalSource
2011
2478
  );
2012
2479
  }
2013
2480
  );
@@ -2041,12 +2508,13 @@ function injectRspack(compiler, serverPortFn, resolveClientModule2) {
2041
2508
  }
2042
2509
 
2043
2510
  // src/injectors/vite.ts
2044
- function getViteVirtualModuleScript(serverPort) {
2511
+ function getViteVirtualModuleScript(serverPort, publicServerUrl) {
2045
2512
  return `
2046
2513
  import { mountInspector } from '@inspecto-dev/core';
2047
2514
  window.__AI_INSPECTOR_PORT__ = ${serverPort};
2515
+ window.__AI_INSPECTOR_SERVER_URL__ = '${publicServerUrl ?? `http://127.0.0.1:${serverPort}`}';
2048
2516
  mountInspector({
2049
- serverUrl: 'http://127.0.0.1:' + window.__AI_INSPECTOR_PORT__,
2517
+ serverUrl: window.__AI_INSPECTOR_SERVER_URL__,
2050
2518
  });
2051
2519
  `;
2052
2520
  }
@@ -2079,6 +2547,11 @@ var InspectoPlugin = createUnplugin((userOptions = {}) => {
2079
2547
  }
2080
2548
  return serverPort;
2081
2549
  };
2550
+ const getPublicServerUrl = (port) => resolvePublicServerUrl({
2551
+ cwd: serverState.cwd || process.cwd(),
2552
+ configRoot: serverState.configRoot || projectRoot,
2553
+ port
2554
+ });
2082
2555
  return {
2083
2556
  name: "inspecto-overlay",
2084
2557
  enforce: "pre",
@@ -2091,7 +2564,7 @@ var InspectoPlugin = createUnplugin((userOptions = {}) => {
2091
2564
  },
2092
2565
  webpack: (compiler) => {
2093
2566
  if (isProduction) return;
2094
- injectWebpack(compiler, ensureServer, resolveClientModule);
2567
+ injectWebpack(compiler, ensureServer, getPublicServerUrl, resolveClientModule);
2095
2568
  },
2096
2569
  rspack: (compiler) => {
2097
2570
  if (isProduction) return;
@@ -2117,7 +2590,10 @@ var InspectoPlugin = createUnplugin((userOptions = {}) => {
2117
2590
  },
2118
2591
  load(id) {
2119
2592
  if (id === VITE_VIRTUAL_MODULE_ID) {
2120
- return getViteVirtualModuleScript(serverPort ?? DEFAULT_PORT);
2593
+ return getViteVirtualModuleScript(
2594
+ serverPort ?? DEFAULT_PORT,
2595
+ getPublicServerUrl(serverPort ?? DEFAULT_PORT)
2596
+ );
2121
2597
  }
2122
2598
  return null;
2123
2599
  },