@comty.io/core 0.1.1 → 0.1.3

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.
package/src/browser.js CHANGED
@@ -9,17 +9,7 @@ const instanceKey = Symbol.for("comty.sdk.instance");
9
9
  /** Initialize once per document; safe to import and call during server rendering. */
10
10
  export function initComty(options = {}) {
11
11
  if (typeof window === "undefined" || typeof document === "undefined") return Promise.resolve();
12
- const isLocalSite = ["localhost", "127.0.0.1", "[::1]"].includes(window.location.hostname);
13
- const agent = options.agent;
14
- if (agent && (!isLocalSite || typeof agent.token !== "string" || !/^[a-f0-9]{64}$/.test(agent.token))) {
15
- return Promise.reject(new TypeError("Comty: invalid local companion configuration"));
16
- }
17
- if (agent) {
18
- try {
19
- if (new URL(agent.url).origin !== window.location.origin) throw new Error();
20
- } catch { return Promise.reject(new TypeError("Comty: companion must share the page origin")); }
21
- }
22
- const projectKey = options.projectKey || (agent ? "local" : undefined);
12
+ const projectKey = options.projectKey;
23
13
  const apiUrl = options.apiUrl || "http://localhost:4000";
24
14
  if (typeof projectKey !== "string" || !projectKey.trim()) {
25
15
  return Promise.reject(new TypeError("Comty: projectKey is required"));
@@ -31,14 +21,12 @@ export function initComty(options = {}) {
31
21
  if (!["http:", "https:"].includes(parsedApi.protocol)) {
32
22
  return Promise.reject(new TypeError("Comty: apiUrl must be an absolute HTTP(S) URL"));
33
23
  }
34
- const configuration = JSON.stringify([projectKey, apiUrl, agent || null]);
24
+ const configuration = JSON.stringify([projectKey, apiUrl]);
35
25
  const existing = window[instanceKey];
36
26
  if (existing) {
37
27
  return existing.configuration === configuration ? existing.ready
38
28
  : Promise.reject(new Error("Comty: already initialized with different options"));
39
29
  }
40
- const agentBridgeUrl = agent?.url;
41
- const localOnly = Boolean(agent && !options.projectKey);
42
30
  const ready = new Promise((resolve, reject) => {
43
31
  const mount = () => {
44
32
  try { resolve(mountComty()); } catch (error) { reject(error); }
@@ -52,19 +40,10 @@ export function initComty(options = {}) {
52
40
 
53
41
  function mountComty() {
54
42
  let projectId = null;
55
- let localTasks = [];
56
- let localApprovals = [];
57
- let agentPanelOpen = false;
58
- let agentPanelSignature = "";
59
- let agentPanelTransition = 0;
60
- let agentConnectionError = "";
61
- let agentActionError = "";
62
- const agentPendingActions = new Set();
63
43
  const session = {
64
44
  mode: "off",
65
- get comments() { return this.mode === "browse" || this.mode === "edit" || this.mode === "agent"; },
66
- get edit() { return this.mode === "edit" || this.mode === "agent"; },
67
- get agent() { return this.mode === "agent"; },
45
+ get comments() { return this.mode === "browse" || this.mode === "edit"; },
46
+ get edit() { return this.mode === "edit"; },
68
47
  get inspect() { return this.mode === "inspect"; },
69
48
  };
70
49
  const selectedStatusFilters = new Set();
@@ -85,10 +64,9 @@ export function initComty(options = {}) {
85
64
  const tokenStorageKey = `comty:${projectKey}:token`;
86
65
  let sdkToken = "";
87
66
  const activeHoverCommentIds = new Set();
88
- /** @type {null | 'edit' | 'browse' | 'agent'} */
67
+ /** @type {null | 'edit' | 'browse'} */
89
68
  let sessionEntry = null;
90
69
  const placedPins = new Map();
91
- const workingOutlines = new Map();
92
70
  const placementDiagnostics = new Map();
93
71
  let inspectedElement = null;
94
72
  let selectedElement = null;
@@ -135,7 +113,6 @@ export function initComty(options = {}) {
135
113
  const root = document.createElement("div");
136
114
  root.id = "comty-root";
137
115
  document.body.appendChild(root);
138
- if (agent) document.documentElement.classList.add("comty-agent-enabled");
139
116
 
140
117
  const style = document.createElement("style");
141
118
  style.innerHTML = getComtyStyle();
@@ -183,40 +160,20 @@ export function initComty(options = {}) {
183
160
  const inspectorBtn = toolButton("Inspect · Shift I", '<path d="M8 3H5a2 2 0 0 0-2 2v3m13-5h3a2 2 0 0 1 2 2v3M3 16v3a2 2 0 0 0 2 2h3m13-5v3a2 2 0 0 1-2 2h-3"/><path d="m9 9 8 4-4 1-1 4Z"/>', () => setSession("inspect"));
184
161
  const btn = toolButton("Comment · Shift C", '<path d="M21 11.5a8.4 8.4 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.4 8.4 0 0 1-3.8-.9L3 21l1.9-5.7a8.4 8.4 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6A8.4 8.4 0 0 1 12.5 3h.5a8.5 8.5 0 0 1 8 8v.5Z"/>', () => {
185
162
  if (!canWrite) showAuthPrompt();
186
- else if (session.agent || session.inspect || !isSessionActive()) startEditSession();
163
+ else if (session.inspect || !isSessionActive()) startEditSession();
187
164
  else if (!session.edit) setSession("edit", { entry: sessionEntry });
188
165
  });
189
166
  btn.id = "comty-toggle";
190
- const agentBtn = isLocalSite
191
- ? toolButton("Agent task · localhost only", '<path d="M8 7 3 12l5 5m8-10 5 5-5 5M14 4l-4 16"/>', () => {
192
- startAgentSession();
193
- })
194
- : null;
195
167
  const divider = document.createElement("span");
196
168
  divider.className = "comty-tool-divider";
197
169
  toolbar.appendChild(divider);
198
170
  const commentTools = document.createElement("div");
199
171
  commentTools.className = "comty-tool-context";
200
172
  toolbar.appendChild(commentTools);
201
- const agentTools = agentBtn ? document.createElement("div") : null;
202
- if (agentTools) {
203
- agentTools.className = "comty-tool-context";
204
- agentTools.hidden = true;
205
- toolbar.appendChild(agentTools);
206
- }
207
- const agentHistoryBtn = agentTools ? toolButton("Local agent history", '<path d="M3 12a9 9 0 1 0 3-6.7M3 4v4h4"/><path d="M12 7v5l3 2"/>', () => {
208
- if (!agent) { showAgentBridgeError(); return; }
209
- agentPanelOpen = !agentPanelOpen;
210
- renderAgentPanel();
211
- }) : null;
212
- if (agentHistoryBtn) {
213
- agentHistoryBtn.setAttribute("aria-expanded", "false");
214
- agentTools.appendChild(agentHistoryBtn);
215
- }
216
173
  const primaryTools = document.createElement("div");
217
174
  primaryTools.className = "comty-tool-primary";
218
175
  toolbar.insertBefore(primaryTools, browseBtn);
219
- for (const button of [browseBtn, inspectorBtn, btn, agentBtn].filter(Boolean)) primaryTools.appendChild(button);
176
+ for (const button of [browseBtn, inspectorBtn, btn]) primaryTools.appendChild(button);
220
177
  const inspectorTools = document.createElement("div");
221
178
  inspectorTools.className = "comty-tool-context";
222
179
  inspectorTools.hidden = true;
@@ -1065,50 +1022,6 @@ export function initComty(options = {}) {
1065
1022
  let toolbarMotionFrame = 0;
1066
1023
  let toolbarAnimations = [];
1067
1024
  const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
1068
- let workingIndicatorTimer = 0;
1069
- function scheduleWorkingIndicators() {
1070
- if (workingIndicatorTimer || reducedMotion.matches || document.hidden) return;
1071
- workingIndicatorTimer = setTimeout(() => {
1072
- workingIndicatorTimer = 0;
1073
- if (!root.isConnected) return;
1074
- const indicators = root.querySelectorAll('.comty-working-indicator');
1075
- if (!indicators.length) return;
1076
- for (const indicator of indicators) {
1077
- const previous = Number(indicator.dataset.activeDots.split(',')[0]);
1078
- const first = (previous + 1 + Math.floor(Math.random() * 8)) % 9;
1079
- const second = (first + 1 + Math.floor(Math.random() * 8)) % 9;
1080
- indicator.dataset.activeDots = `${first},${second}`;
1081
- [...indicator.children].forEach((dot, index) => dot.toggleAttribute('data-active', index === first || index === second));
1082
- }
1083
- scheduleWorkingIndicators();
1084
- }, 600);
1085
- }
1086
- function createWorkingIndicator() {
1087
- const indicator = document.createElement('span');
1088
- indicator.className = 'comty-working-indicator';
1089
- indicator.setAttribute('aria-hidden', 'true');
1090
- indicator.dataset.activeDots = '3,5';
1091
- for (let index = 0; index < 9; index++) {
1092
- const dot = document.createElement('i');
1093
- dot.toggleAttribute('data-active', index === 3 || index === 5);
1094
- indicator.appendChild(dot);
1095
- }
1096
- scheduleWorkingIndicators();
1097
- return indicator;
1098
- }
1099
- function resetWorkingIndicators() {
1100
- clearTimeout(workingIndicatorTimer);
1101
- workingIndicatorTimer = 0;
1102
- if (reducedMotion.matches) {
1103
- for (const indicator of root.querySelectorAll('.comty-working-indicator')) {
1104
- indicator.dataset.activeDots = '3,5';
1105
- [...indicator.children].forEach((dot, index) => dot.toggleAttribute('data-active', index === 3 || index === 5));
1106
- }
1107
- }
1108
- if (root.querySelector('.comty-working-indicator')) scheduleWorkingIndicators();
1109
- }
1110
- reducedMotion.addEventListener('change', resetWorkingIndicators);
1111
- document.addEventListener('visibilitychange', resetWorkingIndicators);
1112
1025
  const clampToolbar = (value, size, viewport) => Math.max(8, Math.min(value, viewport - size - 8));
1113
1026
  function rememberToolbarPosition(left, top) {
1114
1027
  const width = toolbar.offsetWidth, height = toolbar.offsetHeight;
@@ -1221,7 +1134,7 @@ export function initComty(options = {}) {
1221
1134
 
1222
1135
  function prepareToolbarMotion() {
1223
1136
  if (toolbarMotionFrame) return;
1224
- const nodes = [toolbarSurface, dragHandle, primaryTools, commentTools, agentTools, inspectorTools].filter(Boolean);
1137
+ const nodes = [toolbarSurface, dragHandle, primaryTools, commentTools, inspectorTools].filter(Boolean);
1225
1138
  // Move each group's background with its icons, including when interrupted.
1226
1139
  const before = new Map(nodes.filter(node => !node.hidden).map(node => [node, {
1227
1140
  rect: node.getBoundingClientRect(), opacity: getComputedStyle(node).opacity,
@@ -1464,7 +1377,7 @@ export function initComty(options = {}) {
1464
1377
  return;
1465
1378
  }
1466
1379
 
1467
- if ((session.edit && mode === "browse") || (session.agent && mode === "edit")) {
1380
+ if (session.edit && mode === "browse") {
1468
1381
  closeBubbles();
1469
1382
  }
1470
1383
  const hadComments = session.comments;
@@ -1478,7 +1391,6 @@ export function initComty(options = {}) {
1478
1391
  } else {
1479
1392
  hideInspector();
1480
1393
  }
1481
- if (session.agent && sidepanelOpen) setSidepanelOpen(false);
1482
1394
  syncToolbar();
1483
1395
  ensureLiveTracking();
1484
1396
  if (renderComments || !hadComments) renderCachedComments();
@@ -1491,10 +1403,6 @@ export function initComty(options = {}) {
1491
1403
  }
1492
1404
 
1493
1405
  function showAuthPrompt(forbidden = false) {
1494
- if (localOnly) {
1495
- showAgentBridgeError("Shared comments need a Comty project key. Use the Agent tool for local tasks without signing in.");
1496
- return;
1497
- }
1498
1406
  document.querySelector(".comty-auth-backdrop")?.remove();
1499
1407
  const opener = document.activeElement;
1500
1408
  const backdrop = document.createElement("div");
@@ -1556,17 +1464,14 @@ export function initComty(options = {}) {
1556
1464
  }
1557
1465
 
1558
1466
  function syncToolbar() {
1559
- if (commentTools.hidden !== (session.inspect || session.agent) || inspectorTools.hidden !== !session.inspect || (agentTools && agentTools.hidden !== !session.agent)) prepareToolbarMotion();
1467
+ if (commentTools.hidden !== session.inspect || inspectorTools.hidden !== !session.inspect) prepareToolbarMotion();
1560
1468
  root.classList.toggle("comty-active", session.comments || session.edit || sidepanelOpen || session.inspect);
1561
1469
  browseBtn.setAttribute("aria-pressed", String(!session.edit && !session.inspect));
1562
1470
  inspectorBtn.setAttribute("aria-pressed", String(session.inspect));
1563
- btn.setAttribute("aria-pressed", String(session.edit && !session.agent));
1564
- agentBtn?.setAttribute("aria-pressed", String(session.edit && session.agent));
1565
- commentTools.hidden = session.inspect || session.agent;
1566
- if (agentTools) agentTools.hidden = !session.agent;
1471
+ btn.setAttribute("aria-pressed", String(session.edit));
1472
+ commentTools.hidden = session.inspect;
1567
1473
  inspectorTools.hidden = !session.inspect;
1568
1474
  syncInspectorTools();
1569
- renderAgentPanel();
1570
1475
  }
1571
1476
 
1572
1477
  const inspectionLayer = document.createElement("div");
@@ -2014,13 +1919,6 @@ export function initComty(options = {}) {
2014
1919
  setSession("edit", { renderComments: true });
2015
1920
  }
2016
1921
 
2017
- function startAgentSession() {
2018
- if (!isLocalSite) return;
2019
- if (session.agent) return;
2020
- closeBubbles();
2021
- setSession("agent", { renderComments: true });
2022
- }
2023
-
2024
1922
  function toggleSidepanel() {
2025
1923
  if (sidepanelOpen) {
2026
1924
  setSidepanelOpen(false);
@@ -2044,7 +1942,7 @@ export function initComty(options = {}) {
2044
1942
  showAuthPrompt();
2045
1943
  return;
2046
1944
  }
2047
- if (session.agent || session.inspect || !isSessionActive()) {
1945
+ if (session.inspect || !isSessionActive()) {
2048
1946
  startEditSession();
2049
1947
  return;
2050
1948
  }
@@ -2819,7 +2717,7 @@ export function initComty(options = {}) {
2819
2717
 
2820
2718
  const textarea = document.createElement("textarea");
2821
2719
  textarea.className = "comty-create-textarea";
2822
- textarea.placeholder = payload.kind === "agent" ? "Describe the change for your agent" : "Add a comment";
2720
+ textarea.placeholder = "Add a comment";
2823
2721
  textarea.setAttribute("aria-label", textarea.placeholder);
2824
2722
  textarea.rows = 1;
2825
2723
 
@@ -2828,7 +2726,7 @@ export function initComty(options = {}) {
2828
2726
 
2829
2727
  const submitBtn = document.createElement("button");
2830
2728
  submitBtn.className = "comty-btn-submit";
2831
- submitBtn.setAttribute("aria-label", payload.kind === "agent" ? "Send agent task" : "Post comment");
2729
+ submitBtn.setAttribute("aria-label", "Post comment");
2832
2730
  submitBtn.innerHTML = `
2833
2731
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2834
2732
  <path d="M12 19V5M5 12l7-7 7 7"/>
@@ -2842,22 +2740,18 @@ export function initComty(options = {}) {
2842
2740
  submitBtn.disabled = true;
2843
2741
  textarea.disabled = true;
2844
2742
 
2845
- if (payload.kind === "agent" && !agent) {
2846
- showAgentBridgeError(); submitBtn.disabled = false; textarea.disabled = false; return;
2847
- }
2848
2743
  payload.content = content;
2849
2744
  textarea.textContent = content;
2850
2745
 
2851
2746
  let post;
2852
2747
  try {
2853
- post = await fetch(payload.kind === "agent" ? `${agentBridgeUrl}/tasks` : `${apiUrl}/api/comments`, {
2748
+ post = await fetch(`${apiUrl}/api/comments`, {
2854
2749
  method: "POST",
2855
- headers: payload.kind === "agent" ? agentHeaders() : getSdkHeaders({ "Content-Type": "application/json" }),
2856
- body: JSON.stringify(payload.kind === "agent" ? { ...payload, projectKey } : payload),
2750
+ headers: getSdkHeaders({ "Content-Type": "application/json" }),
2751
+ body: JSON.stringify(payload),
2857
2752
  });
2858
2753
  } catch (error) {
2859
2754
  console.error("Comty: save failed", error);
2860
- if (payload.kind === "agent") showAgentBridgeError();
2861
2755
  submitBtn.disabled = false;
2862
2756
  textarea.disabled = false;
2863
2757
  return;
@@ -2865,15 +2759,14 @@ export function initComty(options = {}) {
2865
2759
 
2866
2760
  if (!post.ok) {
2867
2761
  console.error("Comty: save failed", await post.text());
2868
- if (payload.kind === "agent") showAgentBridgeError();
2869
2762
  submitBtn.disabled = false;
2870
2763
  textarea.disabled = false;
2871
2764
  return;
2872
2765
  }
2873
2766
 
2874
2767
  const data = await post.json();
2875
- const commentId = payload.kind === "agent" ? data?.task?.id : data?.id;
2876
- const captureNodes = commentId && payload.kind !== "agent"
2768
+ const commentId = data?.id;
2769
+ const captureNodes = commentId
2877
2770
  ? createCaptureAnnotation(pin, bubble, content)
2878
2771
  : [];
2879
2772
  bubble.remove();
@@ -2881,10 +2774,9 @@ export function initComty(options = {}) {
2881
2774
  draftAnchor = null;
2882
2775
  hideInspector();
2883
2776
  if (commentId) {
2884
- if (payload.kind === "agent") showAgentTaskToast(String(commentId));
2885
- else addLocalComment(String(commentId), payload);
2777
+ addLocalComment(String(commentId), payload);
2886
2778
  void loadComments({ force: true });
2887
- if (payload.kind !== "agent") void uploadCommentScreenshot(String(commentId), payload, captureNodes);
2779
+ void uploadCommentScreenshot(String(commentId), payload, captureNodes);
2888
2780
  } else {
2889
2781
  void loadComments({ force: true });
2890
2782
  }
@@ -2941,223 +2833,6 @@ export function initComty(options = {}) {
2941
2833
  setTimeout(() => textarea.focus(), 10);
2942
2834
  }
2943
2835
 
2944
- function agentHeaders() { return { "Content-Type": "application/json", "X-Comty-Token": agent?.token || "" }; }
2945
-
2946
- function showAgentBridgeError(message = "Start your app with comty dev to use the local agent. Check the terminal if it is already running.") {
2947
- root.querySelectorAll(".comty-agent-task-toast").forEach((node) => node.remove());
2948
- const toast = document.createElement("div");
2949
- toast.className = "comty-auth-toast comty-agent-task-toast";
2950
- toast.setAttribute("role", "alert");
2951
- const issue = describeAgentError(message);
2952
- toast.textContent = issue.title + ". " + issue.description;
2953
- root.appendChild(toast);
2954
- setTimeout(() => toast.remove(), 5000);
2955
- }
2956
-
2957
- function showAgentTaskToast() {
2958
- agentPanelOpen = true;
2959
- agentPanelSignature = "";
2960
- renderAgentPanel();
2961
- }
2962
-
2963
- function describeAgentError(error) {
2964
- const raw = typeof error === 'string' ? error : JSON.stringify(error, null, 2) || 'Unknown error';
2965
- let value = error;
2966
- for (let depth = 0; depth < 8; depth++) {
2967
- if (typeof value === 'string') {
2968
- try { value = JSON.parse(value); continue; } catch {}
2969
- const start = value.indexOf('{'), end = value.lastIndexOf('}');
2970
- if (start >= 0 && end > start) { try { value = JSON.parse(value.slice(start, end + 1)); continue; } catch {} }
2971
- break;
2972
- }
2973
- if (!value || typeof value !== 'object') break;
2974
- value = value.error || value.message || value.cause || value.stderr || value;
2975
- }
2976
- const message = typeof value === 'string' ? value : '';
2977
- const source = raw + ' ' + message;
2978
- const agentName = /codex/i.test(source) ? 'Codex' : /claude/i.test(source) ? 'Claude Code' : /cursor/i.test(source) ? 'Cursor' : 'your coding agent';
2979
- let title, description;
2980
- if (/requires a newer version|upgrade.*latest|outdated|unsupported.*version/i.test(source)) {
2981
- title = 'Agent update required'; description = `Update ${agentName}, restart your local development session, then retry this task.`;
2982
- } else if (/insufficient_quota|quota exceeded|credit balance|billing|usage limit/i.test(source)) {
2983
- title = 'Usage limit reached'; description = 'Check your agent account’s usage allowance or billing, then retry when capacity is available.';
2984
- } else if (/rate.?limit|too many requests|\b429\b/i.test(source)) {
2985
- title = 'Too many requests'; description = 'The agent is temporarily rate limited. Wait a moment before retrying.';
2986
- } else if (/unauthorized|unauthenticated|authentication|not logged|sign.?in|log.?in|invalid.api.key|\b401\b/i.test(source)) {
2987
- title = 'Sign in to your agent'; description = `Open ${agentName} and sign in again, then retry this task.`;
2988
- } else if (/model.*(?:not found|does not exist|unavailable|not supported)|unsupported model|model_not_found/i.test(source)) {
2989
- title = 'Model unavailable'; description = 'Choose a model available to your agent account in the local Comty configuration, then restart and retry.';
2990
- } else if (/disconnected|ECONNREFUSED|ENOTFOUND|network|failed to fetch|development server stopped/i.test(source)) {
2991
- title = 'Connection lost'; description = 'Check that your local development server and coding agent are running. Reconnect before retrying; your task history is kept.';
2992
- } else if (/timed? ?out|timeout|exceeded.*minutes/i.test(source)) {
2993
- title = 'Task timed out'; description = 'Review any changes already made, then retry with a smaller, more specific request.';
2994
- } else if (/permission|denied|forbidden|\b403\b/i.test(source)) {
2995
- title = 'Permission needed'; description = 'Check the agent’s access to this project and any pending approval before retrying.';
2996
- } else if (/ENOENT|command not found|not installed|executable.*not found/i.test(source)) {
2997
- title = 'Agent not found'; description = `Install or configure ${agentName} in your local development environment, then restart and retry.`;
2998
- } else if (/completion was not verified/i.test(source)) {
2999
- title = 'Result needs checking'; description = 'The agent could not confirm completion. Review the changes before retrying.';
3000
- } else {
3001
- title = 'Task could not finish';
3002
- description = message && !/[{}]|\[object Object\]/.test(message) ? message.slice(0, 300) : 'The agent reported an unexpected error. Review the technical details and any changes before retrying.';
3003
- }
3004
- return { title, description, raw, message };
3005
- }
3006
-
3007
- function renderAgentPanel() {
3008
- if (!agent) return;
3009
- let panel = root.querySelector(".comty-agent-panel");
3010
- if (!panel) {
3011
- panel = document.createElement("section");
3012
- panel.className = "comty-agent-panel";
3013
- panel.setAttribute("aria-label", "Local agent tasks");
3014
- panel.hidden = true;
3015
- // Keep the dock above inspection overlays and below the toolbar.
3016
- root.insertBefore(panel, toolbar);
3017
- }
3018
- const open = session.agent && agentPanelOpen;
3019
- if (open) {
3020
- if (panel.hidden) {
3021
- const rect = toolbar.getBoundingClientRect();
3022
- panel.dataset.side = rect.left + rect.width / 2 < innerWidth / 2 ? "left" : "right";
3023
- document.documentElement.dataset.comtyAgentSide = panel.dataset.side;
3024
- panel.dataset.closed = "";
3025
- panel.hidden = false;
3026
- panel.getBoundingClientRect();
3027
- }
3028
- if (panel.hasAttribute("data-closed")) { agentPanelTransition++; delete panel.dataset.closed; }
3029
- } else if (!panel.hidden && !panel.hasAttribute("data-closed")) {
3030
- const transition = ++agentPanelTransition;
3031
- panel.dataset.closed = "";
3032
- const finish = () => { if (transition === agentPanelTransition && panel.hasAttribute("data-closed")) panel.hidden = true; };
3033
- if (reducedMotion.matches) finish();
3034
- else setTimeout(finish, parseFloat(getComputedStyle(root).getPropertyValue("--comty-motion-duration")));
3035
- }
3036
- panel.inert = !open;
3037
- panel.setAttribute("aria-hidden", String(!open));
3038
- document.documentElement.classList.toggle("comty-agent-docked", open);
3039
- agentHistoryBtn?.setAttribute("aria-expanded", String(open));
3040
- agentHistoryBtn?.setAttribute("aria-pressed", String(open));
3041
- const signature = JSON.stringify([localTasks, localApprovals, agentConnectionError, agentActionError, agentPanelOpen]);
3042
- if (signature === agentPanelSignature) return;
3043
- agentPanelSignature = signature;
3044
- root.querySelector('.comty-agent-error-popover')?.remove();
3045
- const expanded = new Set([...panel.querySelectorAll('details[data-detail-key][open]')].map(node => node.dataset.detailKey));
3046
- const focusKey = panel.contains(document.activeElement) ? document.activeElement.dataset.focusKey : null;
3047
- const scrollTop = panel.querySelector('.comty-agent-panel-content')?.scrollTop || 0;
3048
- const node = (tag, className, text) => {
3049
- const element = document.createElement(tag);
3050
- if (className) element.className = className;
3051
- if (text != null) element.textContent = text;
3052
- return element;
3053
- };
3054
- const codeIcon = '<path d="m8 6-6 6 6 6m8-12 6 6-6 6m-3-15-2 18"/>';
3055
- const brand = () => { const element = node('span', 'comty-agent-brand'); element.innerHTML = icon(codeIcon); element.setAttribute('aria-hidden', 'true'); return element; };
3056
- panel.replaceChildren();
3057
- const header = node('header', 'comty-agent-panel-header');
3058
- const heading = node('div', 'comty-agent-heading');
3059
- heading.append(node('strong', '', 'Modifications'));
3060
- const toggle = node('button', 'comty-agent-action comty-agent-close'); toggle.type = 'button';
3061
- toggle.innerHTML = icon('<path d="m6 6 12 12M6 18 18 6"/>');
3062
- toggle.setAttribute('aria-label', 'Close local agent sidebar'); toggle.title = 'Close sidebar'; toggle.dataset.focusKey = 'close';
3063
- toggle.onclick = () => { agentPanelOpen = false; renderAgentPanel(); agentHistoryBtn?.focus({ preventScroll: true }); };
3064
- header.append(heading, toggle); panel.appendChild(header);
3065
- const content = node('div', 'comty-agent-panel-content'); panel.appendChild(content);
3066
- const technicalDetails = (parent, raw, key) => {
3067
- const details = node('details', 'comty-agent-details'); details.dataset.detailKey = key; details.open = expanded.has(key);
3068
- const summary = node('summary', '', 'Technical details'); summary.dataset.focusKey = 'details-' + key;
3069
- details.append(summary, node('pre', '', raw)); parent.appendChild(details);
3070
- };
3071
- const errorMessage = (parent, error, key) => {
3072
- const issue = describeAgentError(error);
3073
- const message = node('div', 'comty-agent-error');
3074
- message.append(node('strong', '', issue.title), node('p', '', issue.description));
3075
- parent.appendChild(message); technicalDetails(parent, issue.raw, key);
3076
- };
3077
- if (agentConnectionError || agentActionError) {
3078
- const banner = node('div', 'comty-agent-item comty-agent-banner'); banner.setAttribute('role', 'alert');
3079
- errorMessage(banner, agentConnectionError || agentActionError, 'connection'); content.appendChild(banner);
3080
- }
3081
- const action = (parent, label, path, input = {}, primary = false) => {
3082
- const button = node('button', 'comty-agent-action', label); button.type = 'button';
3083
- const key = path + JSON.stringify(input); button.dataset.focusKey = key;
3084
- button.disabled = agentPendingActions.has(key);
3085
- if (primary) button.dataset.primary = '';
3086
- button.onclick = async () => {
3087
- if (agentPendingActions.has(key)) return;
3088
- agentActionError = '';
3089
- agentPendingActions.add(key); button.disabled = true; button.textContent = 'Please wait…';
3090
- try {
3091
- const response = await fetch(`${agentBridgeUrl}/${path}`, { method: 'POST', headers: agentHeaders(), body: JSON.stringify(input) });
3092
- if (!response.ok) {
3093
- const body = await response.json().catch(() => null);
3094
- throw new Error(typeof body?.error === 'string' ? body.error : JSON.stringify(body?.error || { status: response.status, message: 'Request failed' }));
3095
- }
3096
- await loadComments({ force: true });
3097
- } catch (error) { agentActionError = error.message; }
3098
- finally { agentPendingActions.delete(key); agentPanelSignature = ''; renderAgentPanel(); }
3099
- };
3100
- parent.appendChild(button);
3101
- };
3102
- for (const approval of localApprovals) {
3103
- const section = node('div', 'comty-agent-item');
3104
- section.append(node('h3', '', 'Approval required'));
3105
- const task = localTasks.find(task => task.id === approval.taskId);
3106
- if (task) section.appendChild(node('p', '', task.content));
3107
- section.appendChild(node('p', '', 'Review this operation before allowing the agent to continue.'));
3108
- const operation = approval.details?.command || approval.details?.file_path || approval.details?.path;
3109
- if (typeof operation === 'string' || Array.isArray(operation)) section.appendChild(node('pre', '', Array.isArray(operation) ? operation.join(' ') : operation));
3110
- technicalDetails(section, JSON.stringify(approval.details ?? {}, null, 2), 'approval-' + approval.id);
3111
- const actions = node('div', 'comty-agent-actions');
3112
- action(actions, 'Allow once', `approvals/${approval.id}`, { allow: true }, true);
3113
- action(actions, 'Deny', `approvals/${approval.id}`, { allow: false });
3114
- section.appendChild(actions); content.appendChild(section);
3115
- }
3116
- const list = node('div', 'comty-agent-list'); content.appendChild(list);
3117
- const hideErrorPopover = () => root.querySelector('.comty-agent-error-popover')?.remove();
3118
- content.addEventListener('scroll', hideErrorPopover, { passive: true });
3119
- for (const [index, task] of [...localTasks].reverse().entries()) {
3120
- const failed = Boolean(task.error) || task.status === 'in_review';
3121
- const state = failed ? 'error' : task.status === 'done' ? 'success' : task.status === 'cancel' ? 'cancel' : 'working';
3122
- const row = node('div', 'comty-agent-row'); row.dataset.taskId = task.id;
3123
- const dot = state === 'working' ? createWorkingIndicator() : node('span', 'comty-agent-dot');
3124
- dot.dataset.status = state; dot.setAttribute('aria-hidden', 'true');
3125
- const name = node('span', 'comty-agent-row-name', task.content);
3126
- row.append(dot, name);
3127
- if (state === 'working') row.appendChild(node('span', 'comty-agent-row-state', task.status === 'todo' ? 'Starting' : 'Working'));
3128
- const actions = node('div', 'comty-agent-row-actions');
3129
- if (['in_review', 'cancel'].includes(task.status)) action(actions, 'Retry', `tasks/${task.id}/retry`);
3130
- if (['todo', 'in_progress'].includes(task.status)) action(actions, 'Cancel', `tasks/${task.id}/cancel`);
3131
- if (actions.childElementCount) row.appendChild(actions);
3132
- row.setAttribute('aria-label', `${task.content}, ${state === 'error' ? 'failed' : state === 'success' ? 'completed' : state === 'cancel' ? 'cancelled' : task.status === 'todo' ? 'starting' : 'working'}`);
3133
- if (failed) {
3134
- const tooltipId = `comty-agent-error-${index}`;
3135
- const showErrorPopover = () => {
3136
- hideErrorPopover();
3137
- const issue = describeAgentError(task.error || task.result?.summary || task.progress || 'Completion was not verified');
3138
- const popover = node('div', 'comty-agent-error-popover'); popover.id = tooltipId; popover.setAttribute('role', 'tooltip');
3139
- popover.append(node('strong', '', task.content), node('p', '', issue.message || issue.description));
3140
- root.appendChild(popover);
3141
- const rowRect = row.getBoundingClientRect();
3142
- const popoverRect = popover.getBoundingClientRect();
3143
- const left = panel.dataset.side === 'right' ? rowRect.left - popoverRect.width - 8 : rowRect.right + 8;
3144
- popover.style.left = `${Math.max(8, Math.min(left, innerWidth - popoverRect.width - 8))}px`;
3145
- popover.style.top = `${Math.max(8, Math.min(rowRect.top - 4, innerHeight - popoverRect.height - 8))}px`;
3146
- };
3147
- row.addEventListener('mouseenter', showErrorPopover);
3148
- row.addEventListener('mouseleave', hideErrorPopover);
3149
- }
3150
- list.appendChild(row);
3151
- }
3152
- if (!localTasks.length && !localApprovals.length) {
3153
- const empty = node('div', 'comty-agent-empty');
3154
- empty.append(brand(), node('strong', '', localTasks.length ? 'All caught up' : 'What shall we change?'), node('p', '', 'Point at an element on the page and describe the change you want.'));
3155
- content.appendChild(empty);
3156
- }
3157
- content.scrollTop = scrollTop;
3158
- if (focusKey && open) [...panel.querySelectorAll('[data-focus-key]')].find(element => element.dataset.focusKey === focusKey)?.focus({ preventScroll: true });
3159
- }
3160
-
3161
2836
  function setSidepanelOpen(open) {
3162
2837
  sidepanelOpen = open;
3163
2838
  sidepanel.classList.toggle("comty-sidepanel-open", sidepanelOpen);
@@ -3211,7 +2886,6 @@ export function initComty(options = {}) {
3211
2886
  ? sidepanelSearch.value.trim().toLowerCase()
3212
2887
  : "";
3213
2888
  let list = cachedComments.filter((comment) => {
3214
- if (comment.local_agent || comment.kind === "agent") return false;
3215
2889
  if (
3216
2890
  search &&
3217
2891
  !`${comment.content ?? ""} ${getCommentLabel(comment)}`
@@ -3397,15 +3071,14 @@ export function initComty(options = {}) {
3397
3071
  createStatusFilters();
3398
3072
 
3399
3073
  async function updateCommentStatus(comment, status) {
3400
- if (comment?.local_agent || !projectId || !comment?.id) return false;
3074
+ if (!projectId || !comment?.id) return false;
3401
3075
 
3402
3076
  let res;
3403
3077
  try {
3404
- const local = comment.local_agent === true;
3405
- res = await fetch(local ? `${agentBridgeUrl}/tasks/${encodeURIComponent(comment.id)}/status` : `${apiUrl}/api/comments/${comment.id}/status`, {
3078
+ res = await fetch(`${apiUrl}/api/comments/${comment.id}/status`, {
3406
3079
  method: "POST",
3407
- headers: local ? { "Content-Type": "application/json" } : getSdkHeaders({ "Content-Type": "application/json" }),
3408
- body: JSON.stringify(local ? { status } : {
3080
+ headers: getSdkHeaders({ "Content-Type": "application/json" }),
3081
+ body: JSON.stringify({
3409
3082
  projectKey,
3410
3083
  host: window.location.host,
3411
3084
  status,
@@ -3422,7 +3095,7 @@ export function initComty(options = {}) {
3422
3095
  }
3423
3096
 
3424
3097
  const data = await res.json();
3425
- comment.status = getCommentStatus(data.task?.status ?? data.status).value;
3098
+ comment.status = getCommentStatus(data.status).value;
3426
3099
  return comment.status;
3427
3100
  }
3428
3101
 
@@ -3610,7 +3283,7 @@ export function initComty(options = {}) {
3610
3283
  const openCommentThread = createCommentThread({
3611
3284
  root, apiUrl, projectKey, commentStatuses, selectedStatusFilters, placedPins,
3612
3285
  closeBubbles, getCommentStatus, updateCommentStatus, applyStatusDot,
3613
- renderSidepanelComments, applyStatusFilters, loadComments, createThreadItem,
3286
+ renderSidepanelComments, applyStatusFilters, createThreadItem,
3614
3287
  isOwnCommentEntry, updateCommentContent, deleteSdkComment, getCommentId,
3615
3288
  updateReplyContent, deleteSdkReply, createCommentAvatar, getSdkHeaders,
3616
3289
  showAuthPrompt,
@@ -3839,12 +3512,12 @@ export function initComty(options = {}) {
3839
3512
  function reconcileComments(comments) {
3840
3513
  const previous = new Map(cachedComments.map(comment => [String(comment.id), comment]));
3841
3514
  cachedComments = comments
3842
- .filter(comment => comment.kind !== "agent" || comment.status !== "done")
3515
+ .filter(comment => comment.kind !== "agent" && !comment.local_agent)
3843
3516
  .map(comment => Object.assign(previous.get(String(comment.id)) || {}, comment));
3844
3517
  }
3845
3518
 
3846
3519
  async function loadComments({ force = false } = {}) {
3847
- if (!projectId && !agent) return;
3520
+ if (!projectId) return;
3848
3521
  const pagePath = window.location.pathname;
3849
3522
  if (!force && cachedPagePath === pagePath) { await renderComments(cachedComments); return; }
3850
3523
  if (pendingCommentsLoad) {
@@ -3852,7 +3525,7 @@ export function initComty(options = {}) {
3852
3525
  if (!force) return;
3853
3526
  }
3854
3527
  pendingCommentsLoad = (async () => {
3855
- let comments = cachedPagePath === pagePath ? cachedComments.filter(comment => !comment.local_agent) : [];
3528
+ let comments = cachedPagePath === pagePath ? cachedComments : [];
3856
3529
  if (projectId) {
3857
3530
  try {
3858
3531
  const url = new URL(`${apiUrl}/api/comments`);
@@ -3863,37 +3536,15 @@ export function initComty(options = {}) {
3863
3536
  if (Array.isArray(data.comments)) comments = data.comments;
3864
3537
  } catch (error) { console.error("Comty: list comments failed", error); }
3865
3538
  }
3866
- if (agent) {
3867
- try {
3868
- const res = await fetch(`${agentBridgeUrl}/tasks`, { headers: agentHeaders(), cache: "no-store" });
3869
- if (!res.ok) throw new Error("Local companion unavailable");
3870
- const data = await res.json();
3871
- if (Array.isArray(data.tasks)) localTasks = data.tasks;
3872
- if (Array.isArray(data.approvals)) localApprovals = data.approvals;
3873
- agentConnectionError = "";
3874
- } catch (error) {
3875
- agentConnectionError = "Local agent disconnected. Check the development server, then reload this page. Your task history is kept.";
3876
- console.error("Comty: local tasks failed", error);
3877
- }
3878
- comments.push(...localTasks.filter(task => task.page_path === pagePath && !["done", "cancel"].includes(task.status)));
3879
- renderAgentPanel();
3880
- }
3881
3539
  reconcileComments(comments);
3882
3540
  cachedPagePath = pagePath;
3883
3541
  renderSidepanelComments();
3884
3542
  await renderComments(cachedComments);
3885
- syncAgentWorkingOutlines();
3886
3543
  ensureLiveTracking();
3887
3544
  })();
3888
3545
  try { await pendingCommentsLoad; } finally { pendingCommentsLoad = null; }
3889
3546
  }
3890
3547
 
3891
- if (agent) {
3892
- setInterval(() => {
3893
- if (!pendingCommentsLoad && document.visibilityState === "visible") void loadComments({ force: true });
3894
- }, 2000);
3895
- }
3896
-
3897
3548
  function getPinPoint(pin) {
3898
3549
  return { x: parseFloat(pin.style.left), y: parseFloat(pin.style.top) };
3899
3550
  }
@@ -3960,50 +3611,16 @@ export function initComty(options = {}) {
3960
3611
  }
3961
3612
  }
3962
3613
 
3963
- function syncAgentWorkingOutlines() {
3964
- const working = localTasks.filter(task => task.status === "in_progress" && task.page_path === window.location.pathname);
3965
- const ids = new Set(working.map(task => String(task.id)));
3966
- for (const [id, outline] of workingOutlines) {
3967
- if (ids.has(id)) continue;
3968
- outline.remove(); workingOutlines.delete(id);
3969
- }
3970
- const modal = getTopActiveModal();
3971
- for (const task of working) {
3972
- const id = String(task.id);
3973
- let outline = workingOutlines.get(id);
3974
- if (!outline) {
3975
- outline = document.createElement("div");
3976
- outline.className = "comty-agent-working";
3977
- outline.setAttribute("aria-label", "Agent working on this element");
3978
- const badge = document.createElement("span");
3979
- badge.className = "comty-agent-working-badge";
3980
- badge.append(createWorkingIndicator(), "Working");
3981
- outline.appendChild(badge); root.appendChild(outline);
3982
- workingOutlines.set(id, outline);
3983
- }
3984
- const target = resolveCommentElement(task);
3985
- const point = target && getVisibleAnchorPoint(target, Number(task.rel_x), Number(task.rel_y), modal);
3986
- outline.hidden = !point;
3987
- if (!point) continue;
3988
- const rect = target.getBoundingClientRect();
3989
- setMeasuredRect(outline, rect);
3990
- outline.toggleAttribute("data-badge-below", rect.top < 32);
3991
- }
3992
- }
3993
-
3994
3614
  function onFrame() {
3995
3615
  if (motion.enabled) updateMotion();
3996
3616
  if (session.comments) syncCommentAnchors();
3997
- if (session.agent || workingOutlines.size || localTasks.some(task => task.status === "in_progress" && task.page_path === window.location.pathname)) {
3998
- syncAgentWorkingOutlines();
3999
- }
4000
3617
  if (session.inspect || session.edit || inspectionLayer.childNodes.length) renderInspector();
4001
3618
  if (session.inspect && rulersEnabled) renderRulers();
4002
3619
  if (session.inspect && rulersEnabled) measurements.render();
4003
3620
  }
4004
3621
 
4005
3622
  function ensureLiveTracking() {
4006
- const active = motion.enabled || session.inspect || session.edit || (session.comments && cachedComments.length > 0) || workingOutlines.size > 0;
3623
+ const active = motion.enabled || session.inspect || session.edit || (session.comments && cachedComments.length > 0);
4007
3624
  if (!active || document.hidden) {
4008
3625
  if (liveFrame) cancelAnimationFrame(liveFrame);
4009
3626
  liveFrame = null;
@@ -4023,14 +3640,13 @@ export function initComty(options = {}) {
4023
3640
  syncOverlayHost();
4024
3641
 
4025
3642
  function renderCachedComments() {
4026
- if (!session.comments && !session.inspect && !session.edit && !workingOutlines.size) return;
3643
+ if (!session.comments && !session.inspect && !session.edit) return;
4027
3644
  if (resizeRenderFrame) {
4028
3645
  window.cancelAnimationFrame(resizeRenderFrame);
4029
3646
  }
4030
3647
  resizeRenderFrame = window.requestAnimationFrame(() => {
4031
3648
  resizeRenderFrame = null;
4032
3649
  syncCommentAnchors();
4033
- syncAgentWorkingOutlines();
4034
3650
  renderInspector();
4035
3651
  ensureLiveTracking();
4036
3652
  });
@@ -4211,12 +3827,11 @@ export function initComty(options = {}) {
4211
3827
  const y = e.clientY;
4212
3828
 
4213
3829
  const anchorContext = getAnchorContext(el);
4214
- if (session.agent && anchorContext) anchorContext.inspectedStyles = getInspectRows(el);
4215
3830
 
4216
3831
  const payload = {
4217
3832
  ...getDeviceContext(),
4218
3833
  projectId,
4219
- kind: session.agent ? "agent" : "comment",
3834
+ kind: "comment",
4220
3835
  host: window.location.host,
4221
3836
  pagePath: window.location.pathname,
4222
3837
  selector: getSelector(el),
@@ -4316,7 +3931,6 @@ export function initComty(options = {}) {
4316
3931
  window.addEventListener("blur", () => { measureAlt = false; renderInspector(); });
4317
3932
 
4318
3933
  async function boot() {
4319
- if (localOnly) { await loadComments(); return; }
4320
3934
  const res = await fetch(`${apiUrl}/api/sdk/bootstrap`, {
4321
3935
  method: "POST",
4322
3936
  headers: getSdkHeaders({ "Content-Type": "application/json" }),
@@ -4329,7 +3943,6 @@ export function initComty(options = {}) {
4329
3943
 
4330
3944
  if (!res.ok) {
4331
3945
  console.error("Comty: bootstrap failed", await res.text());
4332
- if (agent) await loadComments();
4333
3946
  return;
4334
3947
  }
4335
3948
 
@@ -4365,26 +3978,12 @@ export function initComty(options = {}) {
4365
3978
  getDistanceSegments, getInspectRows, syncCommentAnchors,
4366
3979
  setInspectorMode(enabled) { setSession(enabled ? "inspect" : "off"); },
4367
3980
  startEditSession, startBrowseSession, endSession, showInspector,
4368
- motion, setMotionEnabled, updateMotion, describeAgentError,
4369
- previewAgent(tasks, approvals = [], error = "") {
4370
- localTasks = tasks;
4371
- localApprovals = approvals;
4372
- agentConnectionError = error;
4373
- startAgentSession();
4374
- agentPanelOpen = true;
4375
- agentPanelSignature = "";
4376
- renderAgentPanel();
4377
- },
3981
+ motion, setMotionEnabled, updateMotion,
4378
3982
  seed(comments) {
4379
3983
  reconcileComments(comments);
4380
3984
  setSession(session.comments ? session.mode : "browse", { entry: sessionEntry, renderComments: true });
4381
3985
  return renderComments(cachedComments, { retry: false });
4382
3986
  },
4383
- seedAgentTasks(tasks) {
4384
- localTasks = tasks;
4385
- syncAgentWorkingOutlines();
4386
- ensureLiveTracking();
4387
- },
4388
3987
  setCanWrite(value) { canWrite = value; },
4389
3988
  invalidate() { anchorRevision += 1; },
4390
3989
  });
@@ -4392,7 +3991,6 @@ export function initComty(options = {}) {
4392
3991
 
4393
3992
  return boot().catch(error => {
4394
3993
  console.error("Comty: bootstrap failed", error);
4395
- if (agent) return loadComments();
4396
3994
  });
4397
3995
  }
4398
3996
  }