@comty.io/core 0.1.5 → 0.1.6
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 +74 -2
- package/comty-sdk.js +842 -119
- package/package.json +16 -5
- package/src/acp.mjs +68 -0
- package/src/agent-process.mjs +89 -0
- package/src/anchors.js +58 -17
- package/src/browser.d.ts +3 -1
- package/src/browser.js +658 -79
- package/src/claude-permission.mjs +37 -0
- package/src/claude.mjs +120 -0
- package/src/codex.mjs +66 -0
- package/src/comments.js +31 -11
- package/src/companion.mjs +201 -0
- package/src/cursor.mjs +49 -0
- package/src/dev.mjs +171 -0
- package/src/gemini.mjs +3 -0
- package/src/grok.mjs +3 -0
- package/src/style.js +95 -12
- package/src/vite.d.ts +5 -0
- package/src/vite.mjs +68 -0
package/comty-sdk.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
function getComtyStyle() {
|
|
4
4
|
const toolbarSurface = "#2b2d2f";
|
|
5
5
|
const toolbarMuted = "#b8babb";
|
|
6
|
-
const commentCursor = `<svg xmlns="http://www.w3.org/2000/svg" width="
|
|
6
|
+
const commentCursor = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 40 40"><path d="M20 2C30 2 38 10 38 20S30 38 20 38H3V28C2 25 2 23 2 20 2 10 10 2 20 2Z" fill="white" stroke="#d6dbe2"/><circle cx="20" cy="20" r="14" fill="#e6e6e6"/><path d="M20 14v12m-6-6h12" fill="none" stroke="#404040" stroke-width="2.5" stroke-linecap="round"/></svg>`;
|
|
7
7
|
const typographyCursor = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><rect x="8" y="8" width="23" height="23" rx="7" fill="${toolbarSurface}" stroke="${toolbarMuted}" stroke-width=".75"/><path d="m14 25 5.5-12L25 25m-8.5-4h6M13 25h4m5 0h4" fill="none" stroke="${toolbarMuted}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="m2 2 2 15 4-5 6-3Z" fill="${toolbarSurface}" stroke="${toolbarMuted}" stroke-width="1.5" stroke-linejoin="round"/></svg>`;
|
|
8
8
|
return `
|
|
9
9
|
#comty-root {
|
|
@@ -13,7 +13,7 @@ function getComtyStyle() {
|
|
|
13
13
|
--comty-muted: var(--comty-toolbar-muted);
|
|
14
14
|
--comty-accent: #55aaf5;
|
|
15
15
|
--comty-error: #ef7676;
|
|
16
|
-
--comty-outline: var(--comty-
|
|
16
|
+
--comty-outline: var(--comty-accent);
|
|
17
17
|
--comty-comment-focus: var(--comty-toolbar-muted);
|
|
18
18
|
--comty-paper: #fff;
|
|
19
19
|
--comty-ink: #171717;
|
|
@@ -60,6 +60,8 @@ function getComtyStyle() {
|
|
|
60
60
|
--comty-dialog-width: 32rem;
|
|
61
61
|
--comty-dialog-shadow: 0 1.5rem 4rem rgb(0 0 0 / .16);
|
|
62
62
|
--comty-motion-duration: 220ms;
|
|
63
|
+
--comty-working-dot-size: .25em;
|
|
64
|
+
--comty-working-dot-gap: .125em;
|
|
63
65
|
--comty-ease-out: cubic-bezier(0.23, 1, 0.32, 1);
|
|
64
66
|
--comty-ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
|
|
65
67
|
position: fixed;
|
|
@@ -71,13 +73,73 @@ function getComtyStyle() {
|
|
|
71
73
|
font-family: var(--comty-font);
|
|
72
74
|
color-scheme: dark;
|
|
73
75
|
}
|
|
74
|
-
|
|
76
|
+
#comty-root :is(button, .comty-comment-avatar) { user-select: none; -webkit-user-select: none; }
|
|
77
|
+
#comty-root .comty-comment-avatar img { -webkit-user-drag: none; }
|
|
78
|
+
#comty-root .comty-pin .comty-comment-avatar { pointer-events: none; }
|
|
79
|
+
#comty-root > .comty-agent-panel { position: fixed; right: 1rem; bottom: 5rem; z-index: 2147483647; width: min(22rem, calc(100vw - 2rem)); max-height: min(32rem, calc(100vh - 6rem)); box-sizing: border-box; display: flex; flex-direction: column; overflow: hidden; pointer-events: auto; border: .0625rem solid var(--comty-border); border-radius: 1rem; background: var(--comty-surface); box-shadow: var(--comty-floating-shadow); color: var(--comty-foreground); font: .875rem/1.4 ui-sans-serif, system-ui, sans-serif; transform: translateY(0); opacity: 1; transition: transform var(--comty-motion-duration) var(--comty-ease-out), opacity var(--comty-motion-duration) ease; color-scheme: dark; }
|
|
80
|
+
#comty-root > .comty-agent-panel[data-side="left"] { left: 1rem; right: auto; }
|
|
81
|
+
#comty-root > .comty-agent-panel[data-closed] { transform: translateY(.5rem); opacity: 0; pointer-events: none; }
|
|
82
|
+
#comty-root > .comty-agent-panel[hidden] { display: none; }
|
|
83
|
+
.comty-agent-panel *, .comty-agent-panel *::before { box-sizing: border-box; }
|
|
84
|
+
.comty-agent-panel-header { display: flex; flex: 0 0 auto; align-items: center; gap: .75rem; padding: .5rem .75rem; border-bottom: .0625rem solid var(--comty-hover-surface); }
|
|
85
|
+
.comty-agent-brand { display: grid; place-items: center; width: 2.375rem; height: 2.375rem; flex-shrink: 0; border-radius: .6875rem; color: var(--comty-accent); background: var(--comty-hover-surface); }
|
|
86
|
+
.comty-agent-brand.comty-agent-avatar { width: 2.375rem; height: 2.375rem; border-radius: 0; background: transparent; }
|
|
87
|
+
.comty-agent-panel svg { display: block; width: 1rem; height: 1rem; flex-shrink: 0; }
|
|
88
|
+
.comty-agent-brand svg { width: 1.25rem; height: 1.25rem; }
|
|
89
|
+
.comty-agent-brand.comty-agent-avatar svg { width: 100%; height: 100%; }
|
|
90
|
+
.comty-agent-answer { display: flex; align-items: flex-start; gap: .5rem; padding: .375rem .5rem .875rem; }
|
|
91
|
+
.comty-agent-answer .comty-agent-brand { width: 1.5rem; height: 1.5rem; }
|
|
92
|
+
.comty-agent-answer p { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--comty-foreground); }
|
|
93
|
+
.comty-agent-heading { flex: 1; min-width: 0; }
|
|
94
|
+
.comty-agent-heading strong { display: block; font-weight: 600; font-size: .875rem; }
|
|
95
|
+
.comty-agent-panel-content { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: var(--comty-hover-surface) transparent; padding: .375rem .5rem 2rem; }
|
|
96
|
+
.comty-agent-item { padding: .375rem .25rem; overflow-wrap: anywhere; }
|
|
97
|
+
.comty-agent-item + .comty-agent-item { border-top: .0625rem solid var(--comty-hover-surface); }
|
|
98
|
+
.comty-agent-item h3 { margin: 0 0 .25rem; color: var(--comty-foreground); font: 500 .875rem/1.4 ui-sans-serif, system-ui, sans-serif; }
|
|
99
|
+
.comty-agent-list { display: grid; gap: .125rem; }
|
|
100
|
+
.comty-agent-row { position: relative; display: flex; align-items: center; gap: .625rem; min-height: 2rem; padding: .25rem .5rem; border-radius: .5rem; color: var(--comty-foreground); }
|
|
101
|
+
.comty-agent-row:hover { background: var(--comty-hover-surface); }
|
|
102
|
+
.comty-agent-dot { width: .4375rem; height: .4375rem; flex: 0 0 auto; border-radius: 50%; background: var(--comty-muted); }
|
|
103
|
+
.comty-working-indicator { display: inline-grid; grid-template-columns: repeat(3, var(--comty-working-dot-size)); gap: var(--comty-working-dot-gap); flex: 0 0 auto; color: var(--comty-foreground); }
|
|
104
|
+
.comty-working-indicator > i { display: block; width: var(--comty-working-dot-size); height: var(--comty-working-dot-size); border-radius: 50%; background: currentColor; opacity: .25; transition: opacity 150ms linear; }
|
|
105
|
+
.comty-working-indicator > i[data-active] { opacity: 1; }
|
|
106
|
+
.comty-agent-dot[data-status="success"] { background: var(--comty-agent-success); }
|
|
107
|
+
.comty-agent-dot[data-status="error"] { background: var(--comty-agent-error); }
|
|
108
|
+
.comty-agent-row-name { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
|
|
109
|
+
.comty-agent-row-state { flex: 0 0 auto; color: var(--comty-muted); font-size: .875rem; }
|
|
110
|
+
.comty-agent-row-actions { position: absolute; top: 50%; right: .25rem; display: flex; gap: .25rem; border-radius: .25rem; background: var(--comty-hover-surface); opacity: 0; pointer-events: none; transform: translateY(-50%); }
|
|
111
|
+
.comty-agent-row:hover .comty-agent-row-actions, .comty-agent-row-actions:has(.comty-agent-action:focus-visible) { opacity: 1; pointer-events: auto; }
|
|
112
|
+
@media (hover: none) { .comty-agent-row-actions { position: static; opacity: 1; pointer-events: auto; transform: none; background: transparent; } }
|
|
113
|
+
.comty-agent-panel .comty-agent-row-actions .comty-agent-action { min-height: 1.5rem; padding: .125rem .375rem; border-radius: .25rem; background: transparent; color: var(--comty-muted); font-size: .875rem; }
|
|
114
|
+
.comty-agent-panel .comty-agent-row-actions .comty-agent-action:hover { color: var(--comty-foreground); background: var(--comty-surface); }
|
|
115
|
+
.comty-agent-error-popover { position: fixed; z-index: 2147483647; width: min(20rem, calc(100vw - 1rem)); max-height: calc(100vh - 1rem); overflow: auto; padding: .75rem .875rem; border: 0; border-radius: .625rem; background: var(--comty-surface); box-shadow: var(--comty-floating-shadow); color: var(--comty-foreground); font: .875rem/1.4 ui-sans-serif, system-ui, sans-serif; pointer-events: none; color-scheme: dark; }
|
|
116
|
+
.comty-agent-error-popover strong { display: block; font-weight: 600; }
|
|
117
|
+
.comty-agent-error-popover p { margin: .375rem 0 0; color: var(--comty-muted); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
118
|
+
.comty-agent-panel p { margin: .5rem 0 0; font: inherit; color: var(--comty-muted); }
|
|
119
|
+
.comty-agent-error { margin-top: .75rem; }
|
|
120
|
+
.comty-agent-error strong { display: block; font-weight: 500; color: var(--comty-foreground); }
|
|
121
|
+
.comty-agent-error p { margin-top: .25rem; }
|
|
122
|
+
.comty-agent-banner { margin-bottom: 1rem; }
|
|
123
|
+
.comty-agent-banner .comty-agent-error { margin-top: 0; }
|
|
124
|
+
.comty-agent-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .875rem; }
|
|
125
|
+
.comty-agent-panel .comty-agent-action { display: inline-flex; align-items: center; justify-content: center; gap: .375rem; min-height: 2rem; margin: 0; padding: .375rem .625rem; border: 0; border-radius: .5rem; font: 500 .875rem/1.4 ui-sans-serif, system-ui, sans-serif; cursor: pointer; color: var(--comty-foreground); background: var(--comty-hover-surface); transition: background-color 150ms ease; }
|
|
126
|
+
.comty-agent-panel .comty-agent-action:hover { background: color-mix(in srgb, var(--comty-foreground) 12%, var(--comty-hover-surface)); }
|
|
127
|
+
.comty-agent-panel .comty-agent-action[data-primary] { color: var(--comty-paper); background: var(--comty-accent); }
|
|
128
|
+
.comty-agent-panel .comty-agent-action:disabled { opacity: .5; cursor: wait; }
|
|
129
|
+
.comty-agent-panel .comty-agent-close { width: 1.5rem; min-height: 1.5rem; padding: 0; background: transparent; color: var(--comty-muted); }
|
|
130
|
+
.comty-agent-panel :is(button, summary):focus-visible { outline: .125rem solid var(--comty-accent); outline-offset: .1875rem; }
|
|
131
|
+
.comty-agent-details { margin-top: .625rem; }
|
|
132
|
+
.comty-agent-panel summary { cursor: pointer; color: var(--comty-muted); font-size: .875rem; }
|
|
133
|
+
.comty-agent-panel pre { margin: .5rem 0 0; padding: .625rem; white-space: pre-wrap; overflow-wrap: anywhere; max-height: 10rem; overflow: auto; border-radius: .5rem; background: var(--comty-surface); color: var(--comty-muted); font: .875rem/1.4 ui-monospace, monospace; }
|
|
134
|
+
.comty-agent-working { position: fixed; z-index: 1; box-sizing: border-box; border: 2px solid var(--comty-accent); border-radius: 0; pointer-events: none; }
|
|
135
|
+
.comty-agent-working-badge { position: absolute; left: -.125rem; bottom: 100%; display: inline-flex; align-items: center; gap: .4rem; padding: .25rem .5rem; border-radius: .375rem .375rem 0 0; background: var(--comty-surface); color: var(--comty-foreground); font: 600 .6875rem/1.4 ui-sans-serif, system-ui, sans-serif; white-space: nowrap; }
|
|
136
|
+
.comty-agent-working[data-badge-below] .comty-agent-working-badge { bottom: auto; top: 100%; border-radius: 0 0 .375rem .375rem; }
|
|
75
137
|
#comty-root { cursor: default; }
|
|
76
138
|
#comty-root::backdrop { background: transparent; pointer-events: none; }
|
|
77
139
|
html.comty-comment-placement,
|
|
78
140
|
html.comty-comment-placement body,
|
|
79
141
|
html.comty-comment-placement body *:not(#comty-root):not(#comty-root *) {
|
|
80
|
-
cursor: url("data:image/svg+xml,${encodeURIComponent(commentCursor)}")
|
|
142
|
+
cursor: url("data:image/svg+xml,${encodeURIComponent(commentCursor)}") 16 16, crosshair !important;
|
|
81
143
|
}
|
|
82
144
|
#comty-root > .comty-sidepanel-open, #comty-root > .comty-pin, #comty-root > .comty-bubble, #comty-root > .comty-create-bubble, #comty-root > .comty-popover, #comty-root > .comty-auth-backdrop, #comty-root > .comty-auth-toast { pointer-events: auto; }
|
|
83
145
|
.comty-toolbar {
|
|
@@ -144,7 +206,7 @@ function getComtyStyle() {
|
|
|
144
206
|
#comty-root [hidden] { display: none !important; }
|
|
145
207
|
[data-comty-anchor-hidden] { visibility: hidden !important; pointer-events: none !important; }
|
|
146
208
|
@media (prefers-reduced-motion: reduce) {
|
|
147
|
-
.comty-toolbar, .comty-control-button, .comty-sidepanel, .comty-pin, .comty-bubble { transition: none !important; animation: none !important; }
|
|
209
|
+
.comty-toolbar, .comty-control-button, .comty-sidepanel, .comty-pin, .comty-bubble, .comty-agent-panel, .comty-agent-action, .comty-agent-dot, .comty-working-indicator > i { transition: none !important; animation: none !important; }
|
|
148
210
|
}
|
|
149
211
|
|
|
150
212
|
.comty-sidepanel {
|
|
@@ -326,6 +388,16 @@ function getComtyStyle() {
|
|
|
326
388
|
white-space: nowrap;
|
|
327
389
|
}
|
|
328
390
|
|
|
391
|
+
.comty-anchor-badge {
|
|
392
|
+
display: inline-block;
|
|
393
|
+
margin-top: .375rem;
|
|
394
|
+
padding: .125rem .375rem;
|
|
395
|
+
border: 1px solid var(--comty-border);
|
|
396
|
+
border-radius: 999rem;
|
|
397
|
+
color: var(--comty-muted);
|
|
398
|
+
font: .6875rem/1.4 sans-serif;
|
|
399
|
+
}
|
|
400
|
+
|
|
329
401
|
.comty-pin {
|
|
330
402
|
position: fixed;
|
|
331
403
|
z-index: 2147483645;
|
|
@@ -607,6 +679,8 @@ function getComtyStyle() {
|
|
|
607
679
|
height: 100%;
|
|
608
680
|
object-fit: cover;
|
|
609
681
|
}
|
|
682
|
+
.comty-comment-avatar.comty-agent-avatar { overflow: visible; border-radius: 0; background: transparent; color: var(--comty-paper); }
|
|
683
|
+
.comty-comment-avatar.comty-agent-avatar svg { display: block; width: 100%; height: 100%; }
|
|
610
684
|
|
|
611
685
|
.comty-pin-avatar {
|
|
612
686
|
width: 24px;
|
|
@@ -721,12 +795,12 @@ function getComtyStyle() {
|
|
|
721
795
|
}
|
|
722
796
|
|
|
723
797
|
.comty-popover-item.comty-popover-item-destructive {
|
|
724
|
-
color: var(--comty-error);
|
|
798
|
+
color: var(--comty-agent-error);
|
|
725
799
|
}
|
|
726
800
|
|
|
727
801
|
.comty-popover-item.comty-popover-item-destructive:hover,
|
|
728
802
|
.comty-popover-item.comty-popover-item-destructive:focus-visible {
|
|
729
|
-
background: color-mix(in srgb, var(--comty-error) 12%, var(--comty-surface));
|
|
803
|
+
background: color-mix(in srgb, var(--comty-agent-error) 12%, var(--comty-surface));
|
|
730
804
|
outline: none;
|
|
731
805
|
}
|
|
732
806
|
|
|
@@ -1097,15 +1171,15 @@ function getComtyStyle() {
|
|
|
1097
1171
|
margin: 4px 0;
|
|
1098
1172
|
}
|
|
1099
1173
|
|
|
1100
|
-
html.comty-comments-active .comty-pin {
|
|
1174
|
+
html:is(.comty-comments-active, .comty-inspect-annotations) .comty-pin {
|
|
1101
1175
|
display: grid;
|
|
1102
1176
|
}
|
|
1103
1177
|
|
|
1104
|
-
html.comty-comments-active .comty-pin.comty-filtered-out {
|
|
1178
|
+
html:is(.comty-comments-active, .comty-inspect-annotations) .comty-pin.comty-filtered-out {
|
|
1105
1179
|
display: none;
|
|
1106
1180
|
}
|
|
1107
1181
|
|
|
1108
|
-
html.comty-comments-active .comty-bubble {
|
|
1182
|
+
html:is(.comty-comments-active, .comty-inspect-annotations) .comty-bubble {
|
|
1109
1183
|
display: block;
|
|
1110
1184
|
}
|
|
1111
1185
|
|
|
@@ -1215,10 +1289,10 @@ function getComtyStyle() {
|
|
|
1215
1289
|
position: fixed;
|
|
1216
1290
|
box-sizing: border-box;
|
|
1217
1291
|
border: .0625rem solid var(--comty-outline);
|
|
1218
|
-
background:
|
|
1292
|
+
background: transparent;
|
|
1293
|
+
border-radius: 0;
|
|
1219
1294
|
pointer-events: none;
|
|
1220
1295
|
}
|
|
1221
|
-
.comty-inspection-box[data-selected] { background: color-mix(in oklch, var(--comty-outline) 5%, transparent); }
|
|
1222
1296
|
.comty-inspect-spacing { position: fixed; pointer-events: none; background: var(--comty-spacing-color); }
|
|
1223
1297
|
.comty-inspect-margin { --comty-spacing-color: var(--comty-margin-fill); }
|
|
1224
1298
|
.comty-inspect-padding { --comty-spacing-color: var(--comty-padding-fill); }
|
|
@@ -1272,6 +1346,15 @@ function getComtyStyle() {
|
|
|
1272
1346
|
height: 5.75rem;
|
|
1273
1347
|
border-radius: .75rem;
|
|
1274
1348
|
}
|
|
1349
|
+
.comty-agent-scope { display: inline-flex; align-items: center; gap: .375rem; color: var(--comty-muted); font: .6875rem/1.25 var(--comty-font); white-space: nowrap; cursor: pointer; pointer-events: auto; opacity: 0; visibility: hidden; }
|
|
1350
|
+
.comty-create-expanded .comty-agent-scope { opacity: 1; visibility: visible; }
|
|
1351
|
+
.comty-agent-scope input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
|
|
1352
|
+
.comty-agent-scope-track { position: relative; width: 1.5rem; height: .875rem; flex: 0 0 auto; border-radius: 999rem; background: var(--comty-border); transition: background-color 160ms ease; }
|
|
1353
|
+
.comty-agent-scope-track::after { content: ""; position: absolute; top: .125rem; left: .125rem; width: .625rem; height: .625rem; border-radius: 50%; background: var(--comty-paper); transition: transform 160ms ease; }
|
|
1354
|
+
.comty-agent-scope input:checked + .comty-agent-scope-track { background: var(--comty-accent); }
|
|
1355
|
+
.comty-agent-scope input:checked + .comty-agent-scope-track::after { transform: translateX(.625rem); }
|
|
1356
|
+
.comty-agent-scope input:focus-visible + .comty-agent-scope-track { outline: .125rem solid var(--comty-comment-focus); outline-offset: .125rem; }
|
|
1357
|
+
.comty-agent-thread-error { grid-column: 2; margin: .25rem 0 0; color: var(--comty-agent-error); font: .6875rem/1.3 var(--comty-font); }
|
|
1275
1358
|
|
|
1276
1359
|
.comty-create-bubble.comty-create-has-attachment { height: 7.5rem; }
|
|
1277
1360
|
.comty-create-bubble.comty-bubble-left { transform-origin: top right; }
|
|
@@ -1502,6 +1585,14 @@ function createAnchorMatcher({
|
|
|
1502
1585
|
return haystack.toLowerCase().includes(needle.toLowerCase());
|
|
1503
1586
|
}
|
|
1504
1587
|
|
|
1588
|
+
function similarText(a, b) {
|
|
1589
|
+
if (!a || !b) return false;
|
|
1590
|
+
const words = value => new Set(value.toLowerCase().split(/\s+/).filter(Boolean));
|
|
1591
|
+
const left = words(a), right = words(b);
|
|
1592
|
+
const common = [...left].filter(word => right.has(word)).length;
|
|
1593
|
+
return common >= 2 && common / Math.max(left.size, right.size) >= 0.7;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1505
1596
|
function scoreFingerprint(el, fingerprint) {
|
|
1506
1597
|
if (!el || !fingerprint) return 0;
|
|
1507
1598
|
if (
|
|
@@ -1571,6 +1662,8 @@ function createAnchorMatcher({
|
|
|
1571
1662
|
score += 15;
|
|
1572
1663
|
}
|
|
1573
1664
|
|
|
1665
|
+
if (text !== fingerprint.text && similarText(text, fingerprint.text)) score += 24;
|
|
1666
|
+
|
|
1574
1667
|
if (typeof fingerprint.siblingIndex === "number" && el.parentElement) {
|
|
1575
1668
|
const siblings = [...el.parentElement.children].filter(
|
|
1576
1669
|
(child) => child.tagName === el.tagName,
|
|
@@ -1648,6 +1741,8 @@ function createAnchorMatcher({
|
|
|
1648
1741
|
if (ancestorScore > 0) {
|
|
1649
1742
|
score += Math.min(ancestorScore, 40) * (index === 0 ? 0.5 : 0.2);
|
|
1650
1743
|
}
|
|
1744
|
+
// Distinguish repeated cards by their section, even when dimensions match.
|
|
1745
|
+
if (index < 3 && ancestors[index].text && getElementTextSignature(parent) === ancestors[index].text) score += 18;
|
|
1651
1746
|
parent = parent.parentElement;
|
|
1652
1747
|
}
|
|
1653
1748
|
|
|
@@ -1662,46 +1757,75 @@ function createAnchorMatcher({
|
|
|
1662
1757
|
return score;
|
|
1663
1758
|
}
|
|
1664
1759
|
|
|
1665
|
-
function matchesAnchorIdentity(el, fingerprint) {
|
|
1760
|
+
function matchesAnchorIdentity(el, fingerprint, ignoreDescendantText = false, allowTextChanges = true) {
|
|
1666
1761
|
if (!el || !fingerprint || el.tagName.toLowerCase() !== fingerprint.tag) return false;
|
|
1667
1762
|
const attributes = { ariaLabel: "aria-label", name: "name", href: "href", src: "src", alt: "alt", title: "title", role: "role" };
|
|
1668
1763
|
for (const [key, attribute] of Object.entries(attributes)) {
|
|
1669
1764
|
if (fingerprint[key] && getAttributeSignature(el, attribute) !== fingerprint[key]) return false;
|
|
1670
1765
|
}
|
|
1671
|
-
if (fingerprint.
|
|
1766
|
+
if (fingerprint.id && el.id !== fingerprint.id) return false;
|
|
1767
|
+
if (fingerprint.testId && !["data-testid", "data-test", "data-cy"].some(name => getAttributeSignature(el, name) === fingerprint.testId)) return false;
|
|
1768
|
+
const stableIdentity = (fingerprint.id && el.id === fingerprint.id) ||
|
|
1769
|
+
(fingerprint.testId && ["data-testid", "data-test", "data-cy"].some(name => getAttributeSignature(el, name) === fingerprint.testId));
|
|
1770
|
+
if (fingerprint.ownText && getElementOwnTextSignature(el) !== fingerprint.ownText && !(allowTextChanges && stableIdentity)) return false;
|
|
1672
1771
|
// Container text is useful for matching remounted drawers and dialogs.
|
|
1673
|
-
if (!fingerprint.ownText && fingerprint.text && getElementTextSignature(el) !== fingerprint.text)
|
|
1772
|
+
if (!ignoreDescendantText && !fingerprint.ownText && fingerprint.text && getElementTextSignature(el) !== fingerprint.text && !(allowTextChanges && stableIdentity)) {
|
|
1773
|
+
if (!allowTextChanges || !el.children.length || !similarText(getElementTextSignature(el), fingerprint.text)) return false;
|
|
1774
|
+
}
|
|
1674
1775
|
return true;
|
|
1675
1776
|
}
|
|
1676
1777
|
|
|
1677
1778
|
function matchesAnchorContext(el, context) {
|
|
1678
1779
|
if (!context) return true;
|
|
1679
|
-
|
|
1780
|
+
// A unique page landmark keeps its identity when a child is edited.
|
|
1781
|
+
const pageContainer = !context.layer && (context.target?.tag === "main" || context.target?.role === "main") &&
|
|
1782
|
+
[...document.querySelectorAll('main, [role="main"]')].filter(node => !isComtyNode(node)).length === 1;
|
|
1783
|
+
if (!matchesAnchorIdentity(el, context.target, pageContainer)) return false;
|
|
1680
1784
|
const parentFingerprint = context.ancestors?.[0];
|
|
1681
1785
|
if (parentFingerprint?.text && parentFingerprint.text !== context.target.text &&
|
|
1682
|
-
!matchesAnchorIdentity(el.parentElement, parentFingerprint)) return false;
|
|
1786
|
+
!matchesAnchorIdentity(el.parentElement, parentFingerprint, pageContainer)) return false;
|
|
1683
1787
|
const layer = getPresentationLayer(el);
|
|
1684
|
-
if (context.layer && (!layer || !matchesAnchorIdentity(layer, context.layer))) return false;
|
|
1788
|
+
if (context.layer && (!layer || !matchesAnchorIdentity(layer, context.layer, false, false))) return false;
|
|
1685
1789
|
if (context.version >= 2 && !context.layer && layer) return false;
|
|
1686
1790
|
return scoreAnchorCandidate(el, context) >= 50;
|
|
1687
1791
|
}
|
|
1688
1792
|
|
|
1689
|
-
function
|
|
1690
|
-
if (!context?.target) return
|
|
1793
|
+
function resolveAnchor(context, exact = null) {
|
|
1794
|
+
if (!context?.target) return { element: exact, status: exact ? "matched" : "missing" };
|
|
1691
1795
|
let candidates;
|
|
1692
1796
|
try {
|
|
1693
|
-
candidates = [...document.body.querySelectorAll(context.target.tag || "*")];
|
|
1797
|
+
candidates = [...document.body.querySelectorAll(context.target.tag || "*")].filter(el => !isComtyNode(el));
|
|
1694
1798
|
} catch {
|
|
1695
|
-
return null;
|
|
1799
|
+
return { element: null, status: "missing" };
|
|
1696
1800
|
}
|
|
1697
1801
|
const matches = candidates
|
|
1698
|
-
.filter(el =>
|
|
1699
|
-
.map(el => ({ el, score: scoreAnchorCandidate(el, context) }))
|
|
1802
|
+
.filter(el => matchesAnchorContext(el, context))
|
|
1803
|
+
.map(el => ({ el, score: scoreAnchorCandidate(el, context) + (context.version === 1 && el === exact ? 16 : 0) }))
|
|
1700
1804
|
.sort((a, b) => b.score - a.score);
|
|
1701
|
-
if (
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1805
|
+
if (context.candidateCount > 1) {
|
|
1806
|
+
const peers = candidates.filter(el => matchesAnchorIdentity(el, context.target));
|
|
1807
|
+
if (peers.length !== context.candidateCount) {
|
|
1808
|
+
// A removed identical sibling must not move its comment to the survivor.
|
|
1809
|
+
const distinctive = matches[0] && (context.target.id || context.target.testId);
|
|
1810
|
+
if (!distinctive) return { element: null, status: "uncertain" };
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
if (matches[0]?.score >= 58 && (!matches[1] || matches[0].score - matches[1].score >= 12)) {
|
|
1814
|
+
return { element: matches[0].el, status: "matched" };
|
|
1815
|
+
}
|
|
1816
|
+
// A closed/unmounted overlay is not evidence that its contents were deleted.
|
|
1817
|
+
if (context.layer && !candidates.some(el => {
|
|
1818
|
+
const layer = getPresentationLayer(el);
|
|
1819
|
+
return layer && matchesAnchorIdentity(layer, context.layer, false, false);
|
|
1820
|
+
})) return { element: null, status: "hidden" };
|
|
1821
|
+
const plausible = matches.length || candidates.some(el =>
|
|
1822
|
+
(context.target.id && el.id === context.target.id) ||
|
|
1823
|
+
(context.target.text && (getElementTextSignature(el) === context.target.text || similarText(getElementTextSignature(el), context.target.text))));
|
|
1824
|
+
return { element: null, status: plausible ? "uncertain" : "missing" };
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
function resolveByAnchorContext(context) {
|
|
1828
|
+
return resolveAnchor(context).element;
|
|
1705
1829
|
}
|
|
1706
1830
|
|
|
1707
1831
|
function getViewportBounds() {
|
|
@@ -1873,7 +1997,7 @@ function createAnchorMatcher({
|
|
|
1873
1997
|
return { clientX, clientY };
|
|
1874
1998
|
}
|
|
1875
1999
|
|
|
1876
|
-
return { matchesAnchorContext, resolveByAnchorContext, isElementTreeHidden, getTopActiveModal, getVisibleAnchorPoint };
|
|
2000
|
+
return { matchesAnchorContext, resolveAnchor, resolveByAnchorContext, isElementTreeHidden, getTopActiveModal, getVisibleAnchorPoint };
|
|
1877
2001
|
}
|
|
1878
2002
|
|
|
1879
2003
|
const captureEngine = (() => { // Generated from html-to-image 1.11.13 by scripts/capture-engine.mjs.
|
|
@@ -2371,7 +2495,7 @@ function createCommentThread(context) {
|
|
|
2371
2495
|
renderSidepanelComments, applyStatusFilters, createThreadItem, renderCommentContent,
|
|
2372
2496
|
isOwnCommentEntry, updateCommentContent, deleteSdkComment, getCommentId,
|
|
2373
2497
|
updateReplyContent, deleteSdkReply, createCommentAvatar, getSdkHeaders,
|
|
2374
|
-
showAuthPrompt,
|
|
2498
|
+
showAuthPrompt, startThreadAgent,
|
|
2375
2499
|
} = context;
|
|
2376
2500
|
|
|
2377
2501
|
return function openCommentThread(
|
|
@@ -2434,16 +2558,16 @@ function createCommentThread(context) {
|
|
|
2434
2558
|
};
|
|
2435
2559
|
|
|
2436
2560
|
const saveStatus = async (nextStatus, control) => {
|
|
2437
|
-
if (!context.canManage) return false;
|
|
2561
|
+
if (!context.canManage || comment.local_agent) return false;
|
|
2438
2562
|
control.disabled = true;
|
|
2439
2563
|
const savedStatus = await updateCommentStatus(comment, nextStatus);
|
|
2440
2564
|
control.disabled = false;
|
|
2441
2565
|
if (!savedStatus) return false;
|
|
2442
2566
|
|
|
2443
2567
|
const savedStatusOption = getCommentStatus(savedStatus);
|
|
2444
|
-
pin.dataset.status = savedStatusOption.value;
|
|
2445
|
-
applyStatusDot(pinStatusDot, savedStatus);
|
|
2446
|
-
pin
|
|
2568
|
+
if (pin) pin.dataset.status = savedStatusOption.value;
|
|
2569
|
+
if (pinStatusDot) applyStatusDot(pinStatusDot, savedStatus);
|
|
2570
|
+
pin?.setAttribute(
|
|
2447
2571
|
"aria-label",
|
|
2448
2572
|
`Afficher le commentaire, statut ${savedStatusOption.label}`,
|
|
2449
2573
|
);
|
|
@@ -2459,7 +2583,7 @@ function createCommentThread(context) {
|
|
|
2459
2583
|
return true;
|
|
2460
2584
|
};
|
|
2461
2585
|
|
|
2462
|
-
statusMenuButton.disabled = !context.canManage;
|
|
2586
|
+
statusMenuButton.disabled = comment.local_agent || !context.canManage;
|
|
2463
2587
|
statusMenuButton.onclick = (event) => {
|
|
2464
2588
|
event.stopPropagation();
|
|
2465
2589
|
const existingPopover = document.querySelector(
|
|
@@ -2608,7 +2732,7 @@ function createCommentThread(context) {
|
|
|
2608
2732
|
);
|
|
2609
2733
|
};
|
|
2610
2734
|
|
|
2611
|
-
const mainItem = createThreadItem(comment, { showMore: isOwnCommentEntry(comment) });
|
|
2735
|
+
const mainItem = createThreadItem(comment, { showMore: !comment.local_agent && isOwnCommentEntry(comment) });
|
|
2612
2736
|
|
|
2613
2737
|
const startEditingEntry = (entry, item, persist) => {
|
|
2614
2738
|
if (!isOwnCommentEntry(entry)) return;
|
|
@@ -2704,7 +2828,7 @@ function createCommentThread(context) {
|
|
|
2704
2828
|
getCommentId(cachedComment) !== getCommentId(comment),
|
|
2705
2829
|
);
|
|
2706
2830
|
placedPins.delete(getCommentId(comment));
|
|
2707
|
-
pin
|
|
2831
|
+
pin?.remove();
|
|
2708
2832
|
closeBubbles();
|
|
2709
2833
|
renderSidepanelComments();
|
|
2710
2834
|
applyStatusFilters();
|
|
@@ -2751,11 +2875,19 @@ function createCommentThread(context) {
|
|
|
2751
2875
|
};
|
|
2752
2876
|
replies.forEach(appendReply);
|
|
2753
2877
|
thread.appendChild(threadList);
|
|
2878
|
+
bubble.addEventListener('comty-agent-reply', (event) => {
|
|
2879
|
+
const reply = event.detail;
|
|
2880
|
+
if (!reply || replies.some(item => item.id === reply.id)) return;
|
|
2881
|
+
replies.push(reply);
|
|
2882
|
+
comment.replies = replies;
|
|
2883
|
+
appendReply(reply);
|
|
2884
|
+
threadList.scrollTop = threadList.scrollHeight;
|
|
2885
|
+
});
|
|
2754
2886
|
|
|
2755
2887
|
const replyForm = document.createElement("div");
|
|
2756
2888
|
replyForm.className = "comty-reply-form";
|
|
2757
2889
|
|
|
2758
|
-
if (context.canWrite) {
|
|
2890
|
+
if (context.canWrite && !comment.local_agent) {
|
|
2759
2891
|
replyForm.appendChild(createCommentAvatar(context.currentUser || { name: "You" }));
|
|
2760
2892
|
const replyField = document.createElement("div");
|
|
2761
2893
|
replyField.className = "comty-reply-field";
|
|
@@ -2798,6 +2930,18 @@ function createCommentThread(context) {
|
|
|
2798
2930
|
appendReply(data.reply);
|
|
2799
2931
|
replyInput.value = "";
|
|
2800
2932
|
syncReplySize();
|
|
2933
|
+
if (/(^|\s)@agent\b/i.test(content)) {
|
|
2934
|
+
try {
|
|
2935
|
+
await startThreadAgent(comment, replies, content);
|
|
2936
|
+
} catch (error) {
|
|
2937
|
+
const notice = document.createElement('p');
|
|
2938
|
+
notice.className = 'comty-agent-thread-error';
|
|
2939
|
+
notice.setAttribute('role', 'alert');
|
|
2940
|
+
notice.textContent = `Agent could not start: ${error.message}`;
|
|
2941
|
+
replyForm.querySelector('.comty-agent-thread-error')?.remove();
|
|
2942
|
+
replyForm.appendChild(notice);
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2801
2945
|
requestAnimationFrame(() => {
|
|
2802
2946
|
threadList.scrollTop = threadList.scrollHeight;
|
|
2803
2947
|
});
|
|
@@ -2845,7 +2989,7 @@ function createCommentThread(context) {
|
|
|
2845
2989
|
replyField.appendChild(replyInput);
|
|
2846
2990
|
replyField.appendChild(replyButton);
|
|
2847
2991
|
replyForm.appendChild(replyField);
|
|
2848
|
-
} else {
|
|
2992
|
+
} else if (!comment.local_agent) {
|
|
2849
2993
|
replyForm.classList.add("comty-reply-form-guest");
|
|
2850
2994
|
const signInButton = document.createElement("button");
|
|
2851
2995
|
signInButton.className = "comty-sign-in-reply";
|
|
@@ -2858,7 +3002,7 @@ function createCommentThread(context) {
|
|
|
2858
3002
|
replyForm.appendChild(signInButton);
|
|
2859
3003
|
}
|
|
2860
3004
|
|
|
2861
|
-
thread.appendChild(replyForm);
|
|
3005
|
+
if (!comment.local_agent) thread.appendChild(replyForm);
|
|
2862
3006
|
bubble.appendChild(thread);
|
|
2863
3007
|
root.appendChild(bubble);
|
|
2864
3008
|
|
|
@@ -3095,7 +3239,17 @@ const instanceKey = Symbol.for("comty.sdk.instance");
|
|
|
3095
3239
|
/** Initialize once per document; safe to import and call during server rendering. */
|
|
3096
3240
|
function initComty(options = {}) {
|
|
3097
3241
|
if (typeof window === "undefined" || typeof document === "undefined") return Promise.resolve();
|
|
3098
|
-
const
|
|
3242
|
+
const isLocalSite = ["localhost", "127.0.0.1", "[::1]"].includes(window.location.hostname);
|
|
3243
|
+
const agent = options.agent;
|
|
3244
|
+
if (agent && (!isLocalSite || typeof agent.token !== "string" || !/^[a-f0-9]{64}$/.test(agent.token))) {
|
|
3245
|
+
return Promise.reject(new TypeError("Comty: invalid local companion configuration"));
|
|
3246
|
+
}
|
|
3247
|
+
if (agent) {
|
|
3248
|
+
try {
|
|
3249
|
+
if (new URL(agent.url).origin !== window.location.origin) throw new Error();
|
|
3250
|
+
} catch { return Promise.reject(new TypeError("Comty: companion must share the page origin")); }
|
|
3251
|
+
}
|
|
3252
|
+
const projectKey = options.projectKey || (agent ? "local" : undefined);
|
|
3099
3253
|
const apiUrl = options.apiUrl || "http://localhost:4000";
|
|
3100
3254
|
if (typeof projectKey !== "string" || !projectKey.trim()) {
|
|
3101
3255
|
return Promise.reject(new TypeError("Comty: projectKey is required"));
|
|
@@ -3107,12 +3261,14 @@ function initComty(options = {}) {
|
|
|
3107
3261
|
if (!["http:", "https:"].includes(parsedApi.protocol)) {
|
|
3108
3262
|
return Promise.reject(new TypeError("Comty: apiUrl must be an absolute HTTP(S) URL"));
|
|
3109
3263
|
}
|
|
3110
|
-
const configuration = JSON.stringify([projectKey, apiUrl]);
|
|
3264
|
+
const configuration = JSON.stringify([projectKey, apiUrl, agent || null]);
|
|
3111
3265
|
const existing = window[instanceKey];
|
|
3112
3266
|
if (existing) {
|
|
3113
3267
|
return existing.configuration === configuration ? existing.ready
|
|
3114
3268
|
: Promise.reject(new Error("Comty: already initialized with different options"));
|
|
3115
3269
|
}
|
|
3270
|
+
const agentBridgeUrl = agent?.url;
|
|
3271
|
+
const localOnly = Boolean(agent && !options.projectKey);
|
|
3116
3272
|
const ready = new Promise((resolve, reject) => {
|
|
3117
3273
|
const mount = () => {
|
|
3118
3274
|
try { resolve(mountComty()); } catch (error) { reject(error); }
|
|
@@ -3126,10 +3282,22 @@ function initComty(options = {}) {
|
|
|
3126
3282
|
|
|
3127
3283
|
function mountComty() {
|
|
3128
3284
|
let projectId = null;
|
|
3285
|
+
let localTasks = [];
|
|
3286
|
+
const deliveringAgentReplies = new Set();
|
|
3287
|
+
let localApprovals = [];
|
|
3288
|
+
const seenAgentApprovals = new Set();
|
|
3289
|
+
const seenFailedAgentTasks = new Set();
|
|
3290
|
+
let agentPanelOpen = false;
|
|
3291
|
+
let agentPanelSignature = "";
|
|
3292
|
+
let agentPanelTransition = 0;
|
|
3293
|
+
let agentConnectionError = "";
|
|
3294
|
+
let agentActionError = "";
|
|
3295
|
+
const agentPendingActions = new Set();
|
|
3129
3296
|
const session = {
|
|
3130
3297
|
mode: "off",
|
|
3131
|
-
get comments() { return this.mode === "browse" || this.mode === "edit"; },
|
|
3132
|
-
get edit() { return this.mode === "edit"; },
|
|
3298
|
+
get comments() { return this.mode === "browse" || this.mode === "edit" || this.mode === "agent"; },
|
|
3299
|
+
get edit() { return this.mode === "edit" || this.mode === "agent"; },
|
|
3300
|
+
get agent() { return this.mode === "agent"; },
|
|
3133
3301
|
get inspect() { return this.mode === "inspect"; },
|
|
3134
3302
|
};
|
|
3135
3303
|
const selectedStatusFilters = new Set();
|
|
@@ -3153,9 +3321,10 @@ function initComty(options = {}) {
|
|
|
3153
3321
|
const authReturnStorageKey = `comty:${projectKey}:auth-return`;
|
|
3154
3322
|
let sdkToken = "";
|
|
3155
3323
|
const activeHoverCommentIds = new Set();
|
|
3156
|
-
/** @type {null | 'edit' | 'browse'} */
|
|
3324
|
+
/** @type {null | 'edit' | 'browse' | 'agent'} */
|
|
3157
3325
|
let sessionEntry = null;
|
|
3158
3326
|
const placedPins = new Map();
|
|
3327
|
+
const workingOutlines = new Map();
|
|
3159
3328
|
const placementDiagnostics = new Map();
|
|
3160
3329
|
let inspectedElement = null;
|
|
3161
3330
|
let selectedElement = null;
|
|
@@ -3169,8 +3338,8 @@ function initComty(options = {}) {
|
|
|
3169
3338
|
const commentTargets = new Map();
|
|
3170
3339
|
|
|
3171
3340
|
const {
|
|
3172
|
-
matchesAnchorContext,
|
|
3173
|
-
isElementTreeHidden, getTopActiveModal, getVisibleAnchorPoint,
|
|
3341
|
+
matchesAnchorContext,
|
|
3342
|
+
resolveAnchor, isElementTreeHidden, getTopActiveModal, getVisibleAnchorPoint,
|
|
3174
3343
|
} = createAnchorMatcher({
|
|
3175
3344
|
getElementTextSignature,
|
|
3176
3345
|
getElementOwnTextSignature,
|
|
@@ -3248,10 +3417,20 @@ function initComty(options = {}) {
|
|
|
3248
3417
|
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"));
|
|
3249
3418
|
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"/>', () => {
|
|
3250
3419
|
if (!canWrite) showAuthPrompt();
|
|
3251
|
-
else if (session.inspect || !isSessionActive()) startEditSession();
|
|
3420
|
+
else if (session.agent || session.inspect || !isSessionActive()) startEditSession();
|
|
3252
3421
|
else if (!session.edit) setSession("edit", { entry: sessionEntry });
|
|
3253
3422
|
});
|
|
3254
3423
|
btn.id = "comty-toggle";
|
|
3424
|
+
const agentBtn = isLocalSite
|
|
3425
|
+
? toolButton("Agent task · localhost only", '<path d="M8 7 3 12l5 5m8-10 5 5-5 5M14 4l-4 16"/>', () => {
|
|
3426
|
+
if (session.agent) {
|
|
3427
|
+
if (localTasks.some(task => task.status === 'in_review' || (!task.thread_comment_id && task.status === 'done')) || localApprovals.length) {
|
|
3428
|
+
agentPanelOpen = true;
|
|
3429
|
+
renderAgentPanel();
|
|
3430
|
+
}
|
|
3431
|
+
} else startAgentSession();
|
|
3432
|
+
})
|
|
3433
|
+
: null;
|
|
3255
3434
|
const divider = document.createElement("span");
|
|
3256
3435
|
divider.className = "comty-tool-divider";
|
|
3257
3436
|
toolbar.appendChild(divider);
|
|
@@ -3261,7 +3440,7 @@ function initComty(options = {}) {
|
|
|
3261
3440
|
const primaryTools = document.createElement("div");
|
|
3262
3441
|
primaryTools.className = "comty-tool-primary";
|
|
3263
3442
|
toolbar.insertBefore(primaryTools, browseBtn);
|
|
3264
|
-
for (const button of [browseBtn, inspectorBtn, btn]) primaryTools.appendChild(button);
|
|
3443
|
+
for (const button of [browseBtn, inspectorBtn, btn, agentBtn].filter(Boolean)) primaryTools.appendChild(button);
|
|
3265
3444
|
const inspectorTools = document.createElement("div");
|
|
3266
3445
|
inspectorTools.className = "comty-tool-context";
|
|
3267
3446
|
inspectorTools.hidden = true;
|
|
@@ -4114,6 +4293,50 @@ function initComty(options = {}) {
|
|
|
4114
4293
|
let toolbarMotionFrame = 0;
|
|
4115
4294
|
let toolbarAnimations = [];
|
|
4116
4295
|
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
|
|
4296
|
+
let workingIndicatorTimer = 0;
|
|
4297
|
+
function scheduleWorkingIndicators() {
|
|
4298
|
+
if (workingIndicatorTimer || reducedMotion.matches || document.hidden) return;
|
|
4299
|
+
workingIndicatorTimer = setTimeout(() => {
|
|
4300
|
+
workingIndicatorTimer = 0;
|
|
4301
|
+
if (!root.isConnected) return;
|
|
4302
|
+
const indicators = root.querySelectorAll('.comty-working-indicator');
|
|
4303
|
+
if (!indicators.length) return;
|
|
4304
|
+
for (const indicator of indicators) {
|
|
4305
|
+
const previous = Number(indicator.dataset.activeDots.split(',')[0]);
|
|
4306
|
+
const first = (previous + 1 + Math.floor(Math.random() * 8)) % 9;
|
|
4307
|
+
const second = (first + 1 + Math.floor(Math.random() * 8)) % 9;
|
|
4308
|
+
indicator.dataset.activeDots = `${first},${second}`;
|
|
4309
|
+
[...indicator.children].forEach((dot, index) => dot.toggleAttribute('data-active', index === first || index === second));
|
|
4310
|
+
}
|
|
4311
|
+
scheduleWorkingIndicators();
|
|
4312
|
+
}, 600);
|
|
4313
|
+
}
|
|
4314
|
+
function createWorkingIndicator() {
|
|
4315
|
+
const indicator = document.createElement('span');
|
|
4316
|
+
indicator.className = 'comty-working-indicator';
|
|
4317
|
+
indicator.setAttribute('aria-hidden', 'true');
|
|
4318
|
+
indicator.dataset.activeDots = '3,5';
|
|
4319
|
+
for (let index = 0; index < 9; index++) {
|
|
4320
|
+
const dot = document.createElement('i');
|
|
4321
|
+
dot.toggleAttribute('data-active', index === 3 || index === 5);
|
|
4322
|
+
indicator.appendChild(dot);
|
|
4323
|
+
}
|
|
4324
|
+
scheduleWorkingIndicators();
|
|
4325
|
+
return indicator;
|
|
4326
|
+
}
|
|
4327
|
+
function resetWorkingIndicators() {
|
|
4328
|
+
clearTimeout(workingIndicatorTimer);
|
|
4329
|
+
workingIndicatorTimer = 0;
|
|
4330
|
+
if (reducedMotion.matches) {
|
|
4331
|
+
for (const indicator of root.querySelectorAll('.comty-working-indicator')) {
|
|
4332
|
+
indicator.dataset.activeDots = '3,5';
|
|
4333
|
+
[...indicator.children].forEach((dot, index) => dot.toggleAttribute('data-active', index === 3 || index === 5));
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
if (root.querySelector('.comty-working-indicator')) scheduleWorkingIndicators();
|
|
4337
|
+
}
|
|
4338
|
+
reducedMotion.addEventListener('change', resetWorkingIndicators);
|
|
4339
|
+
document.addEventListener('visibilitychange', resetWorkingIndicators);
|
|
4117
4340
|
const clampToolbar = (value, size, viewport) => Math.max(8, Math.min(value, viewport - size - 8));
|
|
4118
4341
|
function rememberToolbarPosition(left, top) {
|
|
4119
4342
|
const width = toolbar.offsetWidth, height = toolbar.offsetHeight;
|
|
@@ -4226,7 +4449,7 @@ function initComty(options = {}) {
|
|
|
4226
4449
|
|
|
4227
4450
|
function prepareToolbarMotion() {
|
|
4228
4451
|
if (toolbarMotionFrame) return;
|
|
4229
|
-
const nodes = [toolbarSurface, dragHandle, primaryTools, commentTools, inspectorTools]
|
|
4452
|
+
const nodes = [toolbarSurface, dragHandle, primaryTools, commentTools, inspectorTools];
|
|
4230
4453
|
// Move each group's background with its icons, including when interrupted.
|
|
4231
4454
|
const before = new Map(nodes.filter(node => !node.hidden).map(node => [node, {
|
|
4232
4455
|
rect: node.getBoundingClientRect(), opacity: getComputedStyle(node).opacity,
|
|
@@ -4450,7 +4673,8 @@ function initComty(options = {}) {
|
|
|
4450
4673
|
selectedElement = null;
|
|
4451
4674
|
sessionEntry = null;
|
|
4452
4675
|
session.mode = "off";
|
|
4453
|
-
|
|
4676
|
+
syncPlacementCursor();
|
|
4677
|
+
document.documentElement.classList.remove("comty-comments-active", "comty-inspect-annotations");
|
|
4454
4678
|
hideInspector();
|
|
4455
4679
|
setSidepanelOpen(false);
|
|
4456
4680
|
closeBubbles();
|
|
@@ -4464,25 +4688,29 @@ function initComty(options = {}) {
|
|
|
4464
4688
|
if (mode === "off") return;
|
|
4465
4689
|
if (mode === "inspect") {
|
|
4466
4690
|
session.mode = "inspect";
|
|
4691
|
+
document.documentElement.classList.add("comty-inspect-annotations");
|
|
4467
4692
|
syncToolbar();
|
|
4693
|
+
renderCachedComments();
|
|
4468
4694
|
ensureLiveTracking();
|
|
4469
4695
|
return;
|
|
4470
4696
|
}
|
|
4471
4697
|
|
|
4472
|
-
if (session.edit && mode === "browse") {
|
|
4698
|
+
if ((session.edit && mode === "browse") || (session.agent && mode === "edit")) {
|
|
4473
4699
|
closeBubbles();
|
|
4474
4700
|
}
|
|
4475
4701
|
const hadComments = session.comments;
|
|
4476
4702
|
session.mode = mode;
|
|
4477
4703
|
sessionEntry = entry;
|
|
4704
|
+
document.documentElement.classList.remove("comty-inspect-annotations");
|
|
4478
4705
|
document.documentElement.classList.toggle("comty-comments-active", session.comments);
|
|
4479
|
-
|
|
4706
|
+
syncPlacementCursor();
|
|
4480
4707
|
if (session.edit) {
|
|
4481
4708
|
setMotionEnabled(false);
|
|
4482
4709
|
selectedElement = null;
|
|
4483
4710
|
} else {
|
|
4484
4711
|
hideInspector();
|
|
4485
4712
|
}
|
|
4713
|
+
if (session.agent && sidepanelOpen) setSidepanelOpen(false);
|
|
4486
4714
|
syncToolbar();
|
|
4487
4715
|
ensureLiveTracking();
|
|
4488
4716
|
if (renderComments || !hadComments) renderCachedComments();
|
|
@@ -4495,6 +4723,10 @@ function initComty(options = {}) {
|
|
|
4495
4723
|
}
|
|
4496
4724
|
|
|
4497
4725
|
function showAuthPrompt(forbidden = false, onReady = startEditSession) {
|
|
4726
|
+
if (localOnly) {
|
|
4727
|
+
showAgentBridgeError("Shared comments need a Comty project key. Use the Agent tool for local tasks without signing in.");
|
|
4728
|
+
return;
|
|
4729
|
+
}
|
|
4498
4730
|
const guestAllowed = allowGuestComments;
|
|
4499
4731
|
document.querySelector(".comty-auth-backdrop")?.remove();
|
|
4500
4732
|
const opener = document.activeElement;
|
|
@@ -4616,14 +4848,16 @@ function initComty(options = {}) {
|
|
|
4616
4848
|
}
|
|
4617
4849
|
|
|
4618
4850
|
function syncToolbar() {
|
|
4619
|
-
if (commentTools.hidden !== session.inspect || inspectorTools.hidden !== !session.inspect) prepareToolbarMotion();
|
|
4851
|
+
if (commentTools.hidden !== (session.inspect || session.agent) || inspectorTools.hidden !== !session.inspect) prepareToolbarMotion();
|
|
4620
4852
|
root.classList.toggle("comty-active", session.comments || session.edit || sidepanelOpen || session.inspect);
|
|
4621
4853
|
browseBtn.setAttribute("aria-pressed", String(!session.edit && !session.inspect));
|
|
4622
4854
|
inspectorBtn.setAttribute("aria-pressed", String(session.inspect));
|
|
4623
|
-
btn.setAttribute("aria-pressed", String(session.edit));
|
|
4624
|
-
|
|
4855
|
+
btn.setAttribute("aria-pressed", String(session.edit && !session.agent));
|
|
4856
|
+
agentBtn?.setAttribute("aria-pressed", String(session.edit && session.agent));
|
|
4857
|
+
commentTools.hidden = session.inspect || session.agent;
|
|
4625
4858
|
inspectorTools.hidden = !session.inspect;
|
|
4626
4859
|
syncInspectorTools();
|
|
4860
|
+
renderAgentPanel();
|
|
4627
4861
|
}
|
|
4628
4862
|
|
|
4629
4863
|
const inspectionLayer = document.createElement("div");
|
|
@@ -5086,6 +5320,13 @@ function initComty(options = {}) {
|
|
|
5086
5320
|
setSession("edit", { renderComments: true });
|
|
5087
5321
|
}
|
|
5088
5322
|
|
|
5323
|
+
function startAgentSession() {
|
|
5324
|
+
if (!isLocalSite) return;
|
|
5325
|
+
if (session.agent) return;
|
|
5326
|
+
closeBubbles();
|
|
5327
|
+
setSession("agent", { renderComments: true });
|
|
5328
|
+
}
|
|
5329
|
+
|
|
5089
5330
|
function toggleSidepanel() {
|
|
5090
5331
|
if (sidepanelOpen) {
|
|
5091
5332
|
setSidepanelOpen(false);
|
|
@@ -5109,7 +5350,7 @@ function initComty(options = {}) {
|
|
|
5109
5350
|
showAuthPrompt();
|
|
5110
5351
|
return;
|
|
5111
5352
|
}
|
|
5112
|
-
if (session.inspect || !isSessionActive()) {
|
|
5353
|
+
if (session.agent || session.inspect || !isSessionActive()) {
|
|
5113
5354
|
startEditSession();
|
|
5114
5355
|
return;
|
|
5115
5356
|
}
|
|
@@ -5397,6 +5638,24 @@ function initComty(options = {}) {
|
|
|
5397
5638
|
ancestors,
|
|
5398
5639
|
layer: layer ? getElementFingerprint(layer) : null,
|
|
5399
5640
|
};
|
|
5641
|
+
// React exposes this in development builds. Keep it as a hint: the coding
|
|
5642
|
+
// agent must confirm the component and variant in source before editing.
|
|
5643
|
+
const fiberKey = Object.keys(el).find(key => key.startsWith('__reactFiber$'));
|
|
5644
|
+
let fiber = fiberKey ? el[fiberKey] : null;
|
|
5645
|
+
const components = [];
|
|
5646
|
+
while (fiber && components.length < 8) {
|
|
5647
|
+
const component = fiber.type;
|
|
5648
|
+
const name = component?.displayName || component?.name;
|
|
5649
|
+
if (name && typeof name === 'string') {
|
|
5650
|
+
const props = fiber.memoizedProps || {};
|
|
5651
|
+
const variants = Object.fromEntries(['variant', 'size', 'tone', 'appearance'].filter(key =>
|
|
5652
|
+
['string', 'number', 'boolean'].includes(typeof props[key])).map(key => [key, props[key]]));
|
|
5653
|
+
const source = fiber._debugSource;
|
|
5654
|
+
components.push({ name, variants, ...(source?.fileName ? { file: source.fileName, line: source.lineNumber } : {}) });
|
|
5655
|
+
}
|
|
5656
|
+
fiber = fiber.return;
|
|
5657
|
+
}
|
|
5658
|
+
if (components.length) context.components = components;
|
|
5400
5659
|
context.candidateCount = [...document.querySelectorAll(context.target.tag)]
|
|
5401
5660
|
.filter(candidate => !isComtyNode(candidate) && matchesAnchorContext(candidate, context)).length;
|
|
5402
5661
|
return context;
|
|
@@ -5425,6 +5684,11 @@ function initComty(options = {}) {
|
|
|
5425
5684
|
};
|
|
5426
5685
|
}
|
|
5427
5686
|
|
|
5687
|
+
function getPagePath() {
|
|
5688
|
+
const { pathname, hash } = window.location;
|
|
5689
|
+
return pathname + (hash.startsWith("#/") || hash.startsWith("#!/") ? hash : "");
|
|
5690
|
+
}
|
|
5691
|
+
|
|
5428
5692
|
function resolveBySelector(selector) {
|
|
5429
5693
|
if (!selector) return null;
|
|
5430
5694
|
try {
|
|
@@ -5446,17 +5710,9 @@ function initComty(options = {}) {
|
|
|
5446
5710
|
}
|
|
5447
5711
|
const context = parseAnchorContext(comment);
|
|
5448
5712
|
const exact = resolveBySelector(comment.selector || "body");
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
if (count !== context.candidateCount) {
|
|
5453
|
-
commentTargets.set(key, { element: null, signature, revision: anchorRevision });
|
|
5454
|
-
return null;
|
|
5455
|
-
}
|
|
5456
|
-
}
|
|
5457
|
-
let element = exact && matchesAnchorContext(exact, context) ? getAnchorElement(exact) : null;
|
|
5458
|
-
if (!element) element = resolveByAnchorContext(context);
|
|
5459
|
-
commentTargets.set(key, { element, signature, revision: anchorRevision });
|
|
5713
|
+
const resolution = resolveAnchor(context, exact);
|
|
5714
|
+
const element = resolution.element ? getAnchorElement(resolution.element) : null;
|
|
5715
|
+
commentTargets.set(key, { ...resolution, element, signature, revision: anchorRevision });
|
|
5460
5716
|
return element;
|
|
5461
5717
|
}
|
|
5462
5718
|
|
|
@@ -5527,9 +5783,14 @@ function initComty(options = {}) {
|
|
|
5527
5783
|
return { deviceType: "desktop", deviceLabel: "Desktop" };
|
|
5528
5784
|
}
|
|
5529
5785
|
|
|
5786
|
+
function syncPlacementCursor() {
|
|
5787
|
+
document.documentElement.classList.toggle("comty-comment-placement", session.edit && !draftAnchor);
|
|
5788
|
+
}
|
|
5789
|
+
|
|
5530
5790
|
function closeBubbles() {
|
|
5531
5791
|
const hadDraft = Boolean(draftAnchor);
|
|
5532
5792
|
draftAnchor = null;
|
|
5793
|
+
syncPlacementCursor();
|
|
5533
5794
|
activeHoverCommentIds.clear();
|
|
5534
5795
|
if (!session.edit || hadDraft) hideInspector();
|
|
5535
5796
|
document
|
|
@@ -5574,7 +5835,7 @@ function initComty(options = {}) {
|
|
|
5574
5835
|
pending: commentId.startsWith("pending-"),
|
|
5575
5836
|
project_id: projectId,
|
|
5576
5837
|
domain: payload.host || window.location.host,
|
|
5577
|
-
page_path: payload.pagePath ||
|
|
5838
|
+
page_path: payload.pagePath || getPagePath(),
|
|
5578
5839
|
selector: payload.selector || "body",
|
|
5579
5840
|
rel_x: payload.relX,
|
|
5580
5841
|
rel_y: payload.relY,
|
|
@@ -5599,7 +5860,7 @@ function initComty(options = {}) {
|
|
|
5599
5860
|
replies: [],
|
|
5600
5861
|
};
|
|
5601
5862
|
|
|
5602
|
-
cachedPagePath =
|
|
5863
|
+
cachedPagePath = getPagePath();
|
|
5603
5864
|
cachedComments = [
|
|
5604
5865
|
...cachedComments.filter(
|
|
5605
5866
|
(cached) => getCommentId(cached) !== commentId,
|
|
@@ -5612,6 +5873,7 @@ function initComty(options = {}) {
|
|
|
5612
5873
|
}
|
|
5613
5874
|
|
|
5614
5875
|
function getCommentAuthorName(comment) {
|
|
5876
|
+
if (comment?.author_kind === 'agent') return 'Agent';
|
|
5615
5877
|
return comment?.author_name || comment?.name || "Anonymous";
|
|
5616
5878
|
}
|
|
5617
5879
|
|
|
@@ -5633,6 +5895,12 @@ function initComty(options = {}) {
|
|
|
5633
5895
|
function createCommentAvatar(comment, extraClass = "") {
|
|
5634
5896
|
const avatar = document.createElement("span");
|
|
5635
5897
|
avatar.className = `comty-comment-avatar${extraClass ? ` ${extraClass}` : ""}`;
|
|
5898
|
+
if (comment?.author_kind === 'agent') {
|
|
5899
|
+
avatar.classList.add('comty-agent-avatar');
|
|
5900
|
+
avatar.setAttribute('aria-label', 'Agent');
|
|
5901
|
+
avatar.innerHTML = '<svg viewBox="0 0 48 48" aria-hidden="true"><path d="M24 2C26.62 2 28.47 5.29 31.27 6.45C34.07 7.61 37.71 6.59 39.56 8.44C41.41 10.29 40.39 13.93 41.55 16.73C42.71 19.53 46 21.38 46 24C46 26.62 42.71 28.47 41.55 31.27C40.39 34.07 41.41 37.71 39.56 39.56C37.71 41.41 34.07 40.39 31.27 41.55C28.47 42.71 26.62 46 24 46C21.38 46 19.53 42.71 16.73 41.55C13.93 40.39 10.29 41.41 8.44 39.56C6.59 37.71 7.61 34.07 6.45 31.27C5.29 28.47 2 26.62 2 24C2 21.38 5.29 19.53 6.45 16.73C7.61 13.93 6.59 10.29 8.44 8.44C10.29 6.59 13.93 7.61 16.73 6.45C19.53 5.29 21.38 2 24 2Z" fill="currentColor"/><path fill="var(--comty-surface)" fill-rule="evenodd" d="M16 33 22.3 15h3.4L32 33h-3.3l-1.3-4h-6.8l-1.3 4H16Zm5.5-6.8h5L24 18.4l-2.5 7.8Z"/></svg>';
|
|
5902
|
+
return avatar;
|
|
5903
|
+
}
|
|
5636
5904
|
const authorName = getCommentAuthorName(comment);
|
|
5637
5905
|
const avatarUrl = getCommentAuthorAvatarUrl(comment);
|
|
5638
5906
|
|
|
@@ -5640,6 +5908,7 @@ function initComty(options = {}) {
|
|
|
5640
5908
|
const image = document.createElement("img");
|
|
5641
5909
|
image.src = avatarUrl;
|
|
5642
5910
|
image.alt = "";
|
|
5911
|
+
image.draggable = false;
|
|
5643
5912
|
image.onerror = () => {
|
|
5644
5913
|
image.remove();
|
|
5645
5914
|
avatar.innerText = authorName.charAt(0).toUpperCase();
|
|
@@ -5864,6 +6133,7 @@ function initComty(options = {}) {
|
|
|
5864
6133
|
payload,
|
|
5865
6134
|
preview: { rect: target.getBoundingClientRect(), rows: getInspectRows(target), selector: getSelector(target), spacing: getSpacingPreview(target) },
|
|
5866
6135
|
};
|
|
6136
|
+
syncPlacementCursor();
|
|
5867
6137
|
ensureLiveTracking();
|
|
5868
6138
|
|
|
5869
6139
|
const pin = document.createElement("div");
|
|
@@ -5881,10 +6151,21 @@ function initComty(options = {}) {
|
|
|
5881
6151
|
|
|
5882
6152
|
const textarea = document.createElement("textarea");
|
|
5883
6153
|
textarea.className = "comty-create-textarea";
|
|
5884
|
-
textarea.placeholder = "Add a comment";
|
|
6154
|
+
textarea.placeholder = payload.kind === "agent" ? "Ask a question or describe a change" : "Add a comment";
|
|
5885
6155
|
textarea.setAttribute("aria-label", textarea.placeholder);
|
|
5886
6156
|
textarea.rows = 1;
|
|
5887
6157
|
|
|
6158
|
+
const scopePicker = document.createElement('label');
|
|
6159
|
+
scopePicker.className = 'comty-agent-scope';
|
|
6160
|
+
if (payload.kind === 'agent') {
|
|
6161
|
+
const input = document.createElement('input');
|
|
6162
|
+
input.type = 'checkbox';
|
|
6163
|
+
input.setAttribute('aria-label', 'Apply to all instances of this variant');
|
|
6164
|
+
const track = document.createElement('span'); track.className = 'comty-agent-scope-track'; track.setAttribute('aria-hidden', 'true');
|
|
6165
|
+
scopePicker.title = 'Apply to all instances of this variant';
|
|
6166
|
+
scopePicker.append(input, track, document.createTextNode('Apply globally'));
|
|
6167
|
+
}
|
|
6168
|
+
|
|
5888
6169
|
const actions = document.createElement("div");
|
|
5889
6170
|
actions.className = "comty-create-actions";
|
|
5890
6171
|
|
|
@@ -5938,7 +6219,7 @@ function initComty(options = {}) {
|
|
|
5938
6219
|
const submitBtn = document.createElement("button");
|
|
5939
6220
|
submitBtn.type = "button";
|
|
5940
6221
|
submitBtn.className = "comty-btn-submit";
|
|
5941
|
-
submitBtn.setAttribute("aria-label", "Post comment");
|
|
6222
|
+
submitBtn.setAttribute("aria-label", payload.kind === "agent" ? "Send agent task" : "Post comment");
|
|
5942
6223
|
submitBtn.disabled = true;
|
|
5943
6224
|
submitBtn.innerHTML = `
|
|
5944
6225
|
<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">
|
|
@@ -5955,49 +6236,62 @@ function initComty(options = {}) {
|
|
|
5955
6236
|
textarea.disabled = true;
|
|
5956
6237
|
linkBtn.disabled = true;
|
|
5957
6238
|
|
|
6239
|
+
if (payload.kind === "agent" && !agent) {
|
|
6240
|
+
showAgentBridgeError(); submitBtn.disabled = false; textarea.disabled = false; return;
|
|
6241
|
+
}
|
|
5958
6242
|
payload.content = content;
|
|
6243
|
+
if (payload.kind === 'agent') payload.scope = scopePicker.querySelector('input').checked ? 'variant' : 'element';
|
|
5959
6244
|
payload.linkUrl = attachedLink || null;
|
|
5960
6245
|
textarea.textContent = content;
|
|
5961
6246
|
|
|
5962
|
-
const
|
|
5963
|
-
const
|
|
5964
|
-
const
|
|
5965
|
-
|
|
6247
|
+
const isAgentTask = payload.kind === "agent";
|
|
6248
|
+
const context = isAgentTask ? null : captureContext();
|
|
6249
|
+
const point = isAgentTask ? null : getPinPoint(pin);
|
|
6250
|
+
const pendingId = isAgentTask ? null : `pending-${crypto.randomUUID()}`;
|
|
5966
6251
|
bubble.remove();
|
|
5967
6252
|
pin.remove();
|
|
5968
6253
|
if (draftAnchor === editorDraft) draftAnchor = null;
|
|
6254
|
+
syncPlacementCursor();
|
|
5969
6255
|
hideInspector();
|
|
5970
|
-
addLocalComment(pendingId, payload);
|
|
6256
|
+
if (!isAgentTask) addLocalComment(pendingId, payload);
|
|
5971
6257
|
let post;
|
|
5972
6258
|
try {
|
|
5973
|
-
post = await fetch(`${apiUrl}/api/comments`, {
|
|
6259
|
+
post = await fetch(payload.kind === "agent" ? `${agentBridgeUrl}/tasks` : `${apiUrl}/api/comments`, {
|
|
5974
6260
|
method: "POST",
|
|
5975
|
-
headers: getSdkHeaders({ "Content-Type": "application/json" }),
|
|
5976
|
-
body: JSON.stringify(payload),
|
|
6261
|
+
headers: payload.kind === "agent" ? agentHeaders() : getSdkHeaders({ "Content-Type": "application/json" }),
|
|
6262
|
+
body: JSON.stringify(payload.kind === "agent" ? { ...payload, projectKey } : payload),
|
|
5977
6263
|
});
|
|
5978
|
-
if (!post.ok) throw new Error(`
|
|
6264
|
+
if (!post.ok) throw new Error(`Save failed (${post.status})`);
|
|
5979
6265
|
const data = await post.json();
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
if (
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
6266
|
+
const id = isAgentTask ? data?.task?.id : data?.id;
|
|
6267
|
+
if (!id) throw new Error("Save returned no ID");
|
|
6268
|
+
if (isAgentTask) {
|
|
6269
|
+
void loadComments({ force: true });
|
|
6270
|
+
} else {
|
|
6271
|
+
cachedComments = cachedComments.filter(item => String(item.id) !== pendingId);
|
|
6272
|
+
if (context.url === location.href) {
|
|
6273
|
+
const comment = addLocalComment(String(id), payload);
|
|
6274
|
+
void uploadCommentScreenshot(comment, context, point);
|
|
6275
|
+
} else void loadComments({ force: true });
|
|
6276
|
+
}
|
|
5987
6277
|
} catch (error) {
|
|
5988
6278
|
console.error("Comty: save failed", error);
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
6279
|
+
if (isAgentTask) showAgentBridgeError(error.message);
|
|
6280
|
+
else {
|
|
6281
|
+
cachedComments = cachedComments.filter(item => String(item.id) !== pendingId);
|
|
6282
|
+
renderSidepanelComments();
|
|
6283
|
+
void renderComments(cachedComments, { retry: false });
|
|
6284
|
+
}
|
|
5992
6285
|
// Keep the original editor and its text available for retry, including attachments.
|
|
5993
6286
|
if (!draftAnchor) draftAnchor = editorDraft;
|
|
6287
|
+
syncPlacementCursor();
|
|
5994
6288
|
root.append(pin, bubble);
|
|
5995
6289
|
submitBtn.disabled = false;
|
|
5996
6290
|
textarea.disabled = false;
|
|
5997
6291
|
linkBtn.disabled = false;
|
|
5998
6292
|
const errorMessage = document.createElement("p");
|
|
5999
6293
|
errorMessage.setAttribute("role", "alert");
|
|
6000
|
-
errorMessage.textContent = "Comment not saved. Your draft is ready to retry
|
|
6294
|
+
errorMessage.textContent = `${isAgentTask ? "Task" : "Comment"} not saved. Your draft is ready to retry.`;
|
|
6001
6295
|
bubble.querySelector('[role="alert"]')?.remove();
|
|
6002
6296
|
bubble.append(errorMessage);
|
|
6003
6297
|
if (bubble.isConnected) textarea.focus();
|
|
@@ -6086,7 +6380,8 @@ function initComty(options = {}) {
|
|
|
6086
6380
|
renderInspector();
|
|
6087
6381
|
};
|
|
6088
6382
|
|
|
6089
|
-
actions.append(
|
|
6383
|
+
if (payload.kind === 'agent') actions.append(scopePicker, submitBtn);
|
|
6384
|
+
else actions.append(linkBtn, submitBtn, linkEditor);
|
|
6090
6385
|
|
|
6091
6386
|
bubble.appendChild(textarea);
|
|
6092
6387
|
bubble.appendChild(attachment);
|
|
@@ -6099,6 +6394,219 @@ function initComty(options = {}) {
|
|
|
6099
6394
|
setTimeout(() => textarea.focus(), 10);
|
|
6100
6395
|
}
|
|
6101
6396
|
|
|
6397
|
+
function agentHeaders() { return { "Content-Type": "application/json", "X-Comty-Token": agent?.token || "" }; }
|
|
6398
|
+
|
|
6399
|
+
function showAgentBridgeError(message = "Start your app with comty dev to use the local agent. Check the terminal if it is already running.") {
|
|
6400
|
+
root.querySelectorAll(".comty-agent-task-toast").forEach((node) => node.remove());
|
|
6401
|
+
const toast = document.createElement("div");
|
|
6402
|
+
toast.className = "comty-auth-toast comty-agent-task-toast";
|
|
6403
|
+
toast.setAttribute("role", "alert");
|
|
6404
|
+
const issue = describeAgentError(message);
|
|
6405
|
+
toast.textContent = issue.title + ". " + issue.description;
|
|
6406
|
+
root.appendChild(toast);
|
|
6407
|
+
setTimeout(() => toast.remove(), 5000);
|
|
6408
|
+
}
|
|
6409
|
+
|
|
6410
|
+
function describeAgentError(error) {
|
|
6411
|
+
const raw = typeof error === 'string' ? error : JSON.stringify(error, null, 2) || 'Unknown error';
|
|
6412
|
+
let value = error;
|
|
6413
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
6414
|
+
if (typeof value === 'string') {
|
|
6415
|
+
try { value = JSON.parse(value); continue; } catch {}
|
|
6416
|
+
const start = value.indexOf('{'), end = value.lastIndexOf('}');
|
|
6417
|
+
if (start >= 0 && end > start) { try { value = JSON.parse(value.slice(start, end + 1)); continue; } catch {} }
|
|
6418
|
+
break;
|
|
6419
|
+
}
|
|
6420
|
+
if (!value || typeof value !== 'object') break;
|
|
6421
|
+
value = value.error || value.message || value.cause || value.stderr || value;
|
|
6422
|
+
}
|
|
6423
|
+
const message = typeof value === 'string' ? value : '';
|
|
6424
|
+
const source = raw + ' ' + message;
|
|
6425
|
+
const agentName = /codex/i.test(source) ? 'Codex' : /claude/i.test(source) ? 'Claude Code' : /cursor/i.test(source) ? 'Cursor' : 'your coding agent';
|
|
6426
|
+
let title, description;
|
|
6427
|
+
if (/requires a newer version|upgrade.*latest|outdated|unsupported.*version/i.test(source)) {
|
|
6428
|
+
title = 'Agent update required'; description = `Update ${agentName}, restart your local development session, then retry this task.`;
|
|
6429
|
+
} else if (/insufficient_quota|quota exceeded|credit balance|billing|usage limit/i.test(source)) {
|
|
6430
|
+
title = 'Usage limit reached'; description = 'Check your agent account’s usage allowance or billing, then retry when capacity is available.';
|
|
6431
|
+
} else if (/rate.?limit|too many requests|\b429\b/i.test(source)) {
|
|
6432
|
+
title = 'Too many requests'; description = 'The agent is temporarily rate limited. Wait a moment before retrying.';
|
|
6433
|
+
} else if (/unauthorized|unauthenticated|authentication|not logged|sign.?in|log.?in|invalid.api.key|\b401\b/i.test(source)) {
|
|
6434
|
+
title = 'Sign in to your agent'; description = `Open ${agentName} and sign in again, then retry this task.`;
|
|
6435
|
+
} else if (/model.*(?:not found|does not exist|unavailable|not supported)|unsupported model|model_not_found/i.test(source)) {
|
|
6436
|
+
title = 'Model unavailable'; description = 'Choose a model available to your agent account in the local Comty configuration, then restart and retry.';
|
|
6437
|
+
} else if (/disconnected|ECONNREFUSED|ENOTFOUND|network|failed to fetch|development server stopped/i.test(source)) {
|
|
6438
|
+
title = 'Connection lost'; description = 'Check that your local development server and coding agent are running, then reload this page.';
|
|
6439
|
+
} else if (/timed? ?out|timeout|exceeded.*minutes/i.test(source)) {
|
|
6440
|
+
title = 'Task timed out'; description = 'Review any changes already made, then retry with a smaller, more specific request.';
|
|
6441
|
+
} else if (/permission|denied|forbidden|\b403\b/i.test(source)) {
|
|
6442
|
+
title = 'Permission needed'; description = 'Check the agent’s access to this project and any pending approval before retrying.';
|
|
6443
|
+
} else if (/ENOENT|command not found|not installed|executable.*not found/i.test(source)) {
|
|
6444
|
+
title = 'Agent not found'; description = `Install or configure ${agentName} in your local development environment, then restart and retry.`;
|
|
6445
|
+
} else if (/completion was not verified/i.test(source)) {
|
|
6446
|
+
title = 'Result needs checking'; description = 'The agent could not confirm completion. Review the changes before retrying.';
|
|
6447
|
+
} else {
|
|
6448
|
+
title = 'Task could not finish';
|
|
6449
|
+
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.';
|
|
6450
|
+
}
|
|
6451
|
+
return { title, description, raw, message };
|
|
6452
|
+
}
|
|
6453
|
+
|
|
6454
|
+
function renderAgentPanel() {
|
|
6455
|
+
if (!agent) return;
|
|
6456
|
+
const visibleTasks = [];
|
|
6457
|
+
const latestResult = [...localTasks].reverse().find(task => ['done', 'in_review'].includes(task.status) && (!task.thread_comment_id || task.status === 'in_review'));
|
|
6458
|
+
if (latestResult) visibleTasks.push(latestResult);
|
|
6459
|
+
if (!visibleTasks.length && !localApprovals.length && !agentActionError) agentPanelOpen = false;
|
|
6460
|
+
let panel = root.querySelector(".comty-agent-panel");
|
|
6461
|
+
if (!panel && !agentPanelOpen) return;
|
|
6462
|
+
if (!panel) {
|
|
6463
|
+
panel = document.createElement("section");
|
|
6464
|
+
panel.className = "comty-agent-panel";
|
|
6465
|
+
panel.setAttribute("aria-label", "Local agent tasks");
|
|
6466
|
+
panel.hidden = true;
|
|
6467
|
+
// Keep the task card above inspection overlays and below the toolbar.
|
|
6468
|
+
root.insertBefore(panel, toolbar);
|
|
6469
|
+
}
|
|
6470
|
+
const open = Boolean(agent) && agentPanelOpen;
|
|
6471
|
+
if (open) {
|
|
6472
|
+
if (panel.hidden) {
|
|
6473
|
+
const rect = toolbar.getBoundingClientRect();
|
|
6474
|
+
panel.dataset.side = rect.left + rect.width / 2 < innerWidth / 2 ? "left" : "right";
|
|
6475
|
+
panel.dataset.closed = "";
|
|
6476
|
+
panel.hidden = false;
|
|
6477
|
+
panel.getBoundingClientRect();
|
|
6478
|
+
}
|
|
6479
|
+
if (panel.hasAttribute("data-closed")) { agentPanelTransition++; delete panel.dataset.closed; }
|
|
6480
|
+
} else if (!panel.hidden && !panel.hasAttribute("data-closed")) {
|
|
6481
|
+
const transition = ++agentPanelTransition;
|
|
6482
|
+
panel.dataset.closed = "";
|
|
6483
|
+
const finish = () => { if (transition === agentPanelTransition && panel.hasAttribute("data-closed")) panel.hidden = true; };
|
|
6484
|
+
if (reducedMotion.matches) finish();
|
|
6485
|
+
else setTimeout(finish, parseFloat(getComputedStyle(root).getPropertyValue("--comty-motion-duration")));
|
|
6486
|
+
}
|
|
6487
|
+
panel.inert = !open;
|
|
6488
|
+
panel.setAttribute("aria-hidden", String(!open));
|
|
6489
|
+
const signature = JSON.stringify([visibleTasks, localApprovals, agentConnectionError, agentActionError, agentPanelOpen]);
|
|
6490
|
+
if (signature === agentPanelSignature) return;
|
|
6491
|
+
agentPanelSignature = signature;
|
|
6492
|
+
root.querySelector('.comty-agent-error-popover')?.remove();
|
|
6493
|
+
const expanded = new Set([...panel.querySelectorAll('details[data-detail-key][open]')].map(node => node.dataset.detailKey));
|
|
6494
|
+
const focusKey = panel.contains(document.activeElement) ? document.activeElement.dataset.focusKey : null;
|
|
6495
|
+
const scrollTop = panel.querySelector('.comty-agent-panel-content')?.scrollTop || 0;
|
|
6496
|
+
const node = (tag, className, text) => {
|
|
6497
|
+
const element = document.createElement(tag);
|
|
6498
|
+
if (className) element.className = className;
|
|
6499
|
+
if (text != null) element.textContent = text;
|
|
6500
|
+
return element;
|
|
6501
|
+
};
|
|
6502
|
+
const brand = () => createCommentAvatar({ author_kind: 'agent' }, 'comty-agent-brand');
|
|
6503
|
+
panel.replaceChildren();
|
|
6504
|
+
const header = node('header', 'comty-agent-panel-header');
|
|
6505
|
+
const heading = node('div', 'comty-agent-heading');
|
|
6506
|
+
heading.append(node('strong', '', 'Agent'));
|
|
6507
|
+
const toggle = node('button', 'comty-agent-action comty-agent-close'); toggle.type = 'button';
|
|
6508
|
+
toggle.innerHTML = icon('<path d="m6 6 12 12M6 18 18 6"/>');
|
|
6509
|
+
toggle.setAttribute('aria-label', 'Close Agent task'); toggle.title = 'Close'; toggle.dataset.focusKey = 'close';
|
|
6510
|
+
toggle.onclick = () => { agentPanelOpen = false; renderAgentPanel(); agentBtn?.focus({ preventScroll: true }); };
|
|
6511
|
+
header.append(heading, toggle); panel.appendChild(header);
|
|
6512
|
+
const content = node('div', 'comty-agent-panel-content'); panel.appendChild(content);
|
|
6513
|
+
const technicalDetails = (parent, raw, key) => {
|
|
6514
|
+
const details = node('details', 'comty-agent-details'); details.dataset.detailKey = key; details.open = expanded.has(key);
|
|
6515
|
+
const summary = node('summary', '', 'Technical details'); summary.dataset.focusKey = 'details-' + key;
|
|
6516
|
+
details.append(summary, node('pre', '', raw)); parent.appendChild(details);
|
|
6517
|
+
};
|
|
6518
|
+
const errorMessage = (parent, error, key) => {
|
|
6519
|
+
const issue = describeAgentError(error);
|
|
6520
|
+
const message = node('div', 'comty-agent-error');
|
|
6521
|
+
message.append(node('strong', '', issue.title), node('p', '', issue.description));
|
|
6522
|
+
parent.appendChild(message); technicalDetails(parent, issue.raw, key);
|
|
6523
|
+
};
|
|
6524
|
+
if (agentConnectionError || agentActionError) {
|
|
6525
|
+
const banner = node('div', 'comty-agent-item comty-agent-banner'); banner.setAttribute('role', 'alert');
|
|
6526
|
+
errorMessage(banner, agentConnectionError || agentActionError, 'connection'); content.appendChild(banner);
|
|
6527
|
+
}
|
|
6528
|
+
const action = (parent, label, path, input = {}, primary = false) => {
|
|
6529
|
+
const button = node('button', 'comty-agent-action', label); button.type = 'button';
|
|
6530
|
+
const key = path + JSON.stringify(input); button.dataset.focusKey = key;
|
|
6531
|
+
button.disabled = agentPendingActions.has(key);
|
|
6532
|
+
if (primary) button.dataset.primary = '';
|
|
6533
|
+
button.onclick = async () => {
|
|
6534
|
+
if (agentPendingActions.has(key)) return;
|
|
6535
|
+
agentActionError = '';
|
|
6536
|
+
agentPendingActions.add(key); button.disabled = true; button.textContent = 'Please wait…';
|
|
6537
|
+
try {
|
|
6538
|
+
const response = await fetch(`${agentBridgeUrl}/${path}`, { method: 'POST', headers: agentHeaders(), body: JSON.stringify(input) });
|
|
6539
|
+
if (!response.ok) {
|
|
6540
|
+
const body = await response.json().catch(() => null);
|
|
6541
|
+
throw new Error(typeof body?.error === 'string' ? body.error : JSON.stringify(body?.error || { status: response.status, message: 'Request failed' }));
|
|
6542
|
+
}
|
|
6543
|
+
await loadComments({ force: true });
|
|
6544
|
+
} catch (error) { agentActionError = error.message; }
|
|
6545
|
+
finally { agentPendingActions.delete(key); agentPanelSignature = ''; renderAgentPanel(); }
|
|
6546
|
+
};
|
|
6547
|
+
parent.appendChild(button);
|
|
6548
|
+
};
|
|
6549
|
+
for (const approval of localApprovals) {
|
|
6550
|
+
const section = node('div', 'comty-agent-item');
|
|
6551
|
+
section.append(node('h3', '', 'Approval required'));
|
|
6552
|
+
const task = localTasks.find(task => task.id === approval.taskId);
|
|
6553
|
+
if (task) section.appendChild(node('p', '', task.content));
|
|
6554
|
+
section.appendChild(node('p', '', 'Review this operation before allowing the agent to continue.'));
|
|
6555
|
+
const operation = approval.details?.command || approval.details?.file_path || approval.details?.path;
|
|
6556
|
+
if (typeof operation === 'string' || Array.isArray(operation)) section.appendChild(node('pre', '', Array.isArray(operation) ? operation.join(' ') : operation));
|
|
6557
|
+
technicalDetails(section, JSON.stringify(approval.details ?? {}, null, 2), 'approval-' + approval.id);
|
|
6558
|
+
const actions = node('div', 'comty-agent-actions');
|
|
6559
|
+
action(actions, 'Allow once', `approvals/${approval.id}`, { allow: true }, true);
|
|
6560
|
+
action(actions, 'Deny', `approvals/${approval.id}`, { allow: false });
|
|
6561
|
+
section.appendChild(actions); content.appendChild(section);
|
|
6562
|
+
}
|
|
6563
|
+
const list = node('div', 'comty-agent-list'); content.appendChild(list);
|
|
6564
|
+
const hideErrorPopover = () => root.querySelector('.comty-agent-error-popover')?.remove();
|
|
6565
|
+
content.addEventListener('scroll', hideErrorPopover, { passive: true });
|
|
6566
|
+
for (const [index, task] of visibleTasks.reverse().entries()) {
|
|
6567
|
+
const failed = Boolean(task.error) || task.status === 'in_review';
|
|
6568
|
+
const state = task.status === 'done' ? 'success' : failed ? 'error' : 'working';
|
|
6569
|
+
const row = node('div', 'comty-agent-row'); row.dataset.taskId = task.id;
|
|
6570
|
+
const dot = state === 'working' ? createWorkingIndicator() : node('span', 'comty-agent-dot');
|
|
6571
|
+
dot.dataset.status = state; dot.setAttribute('aria-hidden', 'true');
|
|
6572
|
+
const name = node('span', 'comty-agent-row-name', task.thread_comment_id ? `@Agent · ${task.content}` : task.content);
|
|
6573
|
+
row.append(dot, name);
|
|
6574
|
+
if (state === 'working') row.appendChild(node('span', 'comty-agent-row-state', task.status === 'todo' ? 'Starting' : 'Working'));
|
|
6575
|
+
const actions = node('div', 'comty-agent-row-actions');
|
|
6576
|
+
if (['in_review', 'cancel'].includes(task.status)) action(actions, 'Retry', `tasks/${task.id}/retry`);
|
|
6577
|
+
if (['todo', 'in_progress'].includes(task.status)) action(actions, 'Cancel', `tasks/${task.id}/cancel`);
|
|
6578
|
+
if (task.status === 'done') action(actions, 'Dismiss', `tasks/${task.id}/dismiss`);
|
|
6579
|
+
if (task.status === 'in_review') action(actions, 'Dismiss', `tasks/${task.id}/dismiss`);
|
|
6580
|
+
if (actions.childElementCount) row.appendChild(actions);
|
|
6581
|
+
row.setAttribute('aria-label', `${task.content}, ${state === 'error' ? 'failed' : state === 'success' ? 'completed' : state === 'cancel' ? 'cancelled' : task.status === 'todo' ? 'starting' : 'working'}`);
|
|
6582
|
+
if (failed) {
|
|
6583
|
+
const tooltipId = `comty-agent-error-${index}`;
|
|
6584
|
+
const showErrorPopover = () => {
|
|
6585
|
+
hideErrorPopover();
|
|
6586
|
+
const issue = describeAgentError(task.error || task.result?.summary || task.progress || 'Completion was not verified');
|
|
6587
|
+
const popover = node('div', 'comty-agent-error-popover'); popover.id = tooltipId; popover.setAttribute('role', 'tooltip');
|
|
6588
|
+
popover.append(node('strong', '', task.content), node('p', '', issue.message || issue.description));
|
|
6589
|
+
root.appendChild(popover);
|
|
6590
|
+
const rowRect = row.getBoundingClientRect();
|
|
6591
|
+
const popoverRect = popover.getBoundingClientRect();
|
|
6592
|
+
const left = panel.dataset.side === 'right' ? rowRect.left - popoverRect.width - 8 : rowRect.right + 8;
|
|
6593
|
+
popover.style.left = `${Math.max(8, Math.min(left, innerWidth - popoverRect.width - 8))}px`;
|
|
6594
|
+
popover.style.top = `${Math.max(8, Math.min(rowRect.top - 4, innerHeight - popoverRect.height - 8))}px`;
|
|
6595
|
+
};
|
|
6596
|
+
row.addEventListener('mouseenter', showErrorPopover);
|
|
6597
|
+
row.addEventListener('mouseleave', hideErrorPopover);
|
|
6598
|
+
}
|
|
6599
|
+
list.appendChild(row);
|
|
6600
|
+
if (task.result?.summary) {
|
|
6601
|
+
const answer = node('div', 'comty-agent-answer');
|
|
6602
|
+
answer.append(brand(), node('p', '', task.result.summary));
|
|
6603
|
+
list.appendChild(answer);
|
|
6604
|
+
}
|
|
6605
|
+
}
|
|
6606
|
+
content.scrollTop = scrollTop;
|
|
6607
|
+
if (focusKey && open) [...panel.querySelectorAll('[data-focus-key]')].find(element => element.dataset.focusKey === focusKey)?.focus({ preventScroll: true });
|
|
6608
|
+
}
|
|
6609
|
+
|
|
6102
6610
|
function setSidepanelOpen(open) {
|
|
6103
6611
|
sidepanelOpen = open;
|
|
6104
6612
|
sidepanel.classList.toggle("comty-sidepanel-open", sidepanelOpen);
|
|
@@ -6134,12 +6642,52 @@ function initComty(options = {}) {
|
|
|
6134
6642
|
|
|
6135
6643
|
function openCommentFromPanel(comment) {
|
|
6136
6644
|
const target = resolveCommentElement(comment);
|
|
6137
|
-
if (!target || isElementTreeHidden(target))
|
|
6645
|
+
if (!target || isElementTreeHidden(target)) {
|
|
6646
|
+
openDetachedThread(comment);
|
|
6647
|
+
return;
|
|
6648
|
+
}
|
|
6138
6649
|
setSession(session.comments ? session.mode : "browse", { entry: sessionEntry, renderComments: true });
|
|
6139
6650
|
target.scrollIntoView({ block: "center", inline: "nearest", behavior: "instant" });
|
|
6651
|
+
const rect = target.getBoundingClientRect();
|
|
6652
|
+
const relX = Number(comment.rel_x ?? comment.relX);
|
|
6653
|
+
const relY = Number(comment.rel_y ?? comment.relY);
|
|
6654
|
+
// Large page containers must reveal the annotation point, not their center.
|
|
6655
|
+
window.scrollBy({
|
|
6656
|
+
left: rect.width > innerWidth && Number.isFinite(relX) ? rect.left + Math.max(0, Math.min(1, relX)) * rect.width - innerWidth / 2 : 0,
|
|
6657
|
+
top: rect.height > innerHeight && Number.isFinite(relY) ? rect.top + Math.max(0, Math.min(1, relY)) * rect.height - innerHeight / 2 : 0,
|
|
6658
|
+
behavior: "instant",
|
|
6659
|
+
});
|
|
6140
6660
|
syncCommentAnchors();
|
|
6141
6661
|
const pin = findPlacedPin(comment);
|
|
6142
6662
|
if (pin && !pin.hasAttribute("data-comty-anchor-hidden")) pin.click();
|
|
6663
|
+
else openDetachedThread(comment);
|
|
6664
|
+
}
|
|
6665
|
+
|
|
6666
|
+
function openDetachedThread(comment) {
|
|
6667
|
+
openCommentThread(comment, null, null, innerWidth / 2, innerHeight / 3, () => {});
|
|
6668
|
+
const bubble = [...root.querySelectorAll(".comty-bubble")].find(node => node.dataset.commentId === String(comment.id));
|
|
6669
|
+
if (bubble) {
|
|
6670
|
+
bubble.dataset.detached = "";
|
|
6671
|
+
positionCommentBubble(bubble, innerWidth / 2, innerHeight / 3);
|
|
6672
|
+
}
|
|
6673
|
+
}
|
|
6674
|
+
|
|
6675
|
+
function updateAnchorBadge(item, comment) {
|
|
6676
|
+
resolveCommentElement(comment);
|
|
6677
|
+
const result = commentTargets.get(String(comment.id));
|
|
6678
|
+
const legacy = getPagePath() !== window.location.pathname && comment.page_path === window.location.pathname;
|
|
6679
|
+
const status = legacy && !result?.element ? "uncertain" : result?.status;
|
|
6680
|
+
const label = status === "missing" ? "Target missing" : status === "uncertain" ? "Target uncertain" : "";
|
|
6681
|
+
let badge = item.querySelector(".comty-anchor-badge");
|
|
6682
|
+
if (!label) { badge?.remove(); return; }
|
|
6683
|
+
if (!badge) {
|
|
6684
|
+
badge = document.createElement("span");
|
|
6685
|
+
badge.className = "comty-anchor-badge";
|
|
6686
|
+
item.querySelector(".comty-sidepanel-item-body").appendChild(badge);
|
|
6687
|
+
}
|
|
6688
|
+
if (badge.textContent !== label) badge.textContent = label;
|
|
6689
|
+
badge.title = legacy ? "This older comment has no saved hash route. Its target may be on another page." :
|
|
6690
|
+
status === "missing" ? "The target is not present on this page. The comment is still available." : "The target cannot be identified reliably. No pin is placed.";
|
|
6143
6691
|
}
|
|
6144
6692
|
|
|
6145
6693
|
let sidepanelFilters = { status: "", device: "", dateSort: "desc" };
|
|
@@ -6152,6 +6700,7 @@ function initComty(options = {}) {
|
|
|
6152
6700
|
? sidepanelSearch.value.trim().toLowerCase()
|
|
6153
6701
|
: "";
|
|
6154
6702
|
let list = cachedComments.filter((comment) => {
|
|
6703
|
+
if (comment.local_agent || comment.kind === "agent") return false;
|
|
6155
6704
|
if (
|
|
6156
6705
|
search &&
|
|
6157
6706
|
!`${comment.content ?? ""} ${getCommentLabel(comment)}`
|
|
@@ -6198,6 +6747,7 @@ function initComty(options = {}) {
|
|
|
6198
6747
|
const item = document.createElement("button");
|
|
6199
6748
|
item.type = "button";
|
|
6200
6749
|
item.className = "comty-sidepanel-item";
|
|
6750
|
+
item.dataset.commentId = String(comment.id);
|
|
6201
6751
|
item.disabled = Boolean(comment.pending);
|
|
6202
6752
|
item.innerHTML = `
|
|
6203
6753
|
<span class="comty-sidepanel-item-status" aria-hidden="true"></span>
|
|
@@ -6221,8 +6771,13 @@ function initComty(options = {}) {
|
|
|
6221
6771
|
if (metaEl instanceof HTMLElement) {
|
|
6222
6772
|
metaEl.innerText = `${comment.author_name || "Anonymous"} · ${comment.pending ? "Sending…" : getCommentLabel(comment)}`;
|
|
6223
6773
|
metaEl.title = getCommentLabel(comment);
|
|
6774
|
+
if (getPagePath() !== window.location.pathname && comment.page_path === window.location.pathname && !resolveCommentElement(comment)) {
|
|
6775
|
+
metaEl.innerText += " · Original page unknown";
|
|
6776
|
+
}
|
|
6224
6777
|
}
|
|
6225
6778
|
|
|
6779
|
+
updateAnchorBadge(item, comment);
|
|
6780
|
+
|
|
6226
6781
|
item.onclick = (event) => {
|
|
6227
6782
|
event.stopPropagation();
|
|
6228
6783
|
openCommentFromPanel(comment);
|
|
@@ -6338,14 +6893,15 @@ function initComty(options = {}) {
|
|
|
6338
6893
|
createStatusFilters();
|
|
6339
6894
|
|
|
6340
6895
|
async function updateCommentStatus(comment, status) {
|
|
6341
|
-
if (!projectId || !comment?.id) return false;
|
|
6896
|
+
if (comment?.local_agent || !projectId || !comment?.id) return false;
|
|
6342
6897
|
|
|
6343
6898
|
let res;
|
|
6344
6899
|
try {
|
|
6345
|
-
|
|
6900
|
+
const local = comment.local_agent === true;
|
|
6901
|
+
res = await fetch(local ? `${agentBridgeUrl}/tasks/${encodeURIComponent(comment.id)}/status` : `${apiUrl}/api/comments/${comment.id}/status`, {
|
|
6346
6902
|
method: "POST",
|
|
6347
|
-
headers: getSdkHeaders({ "Content-Type": "application/json" }),
|
|
6348
|
-
body: JSON.stringify({
|
|
6903
|
+
headers: local ? { "Content-Type": "application/json" } : getSdkHeaders({ "Content-Type": "application/json" }),
|
|
6904
|
+
body: JSON.stringify(local ? { status } : {
|
|
6349
6905
|
projectKey,
|
|
6350
6906
|
host: window.location.host,
|
|
6351
6907
|
status,
|
|
@@ -6362,7 +6918,7 @@ function initComty(options = {}) {
|
|
|
6362
6918
|
}
|
|
6363
6919
|
|
|
6364
6920
|
const data = await res.json();
|
|
6365
|
-
comment.status = getCommentStatus(data.status).value;
|
|
6921
|
+
comment.status = getCommentStatus(data.task?.status ?? data.status).value;
|
|
6366
6922
|
return comment.status;
|
|
6367
6923
|
}
|
|
6368
6924
|
|
|
@@ -6554,6 +7110,23 @@ function initComty(options = {}) {
|
|
|
6554
7110
|
isOwnCommentEntry, updateCommentContent, deleteSdkComment, getCommentId,
|
|
6555
7111
|
updateReplyContent, deleteSdkReply, createCommentAvatar, getSdkHeaders,
|
|
6556
7112
|
showAuthPrompt,
|
|
7113
|
+
startThreadAgent: async (comment, replies, content) => {
|
|
7114
|
+
if (!agent) throw new Error('Start the local agent to reply with @Agent');
|
|
7115
|
+
if (!canManage) throw new Error('Sign in as a project editor to use @Agent');
|
|
7116
|
+
const transcript = [
|
|
7117
|
+
`${getCommentAuthorName(comment)}: ${comment.content}`,
|
|
7118
|
+
...replies.map(reply => `${getCommentAuthorName(reply)}: ${reply.content}`),
|
|
7119
|
+
].join('\n').slice(-24000);
|
|
7120
|
+
const response = await fetch(`${agentBridgeUrl}/tasks`, {
|
|
7121
|
+
method: 'POST', headers: agentHeaders(),
|
|
7122
|
+
body: JSON.stringify({ content, pagePath: comment.page_path || getPagePath(),
|
|
7123
|
+
selector: comment.selector || 'body', relX: comment.rel_x ?? 0.5, relY: comment.rel_y ?? 0.5,
|
|
7124
|
+
domElement: comment.dom_element || '', viewport: comment.viewport || '',
|
|
7125
|
+
anchorContext: comment.anchor_context || '', scope: 'element',
|
|
7126
|
+
thread: { commentId: String(comment.id), transcript } }),
|
|
7127
|
+
});
|
|
7128
|
+
if (!response.ok) throw new Error((await response.json().catch(() => null))?.error || 'Could not start Agent');
|
|
7129
|
+
},
|
|
6557
7130
|
get canWrite() { return canWrite; },
|
|
6558
7131
|
get canManage() { return canManage; },
|
|
6559
7132
|
get allowGuestComments() { return allowGuestComments; },
|
|
@@ -6754,42 +7327,112 @@ function initComty(options = {}) {
|
|
|
6754
7327
|
function reconcileComments(comments) {
|
|
6755
7328
|
const previous = new Map(cachedComments.map(comment => [String(comment.id), comment]));
|
|
6756
7329
|
cachedComments = comments
|
|
6757
|
-
.filter(comment => comment.kind !== "agent"
|
|
7330
|
+
.filter(comment => comment.kind !== "agent" || comment.status !== "done")
|
|
6758
7331
|
.map(comment => Object.assign(previous.get(String(comment.id)) || {}, comment));
|
|
6759
7332
|
for (const comment of previous.values()) {
|
|
6760
7333
|
if (comment.pending && !cachedComments.some(item => item.id === comment.id)) cachedComments.push(comment);
|
|
6761
7334
|
}
|
|
6762
7335
|
}
|
|
6763
7336
|
|
|
7337
|
+
async function deliverThreadReply(task) {
|
|
7338
|
+
if (deliveringAgentReplies.has(task.id)) return;
|
|
7339
|
+
deliveringAgentReplies.add(task.id);
|
|
7340
|
+
try {
|
|
7341
|
+
const response = await fetch(`${apiUrl}/api/comments/${encodeURIComponent(task.thread_comment_id)}/replies`, {
|
|
7342
|
+
method: 'POST', headers: getSdkHeaders({ 'Content-Type': 'application/json' }),
|
|
7343
|
+
body: JSON.stringify({ projectKey, content: task.result.summary, agentTaskId: task.id }),
|
|
7344
|
+
});
|
|
7345
|
+
if (!response.ok) throw new Error(`Could not save Agent reply (${response.status})`);
|
|
7346
|
+
const { reply } = await response.json();
|
|
7347
|
+
const local = cachedComments.find(comment => String(comment.id) === task.thread_comment_id);
|
|
7348
|
+
if (local && !local.replies?.some(item => item.id === reply.id)) {
|
|
7349
|
+
local.replies ||= [];
|
|
7350
|
+
local.replies.push(reply);
|
|
7351
|
+
}
|
|
7352
|
+
for (const bubble of root.querySelectorAll('.comty-bubble')) {
|
|
7353
|
+
if (bubble.dataset.commentId === task.thread_comment_id) bubble.dispatchEvent(new CustomEvent('comty-agent-reply', { detail: reply }));
|
|
7354
|
+
}
|
|
7355
|
+
const ack = await fetch(`${agentBridgeUrl}/tasks/${task.id}/dismiss`, { method: 'POST', headers: agentHeaders(), body: '{}' });
|
|
7356
|
+
if (!ack.ok) throw new Error('Agent reply saved, but local acknowledgement failed');
|
|
7357
|
+
localTasks = localTasks.filter(item => item.id !== task.id);
|
|
7358
|
+
} catch (error) {
|
|
7359
|
+
agentActionError = error.message;
|
|
7360
|
+
console.error('Comty: Agent reply delivery failed', error);
|
|
7361
|
+
} finally {
|
|
7362
|
+
deliveringAgentReplies.delete(task.id);
|
|
7363
|
+
agentPanelSignature = '';
|
|
7364
|
+
renderAgentPanel();
|
|
7365
|
+
}
|
|
7366
|
+
}
|
|
7367
|
+
|
|
6764
7368
|
async function loadComments({ force = false } = {}) {
|
|
6765
|
-
if (!projectId) return;
|
|
6766
|
-
const pagePath =
|
|
7369
|
+
if (!projectId && !agent) return;
|
|
7370
|
+
const pagePath = getPagePath();
|
|
6767
7371
|
if (!force && cachedPagePath === pagePath) { await renderComments(cachedComments); return; }
|
|
6768
7372
|
if (pendingCommentsLoad) {
|
|
6769
7373
|
await pendingCommentsLoad;
|
|
6770
|
-
if (!force) return;
|
|
7374
|
+
if (!force && cachedPagePath === pagePath) return;
|
|
6771
7375
|
}
|
|
6772
7376
|
pendingCommentsLoad = (async () => {
|
|
6773
|
-
let comments = cachedPagePath === pagePath ? cachedComments : [];
|
|
7377
|
+
let comments = cachedPagePath === pagePath ? cachedComments.filter(comment => !comment.local_agent) : [];
|
|
6774
7378
|
if (projectId) {
|
|
6775
7379
|
try {
|
|
6776
|
-
const
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
7380
|
+
const paths = [...new Set([pagePath, window.location.pathname])];
|
|
7381
|
+
const batches = await Promise.all(paths.map(async path => {
|
|
7382
|
+
const url = new URL(`${apiUrl}/api/comments`);
|
|
7383
|
+
url.search = new URLSearchParams({ projectKey, host: window.location.host, pagePath: path, ...(force ? { _t: String(Date.now()) } : {}) });
|
|
7384
|
+
const res = await fetch(url);
|
|
7385
|
+
if (!res.ok) throw new Error(await res.text());
|
|
7386
|
+
const data = await res.json();
|
|
7387
|
+
return Array.isArray(data.comments) ? data.comments : [];
|
|
7388
|
+
}));
|
|
7389
|
+
comments = [...new Map(batches.flat().map(comment => [String(comment.id), comment])).values()];
|
|
6782
7390
|
} catch (error) { console.error("Comty: list comments failed", error); }
|
|
6783
7391
|
}
|
|
7392
|
+
if (agent) {
|
|
7393
|
+
try {
|
|
7394
|
+
const res = await fetch(`${agentBridgeUrl}/tasks`, { headers: agentHeaders(), cache: "no-store" });
|
|
7395
|
+
if (!res.ok) throw new Error("Local companion unavailable");
|
|
7396
|
+
const data = await res.json();
|
|
7397
|
+
if (Array.isArray(data.tasks)) localTasks = data.tasks;
|
|
7398
|
+
const failedTasks = localTasks.filter(task => task.status === 'in_review');
|
|
7399
|
+
if (failedTasks.some(task => !seenFailedAgentTasks.has(task.id))) agentPanelOpen = true;
|
|
7400
|
+
seenFailedAgentTasks.clear();
|
|
7401
|
+
for (const task of failedTasks) seenFailedAgentTasks.add(task.id);
|
|
7402
|
+
if (Array.isArray(data.approvals)) {
|
|
7403
|
+
localApprovals = data.approvals;
|
|
7404
|
+
if (localApprovals.some(approval => !seenAgentApprovals.has(approval.id))) agentPanelOpen = true;
|
|
7405
|
+
seenAgentApprovals.clear();
|
|
7406
|
+
for (const approval of localApprovals) seenAgentApprovals.add(approval.id);
|
|
7407
|
+
}
|
|
7408
|
+
agentConnectionError = "";
|
|
7409
|
+
} catch (error) {
|
|
7410
|
+
agentConnectionError = "Local agent disconnected. Check the development server, then reload this page.";
|
|
7411
|
+
console.error("Comty: local tasks failed", error);
|
|
7412
|
+
}
|
|
7413
|
+
for (const task of localTasks.filter(task => task.thread_comment_id && task.status === 'done' && task.result?.summary)) {
|
|
7414
|
+
void deliverThreadReply(task);
|
|
7415
|
+
}
|
|
7416
|
+
comments.push(...localTasks.filter(task => task.page_path === pagePath && !task.thread_comment_id && !["done", "cancel"].includes(task.status)));
|
|
7417
|
+
renderAgentPanel();
|
|
7418
|
+
}
|
|
7419
|
+
if (pagePath !== getPagePath()) return;
|
|
6784
7420
|
reconcileComments(comments);
|
|
6785
7421
|
cachedPagePath = pagePath;
|
|
6786
7422
|
renderSidepanelComments();
|
|
6787
7423
|
await renderComments(cachedComments);
|
|
7424
|
+
syncAgentWorkingOutlines();
|
|
6788
7425
|
ensureLiveTracking();
|
|
6789
7426
|
})();
|
|
6790
7427
|
try { await pendingCommentsLoad; } finally { pendingCommentsLoad = null; }
|
|
6791
7428
|
}
|
|
6792
7429
|
|
|
7430
|
+
if (agent) {
|
|
7431
|
+
setInterval(() => {
|
|
7432
|
+
if (!pendingCommentsLoad && document.visibilityState === "visible") void loadComments({ force: true });
|
|
7433
|
+
}, 2000);
|
|
7434
|
+
}
|
|
7435
|
+
|
|
6793
7436
|
function getPinPoint(pin) {
|
|
6794
7437
|
return { x: parseFloat(pin.style.left), y: parseFloat(pin.style.top) };
|
|
6795
7438
|
}
|
|
@@ -6817,7 +7460,7 @@ function initComty(options = {}) {
|
|
|
6817
7460
|
}
|
|
6818
7461
|
|
|
6819
7462
|
function syncCommentAnchors() {
|
|
6820
|
-
if (!session.comments) return;
|
|
7463
|
+
if (!session.comments && !session.inspect) return;
|
|
6821
7464
|
const modal = getTopActiveModal();
|
|
6822
7465
|
for (const comment of cachedComments) {
|
|
6823
7466
|
const id = String(comment.id);
|
|
@@ -6828,7 +7471,7 @@ function initComty(options = {}) {
|
|
|
6828
7471
|
placePin(comment, modal);
|
|
6829
7472
|
pin = placedPins.get(id);
|
|
6830
7473
|
}
|
|
6831
|
-
const bubbles = [...root.querySelectorAll(".comty-bubble")].filter(bubble => bubble.dataset.commentId === id || bubble.dataset.hoverId === id);
|
|
7474
|
+
const bubbles = [...root.querySelectorAll(".comty-bubble")].filter(bubble => (bubble.dataset.commentId === id || bubble.dataset.hoverId === id) && !bubble.hasAttribute("data-detached"));
|
|
6832
7475
|
// A pin can remove its target's :hover style as the pointer enters it.
|
|
6833
7476
|
if (!point && pin && target?.isConnected && !target.hasAttribute("hidden") && !isElementTreeHidden(target.parentElement) &&
|
|
6834
7477
|
(activeHoverCommentIds.has(id) || bubbles.some(bubble => bubble.dataset.commentId === id))) continue;
|
|
@@ -6843,6 +7486,10 @@ function initComty(options = {}) {
|
|
|
6843
7486
|
if (pin.style.top !== y) pin.style.top = y;
|
|
6844
7487
|
for (const bubble of bubbles) positionCommentBubble(bubble, point.clientX, point.clientY);
|
|
6845
7488
|
}
|
|
7489
|
+
if (sidepanelOpen) for (const item of sidepanelList.querySelectorAll(".comty-sidepanel-item")) {
|
|
7490
|
+
const comment = cachedComments.find(comment => String(comment.id) === item.dataset.commentId);
|
|
7491
|
+
if (comment) updateAnchorBadge(item, comment);
|
|
7492
|
+
}
|
|
6846
7493
|
if (draftAnchor) {
|
|
6847
7494
|
if (!draftAnchor.target?.isConnected) draftAnchor.target = resolveCommentElement(draftAnchor.payload);
|
|
6848
7495
|
const point = getVisibleAnchorPoint(draftAnchor.target, draftAnchor.payload.relX, draftAnchor.payload.relY, modal);
|
|
@@ -6856,16 +7503,50 @@ function initComty(options = {}) {
|
|
|
6856
7503
|
}
|
|
6857
7504
|
}
|
|
6858
7505
|
|
|
7506
|
+
function syncAgentWorkingOutlines() {
|
|
7507
|
+
const working = localTasks.filter(task => task.status === "in_progress" && task.page_path === getPagePath());
|
|
7508
|
+
const ids = new Set(working.map(task => String(task.id)));
|
|
7509
|
+
for (const [id, outline] of workingOutlines) {
|
|
7510
|
+
if (ids.has(id)) continue;
|
|
7511
|
+
outline.remove(); workingOutlines.delete(id);
|
|
7512
|
+
}
|
|
7513
|
+
const modal = getTopActiveModal();
|
|
7514
|
+
for (const task of working) {
|
|
7515
|
+
const id = String(task.id);
|
|
7516
|
+
let outline = workingOutlines.get(id);
|
|
7517
|
+
if (!outline) {
|
|
7518
|
+
outline = document.createElement("div");
|
|
7519
|
+
outline.className = "comty-agent-working";
|
|
7520
|
+
outline.setAttribute("aria-label", "Agent working on this element");
|
|
7521
|
+
const badge = document.createElement("span");
|
|
7522
|
+
badge.className = "comty-agent-working-badge";
|
|
7523
|
+
badge.append(createWorkingIndicator(), "Working");
|
|
7524
|
+
outline.appendChild(badge); root.appendChild(outline);
|
|
7525
|
+
workingOutlines.set(id, outline);
|
|
7526
|
+
}
|
|
7527
|
+
const target = resolveCommentElement(task);
|
|
7528
|
+
const point = target && getVisibleAnchorPoint(target, Number(task.rel_x), Number(task.rel_y), modal);
|
|
7529
|
+
outline.hidden = !point;
|
|
7530
|
+
if (!point) continue;
|
|
7531
|
+
const rect = target.getBoundingClientRect();
|
|
7532
|
+
setMeasuredRect(outline, rect);
|
|
7533
|
+
outline.toggleAttribute("data-badge-below", rect.top < 32);
|
|
7534
|
+
}
|
|
7535
|
+
}
|
|
7536
|
+
|
|
6859
7537
|
function onFrame() {
|
|
6860
7538
|
if (motion.enabled) updateMotion();
|
|
6861
|
-
if (session.comments) syncCommentAnchors();
|
|
7539
|
+
if (session.comments || session.inspect) syncCommentAnchors();
|
|
7540
|
+
if (session.agent || workingOutlines.size || localTasks.some(task => task.status === "in_progress" && task.page_path === getPagePath())) {
|
|
7541
|
+
syncAgentWorkingOutlines();
|
|
7542
|
+
}
|
|
6862
7543
|
if (session.inspect || session.edit || inspectionLayer.childNodes.length) renderInspector();
|
|
6863
7544
|
if (session.inspect && rulersEnabled) renderRulers();
|
|
6864
7545
|
if (session.inspect && rulersEnabled) measurements.render();
|
|
6865
7546
|
}
|
|
6866
7547
|
|
|
6867
7548
|
function ensureLiveTracking() {
|
|
6868
|
-
const active = motion.enabled || session.inspect || session.edit || (session.comments && cachedComments.length > 0);
|
|
7549
|
+
const active = motion.enabled || session.inspect || session.edit || (session.comments && cachedComments.length > 0) || workingOutlines.size > 0;
|
|
6869
7550
|
if (!active || document.hidden) {
|
|
6870
7551
|
if (liveFrame) cancelAnimationFrame(liveFrame);
|
|
6871
7552
|
liveFrame = null;
|
|
@@ -6885,13 +7566,14 @@ function initComty(options = {}) {
|
|
|
6885
7566
|
syncOverlayHost();
|
|
6886
7567
|
|
|
6887
7568
|
function renderCachedComments() {
|
|
6888
|
-
if (!session.comments && !session.inspect && !session.edit) return;
|
|
7569
|
+
if (!session.comments && !session.inspect && !session.edit && !workingOutlines.size) return;
|
|
6889
7570
|
if (resizeRenderFrame) {
|
|
6890
7571
|
window.cancelAnimationFrame(resizeRenderFrame);
|
|
6891
7572
|
}
|
|
6892
7573
|
resizeRenderFrame = window.requestAnimationFrame(() => {
|
|
6893
7574
|
resizeRenderFrame = null;
|
|
6894
7575
|
syncCommentAnchors();
|
|
7576
|
+
syncAgentWorkingOutlines();
|
|
6895
7577
|
renderInspector();
|
|
6896
7578
|
ensureLiveTracking();
|
|
6897
7579
|
});
|
|
@@ -6933,6 +7615,29 @@ function initComty(options = {}) {
|
|
|
6933
7615
|
characterData: true,
|
|
6934
7616
|
});
|
|
6935
7617
|
|
|
7618
|
+
let activePagePath = getPagePath();
|
|
7619
|
+
function handleRouteChange() {
|
|
7620
|
+
const next = getPagePath();
|
|
7621
|
+
if (next === activePagePath) return;
|
|
7622
|
+
activePagePath = next;
|
|
7623
|
+
closeBubbles();
|
|
7624
|
+
commentTargets.clear();
|
|
7625
|
+
reconcileComments([]);
|
|
7626
|
+
void renderComments([], { retry: false });
|
|
7627
|
+
renderSidepanelComments();
|
|
7628
|
+
void loadComments({ force: true });
|
|
7629
|
+
}
|
|
7630
|
+
window.addEventListener("hashchange", handleRouteChange);
|
|
7631
|
+
window.addEventListener("popstate", handleRouteChange);
|
|
7632
|
+
for (const method of ["pushState", "replaceState"]) {
|
|
7633
|
+
const original = history[method];
|
|
7634
|
+
history[method] = function (...args) {
|
|
7635
|
+
const result = original.apply(this, args);
|
|
7636
|
+
handleRouteChange();
|
|
7637
|
+
return result;
|
|
7638
|
+
};
|
|
7639
|
+
}
|
|
7640
|
+
|
|
6936
7641
|
document.addEventListener("scroll", handleHostLayoutEvent, {
|
|
6937
7642
|
capture: true,
|
|
6938
7643
|
passive: true,
|
|
@@ -7070,13 +7775,14 @@ function initComty(options = {}) {
|
|
|
7070
7775
|
const y = e.clientY;
|
|
7071
7776
|
|
|
7072
7777
|
const anchorContext = getAnchorContext(el);
|
|
7778
|
+
if (session.agent && anchorContext) anchorContext.inspectedStyles = getInspectRows(el);
|
|
7073
7779
|
|
|
7074
7780
|
const payload = {
|
|
7075
7781
|
...getDeviceContext(),
|
|
7076
7782
|
projectId,
|
|
7077
|
-
kind: "comment",
|
|
7783
|
+
kind: session.agent ? "agent" : "comment",
|
|
7078
7784
|
host: window.location.host,
|
|
7079
|
-
pagePath:
|
|
7785
|
+
pagePath: getPagePath(),
|
|
7080
7786
|
selector: getSelector(el),
|
|
7081
7787
|
relX,
|
|
7082
7788
|
relY,
|
|
@@ -7174,6 +7880,7 @@ function initComty(options = {}) {
|
|
|
7174
7880
|
window.addEventListener("blur", () => { measureAlt = false; renderInspector(); });
|
|
7175
7881
|
|
|
7176
7882
|
async function boot() {
|
|
7883
|
+
if (localOnly) { await loadComments(); return; }
|
|
7177
7884
|
const res = await fetch(`${apiUrl}/api/sdk/bootstrap`, {
|
|
7178
7885
|
method: "POST",
|
|
7179
7886
|
headers: getSdkHeaders({ "Content-Type": "application/json" }),
|
|
@@ -7186,6 +7893,7 @@ function initComty(options = {}) {
|
|
|
7186
7893
|
|
|
7187
7894
|
if (!res.ok) {
|
|
7188
7895
|
console.error("Comty: bootstrap failed", await res.text());
|
|
7896
|
+
if (agent) await loadComments();
|
|
7189
7897
|
return;
|
|
7190
7898
|
}
|
|
7191
7899
|
|
|
@@ -7230,16 +7938,30 @@ function initComty(options = {}) {
|
|
|
7230
7938
|
|
|
7231
7939
|
if (typeof options.onTestReady === "function") {
|
|
7232
7940
|
options.onTestReady({
|
|
7233
|
-
getSelector, getAnchorContext, resolveCommentElement, getVisibleAnchorPoint,
|
|
7941
|
+
getPagePath, getSelector, getAnchorContext, resolveCommentElement, getVisibleAnchorPoint,
|
|
7234
7942
|
getDistanceSegments, getInspectRows, syncCommentAnchors,
|
|
7235
7943
|
setInspectorMode(enabled) { setSession(enabled ? "inspect" : "off"); },
|
|
7236
7944
|
startEditSession, startBrowseSession, endSession, showInspector,
|
|
7237
|
-
motion, setMotionEnabled, updateMotion,
|
|
7945
|
+
motion, setMotionEnabled, updateMotion, describeAgentError,
|
|
7946
|
+
previewAgent(tasks, approvals = [], error = "") {
|
|
7947
|
+
localTasks = tasks;
|
|
7948
|
+
localApprovals = approvals;
|
|
7949
|
+
agentConnectionError = error;
|
|
7950
|
+
startAgentSession();
|
|
7951
|
+
agentPanelOpen = true;
|
|
7952
|
+
agentPanelSignature = "";
|
|
7953
|
+
renderAgentPanel();
|
|
7954
|
+
},
|
|
7238
7955
|
seed(comments) {
|
|
7239
7956
|
reconcileComments(comments);
|
|
7240
7957
|
setSession(session.comments ? session.mode : "browse", { entry: sessionEntry, renderComments: true });
|
|
7241
7958
|
return renderComments(cachedComments, { retry: false });
|
|
7242
7959
|
},
|
|
7960
|
+
seedAgentTasks(tasks) {
|
|
7961
|
+
localTasks = tasks;
|
|
7962
|
+
syncAgentWorkingOutlines();
|
|
7963
|
+
ensureLiveTracking();
|
|
7964
|
+
},
|
|
7243
7965
|
setCanWrite(value) { canWrite = value; canManage = value; },
|
|
7244
7966
|
invalidate() { anchorRevision += 1; },
|
|
7245
7967
|
});
|
|
@@ -7247,6 +7969,7 @@ function initComty(options = {}) {
|
|
|
7247
7969
|
|
|
7248
7970
|
return boot().catch(error => {
|
|
7249
7971
|
console.error("Comty: bootstrap failed", error);
|
|
7972
|
+
if (agent) return loadComments();
|
|
7250
7973
|
});
|
|
7251
7974
|
}
|
|
7252
7975
|
}
|