@comty.io/core 0.1.2 → 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/README.md +1 -47
- package/comty-sdk.js +48 -520
- package/package.json +3 -14
- package/src/browser.d.ts +1 -3
- package/src/browser.js +35 -437
- package/src/comments.js +8 -12
- package/src/style.js +5 -71
- package/src/agent-process.mjs +0 -89
- package/src/claude-permission.mjs +0 -37
- package/src/claude.mjs +0 -120
- package/src/codex.mjs +0 -66
- package/src/companion.mjs +0 -207
- package/src/cursor.mjs +0 -49
- package/src/dev.mjs +0 -169
- package/src/vite.d.ts +0 -5
- package/src/vite.mjs +0 -66
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
|
|
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
|
|
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"
|
|
66
|
-
get edit() { return this.mode === "edit"
|
|
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'
|
|
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.
|
|
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
|
|
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,
|
|
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 (
|
|
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 !==
|
|
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
|
|
1564
|
-
|
|
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.
|
|
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 =
|
|
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",
|
|
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(
|
|
2748
|
+
post = await fetch(`${apiUrl}/api/comments`, {
|
|
2854
2749
|
method: "POST",
|
|
2855
|
-
headers:
|
|
2856
|
-
body: JSON.stringify(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 =
|
|
2876
|
-
const captureNodes = commentId
|
|
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
|
-
|
|
2885
|
-
else addLocalComment(String(commentId), payload);
|
|
2777
|
+
addLocalComment(String(commentId), payload);
|
|
2886
2778
|
void loadComments({ force: true });
|
|
2887
|
-
|
|
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 (
|
|
3074
|
+
if (!projectId || !comment?.id) return false;
|
|
3401
3075
|
|
|
3402
3076
|
let res;
|
|
3403
3077
|
try {
|
|
3404
|
-
|
|
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:
|
|
3408
|
-
body: JSON.stringify(
|
|
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.
|
|
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,
|
|
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"
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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:
|
|
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,
|
|
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
|
}
|