clay-server 4.1.0-beta.18 → 4.1.0-beta.19
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/lib/project.js +14 -1
- package/lib/public/app.js +1 -0
- package/lib/public/css/command-palette.css +20 -0
- package/lib/public/css/issues.css +62 -29
- package/lib/public/modules/app-favicon.js +10 -0
- package/lib/public/modules/app-messages.js +3 -0
- package/lib/public/modules/chat-render-runtime.js +20 -0
- package/lib/public/modules/clay-issue-links.js +23 -2
- package/lib/public/modules/clay-log-links.js +11 -1
- package/lib/public/modules/issues-render.js +36 -10
- package/lib/public/modules/issues.js +91 -7
- package/lib/public/modules/markdown.js +3 -1
- package/lib/public/modules/pane-bridge.js +6 -0
- package/lib/public/modules/pane-issue-bridge.js +27 -0
- package/lib/public/modules/project-logs-render.js +2 -2
- package/lib/public/modules/split-view.js +2 -0
- package/lib/public/modules/thinking-lifecycle.js +4 -1
- package/lib/sdk-bridge.js +11 -0
- package/lib/server.js +15 -6
- package/lib/yoke/adapters/codex.js +2 -0
- package/lib/yoke/index.js +5 -1
- package/lib/yoke/mcp-bridge-server.js +18 -5
- package/package.json +1 -1
package/lib/project.js
CHANGED
|
@@ -257,11 +257,20 @@ function createProjectContext(opts) {
|
|
|
257
257
|
var serverPort = opts.port || 2633;
|
|
258
258
|
var serverTls = opts.tls || false;
|
|
259
259
|
var serverAuthToken = opts.authToken || null;
|
|
260
|
+
var mcpBridgeToken = opts.mcpBridgeToken || crypto.randomBytes(32).toString("hex");
|
|
260
261
|
var latestVersion = null;
|
|
261
262
|
var sessionTitleMigrationScheduled = false;
|
|
262
263
|
|
|
263
264
|
// --- YOKE adapters (multi-vendor, lazy init) ---
|
|
264
|
-
var _yokeState = yoke.createAdapters({
|
|
265
|
+
var _yokeState = yoke.createAdapters({
|
|
266
|
+
cwd: cwd,
|
|
267
|
+
slug: slug,
|
|
268
|
+
osUsers: osUsers,
|
|
269
|
+
clayPort: serverPort,
|
|
270
|
+
clayTls: serverTls,
|
|
271
|
+
clayAuthToken: serverAuthToken,
|
|
272
|
+
clayBridgeToken: mcpBridgeToken,
|
|
273
|
+
});
|
|
265
274
|
var adapters = _yokeState.adapters;
|
|
266
275
|
// A new project has no remembered vendor. Select the first installed
|
|
267
276
|
// provider by the shared Claude -> Codex -> Kiro preference order.
|
|
@@ -1228,6 +1237,7 @@ function createProjectContext(opts) {
|
|
|
1228
1237
|
clayPort: serverPort,
|
|
1229
1238
|
clayTls: serverTls,
|
|
1230
1239
|
clayAuthToken: serverAuthToken,
|
|
1240
|
+
clayBridgeToken: mcpBridgeToken,
|
|
1231
1241
|
getWorkerPermissionRouter: function () {
|
|
1232
1242
|
return isMate ? null : _sessionPair.workerPermission;
|
|
1233
1243
|
},
|
|
@@ -2670,6 +2680,9 @@ function createProjectContext(opts) {
|
|
|
2670
2680
|
handleDisconnection: handleDisconnection,
|
|
2671
2681
|
handleHTTP: handleHTTP,
|
|
2672
2682
|
getMcpBridgeHandler: getMcpBridgeHandler,
|
|
2683
|
+
isMcpBridgeAuthenticated: function (req) {
|
|
2684
|
+
return !!(req && req.headers && req.headers["x-clay-bridge-token"] && req.headers["x-clay-bridge-token"] === mcpBridgeToken);
|
|
2685
|
+
},
|
|
2673
2686
|
getStatus: getStatus,
|
|
2674
2687
|
getSessionManager: function () { return sm; },
|
|
2675
2688
|
createDelegatedSession: _delegatedSession.createDelegatedSession,
|
package/lib/public/app.js
CHANGED
|
@@ -400,6 +400,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
400
400
|
issuesRequests: {},
|
|
401
401
|
issuesViewRequest: null,
|
|
402
402
|
issuesViewGeneration: 0,
|
|
403
|
+
issuesUpdateAvailable: null,
|
|
403
404
|
issuesCache: {},
|
|
404
405
|
issuesEpoch: 0,
|
|
405
406
|
issuesSearchTimer: null,
|
|
@@ -513,6 +513,7 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
|
|
|
513
513
|
@keyframes searchClaySpin { to { transform: rotate(360deg); } }
|
|
514
514
|
|
|
515
515
|
.clayos-session-link,
|
|
516
|
+
.clayos-issue-link,
|
|
516
517
|
.clayos-log-link {
|
|
517
518
|
display: inline-flex;
|
|
518
519
|
align-items: center;
|
|
@@ -531,6 +532,7 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
|
|
|
531
532
|
cursor: pointer;
|
|
532
533
|
}
|
|
533
534
|
.clayos-session-link::before,
|
|
535
|
+
.clayos-issue-link::before,
|
|
534
536
|
.clayos-log-link::before {
|
|
535
537
|
content: "\2197";
|
|
536
538
|
display: grid;
|
|
@@ -542,20 +544,28 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
|
|
|
542
544
|
color: var(--accent);
|
|
543
545
|
font-size: 12px;
|
|
544
546
|
}
|
|
547
|
+
.clayos-issue-link::before,
|
|
545
548
|
.clayos-log-link::before { content: "\2261"; }
|
|
546
549
|
.clayos-session-link > span,
|
|
550
|
+
.clayos-issue-link > span,
|
|
547
551
|
.clayos-log-link > span { font-weight: 620; }
|
|
548
552
|
.clayos-session-link > small,
|
|
553
|
+
.clayos-issue-link > small,
|
|
549
554
|
.clayos-log-link > small { color: var(--text-muted); font-size: 10px; }
|
|
550
555
|
.clayos-session-link:hover,
|
|
556
|
+
.clayos-issue-link:hover,
|
|
551
557
|
.clayos-log-link:hover { border-color: color-mix(in srgb, var(--accent) 52%, var(--border)); background: color-mix(in srgb, var(--accent) 11%, var(--bg-alt)); }
|
|
552
558
|
.clayos-session-link:focus-visible,
|
|
559
|
+
.clayos-issue-link:focus-visible,
|
|
553
560
|
.clayos-log-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
554
561
|
.clayos-session-link.is-loading,
|
|
562
|
+
.clayos-issue-link.is-loading,
|
|
555
563
|
.clayos-log-link.is-loading { opacity: 0.62; cursor: progress; }
|
|
556
564
|
.clayos-session-link.is-loading::before,
|
|
565
|
+
.clayos-issue-link.is-loading::before,
|
|
557
566
|
.clayos-log-link.is-loading::before { animation: searchClaySpin 1.2s linear infinite; }
|
|
558
567
|
.clayos-session-link.is-error,
|
|
568
|
+
.clayos-issue-link.is-error,
|
|
559
569
|
.clayos-log-link.is-error { border-color: color-mix(in srgb, var(--warning) 48%, var(--border)); }
|
|
560
570
|
|
|
561
571
|
.search-clay-pending {
|
|
@@ -913,3 +923,13 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
|
|
|
913
923
|
font-family: inherit;
|
|
914
924
|
color: var(--text-muted);
|
|
915
925
|
}
|
|
926
|
+
|
|
927
|
+
.clayos-issue-link { max-width: min(100%, 30em); box-sizing: border-box; }
|
|
928
|
+
.clayos-issue-link::before { content: "\25CE"; flex: 0 0 17px; }
|
|
929
|
+
.clayos-issue-link[data-status="in_progress"]::before { color: #bd923f; }
|
|
930
|
+
.clayos-issue-link[data-status="resolved"]::before { content: "\2713"; color: var(--brand-green); }
|
|
931
|
+
.clayos-issue-link[data-status="closed"]::before { content: "\00D7"; color: var(--text-dimmer); }
|
|
932
|
+
.clayos-issue-link > span { flex-shrink: 0; }
|
|
933
|
+
.clayos-issue-link > small { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
934
|
+
.clayos-log-link { max-width: 100%; box-sizing: border-box; }
|
|
935
|
+
.clayos-log-link > small { min-width: 0; overflow-wrap: anywhere; line-height: 1.35; }
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
#issues-panel .issues-icon-btn .lucide { width: 15px; }
|
|
25
25
|
#issues-panel .issues-icon-btn.hidden { display: none; }
|
|
26
26
|
#issues-panel .issues-icon-btn[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
|
|
27
|
-
#issues-panel button,
|
|
27
|
+
#issues-panel :where(button:not(.clayos-issue-link):not(.clayos-log-link), input, textarea, select) { font: inherit; color: inherit; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 8px 10px; }
|
|
28
28
|
#issues-panel button { cursor: pointer; }
|
|
29
|
-
#issues-panel button:hover { background: var(--bg-hover, rgba(128,128,128,.12)); }
|
|
30
|
-
#issues-panel button:disabled { opacity: .5; cursor:
|
|
29
|
+
#issues-panel button:not(.clayos-issue-link):not(.clayos-log-link):hover { background: var(--bg-hover, rgba(128,128,128,.12)); }
|
|
30
|
+
#issues-panel button:disabled { opacity: .5; cursor: default; }
|
|
31
31
|
#issues-panel :focus-visible { outline: 2px solid var(--accent, #b98655); outline-offset: 2px; }
|
|
32
32
|
.issues-filters { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--border-subtle); background: color-mix(in srgb, var(--bg-alt) 72%, transparent); }
|
|
33
33
|
.issues-search { flex: 1; min-width: 120px; height: 36px; display: flex; align-items: center; gap: 8px; padding: 0 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text-dimmer); }
|
|
@@ -38,35 +38,44 @@
|
|
|
38
38
|
.issues-notice { margin: 0; padding: 0 16px; font-size: 12px; color: var(--text-muted); }
|
|
39
39
|
.issue-row { display: block; width: 100%; text-align: left; margin: 0; padding: 15px 18px 14px !important; border: 0 !important; border-bottom: 1px solid var(--border-subtle) !important; border-left: 2px solid transparent !important; border-radius: 0 !important; background: transparent !important; }
|
|
40
40
|
.issue-row:hover { background: color-mix(in srgb, var(--accent) 5%, transparent) !important; border-left-color: var(--accent) !important; }
|
|
41
|
-
.issue-row-head { display: flex; align-items:
|
|
41
|
+
.issue-row-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 7px; }
|
|
42
42
|
.issue-type { padding: 2px 7px; border-radius: 999px; background: rgba(var(--overlay-rgb), .08); color: var(--text-secondary); font-size: 10px; font-weight: 700; text-transform: uppercase; }
|
|
43
|
-
.issue-row strong {
|
|
43
|
+
.issue-row strong { flex: 1; min-width: 0; font-size: 14px; font-weight: 700; letter-spacing: -.01em; overflow-wrap: anywhere; }
|
|
44
44
|
.issue-row p { margin: 7px 0 0; color: var(--text-secondary); font-size: 12.5px; line-height: 1.55; }
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
.issue-row small { font-size: 10.5px; font-family: var(--font-mono); }
|
|
47
|
+
.issue-row-meta { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; margin-top: 8px; color: var(--text-dimmer); font: 10.5px/1.5 var(--font-mono); }
|
|
48
|
+
.issue-row-meta .issue-status { font-family: var(--font-sans); }
|
|
47
49
|
.issue-status { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; letter-spacing: .03em; }
|
|
48
50
|
.issue-status::before { content: ''; width: 7px; height: 7px; border: 1px solid currentColor; border-radius: 50%; }
|
|
49
|
-
[data-status="in_progress"] { color: #bd923f; }
|
|
50
|
-
[data-status="resolved"] { color: #479b77; }
|
|
51
|
-
[data-status="closed"] { color: var(--text-muted); }
|
|
51
|
+
.issue-status[data-status="in_progress"] { color: #bd923f; }
|
|
52
|
+
.issue-status[data-status="resolved"] { color: #479b77; }
|
|
53
|
+
.issue-status[data-status="closed"] { color: var(--text-muted); }
|
|
52
54
|
.issue-detail { max-width: 760px; margin: 0 auto; padding: 32px 28px 52px; }
|
|
53
|
-
.issue-
|
|
54
|
-
.issue-detail
|
|
55
|
-
.issue-
|
|
56
|
-
.issue-detail .
|
|
57
|
-
.issue-
|
|
58
|
-
.issue-
|
|
59
|
-
.issue-detail
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
#issues-panel .issue-
|
|
63
|
-
|
|
64
|
-
#issues-panel .issue-comment
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
55
|
+
.issue-doc-chips { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; margin-bottom: 10px; }
|
|
56
|
+
.issue-detail .issue-doc-title { margin: 0 0 10px; font-family: var(--font-display); font-size: clamp(24px, 2.8vw, 34px); letter-spacing: -.025em; line-height: 1.12; overflow-wrap: anywhere; }
|
|
57
|
+
.issue-doc-summary { color: var(--text-secondary); font-size: 14px; line-height: 1.6; }
|
|
58
|
+
.issue-detail .issue-doc-summary > p { margin: 0 0 13px; }
|
|
59
|
+
.issue-byline { margin: 0 0 18px; padding-bottom: 18px; border-bottom: 2px solid var(--border-subtle); color: var(--text-dimmer); font: 11px/1.6 var(--font-mono); overflow-wrap: anywhere; }
|
|
60
|
+
.issue-doc-body { margin: 24px 0 0; font-size: 14px; line-height: 1.75; overflow-wrap: anywhere; }
|
|
61
|
+
.issue-detail section { margin-top: 26px; overflow-wrap: anywhere; }
|
|
62
|
+
.issue-detail section h3 { margin: 0 0 12px; font-family: var(--font-display); font-size: 15px; color: var(--text); }
|
|
63
|
+
.issue-evidence { padding-top: 18px; border-top: 1px solid var(--border-subtle); font-size: 13px; line-height: 1.65; }
|
|
64
|
+
#issues-panel .issue-discussion { margin-top: 30px; padding-top: 18px; border-top: 1px solid var(--border-subtle); }
|
|
65
|
+
.issue-discussion h3 > span { margin-left: 4px; font: 11px var(--font-mono); color: var(--text-dimmer); }
|
|
66
|
+
#issues-panel .issue-comment { padding: 10px 12px; margin-bottom: 8px; border: 1px solid var(--border-subtle); border-radius: 9px; background: var(--bg-alt); }
|
|
67
|
+
.issue-comment-meta { margin-bottom: 5px; color: var(--text-dimmer); font: 10.5px/1.4 var(--font-mono); }
|
|
68
|
+
.issue-comment .markdown-body { font-size: 13px; line-height: 1.55; overflow-wrap: anywhere; }
|
|
69
|
+
.issue-comment .markdown-body > :first-child { margin-top: 0; }
|
|
70
|
+
.issue-comment .markdown-body > :last-child { margin-bottom: 0; }
|
|
71
|
+
.issue-comment-review { margin-top: 9px; padding: 8px 10px; border-left: 2px solid var(--accent); background: rgba(var(--overlay-rgb), .04); border-radius: 0 7px 7px 0; }
|
|
72
|
+
.issue-comment-review > span { display: block; margin-bottom: 5px; color: var(--text-dimmer); font: 10.5px var(--font-mono); }
|
|
73
|
+
.issue-discussion-empty { margin: 0; color: var(--text-dimmer); font-size: 12px; }
|
|
74
|
+
#issue-comment-form { display: grid; gap: 8px; margin-top: 18px; }
|
|
75
|
+
#issue-comment-form label { color: var(--text-secondary); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; }
|
|
76
|
+
#issues-panel #issue-comment-input { width: 100%; box-sizing: border-box; min-height: 80px; resize: vertical; font-size: 13px; line-height: 1.55; border-radius: 8px; padding: 10px; background: var(--bg-alt); }
|
|
77
|
+
#issue-comment-form .issue-comment-status { min-height: 16px; margin: 0; color: var(--text-dimmer); font-size: 11px; }
|
|
78
|
+
.issue-comment-actions { display: flex; justify-content: flex-end; }
|
|
70
79
|
#issues-panel .issues-delete-dialog { position: absolute; inset: 0; z-index: 2; display: grid; place-items: center; padding: 24px; background: rgba(0, 0, 0, .38); }
|
|
71
80
|
#issues-panel .issues-delete-dialog.hidden { display: none; }
|
|
72
81
|
#issues-panel .issues-delete-card { width: min(360px, 100%); box-sizing: border-box; padding: 22px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); box-shadow: 0 14px 40px rgba(0, 0, 0, .24); }
|
|
@@ -91,8 +100,8 @@
|
|
|
91
100
|
.issues-content > h2 { padding: 0 28px; }
|
|
92
101
|
.issues-content > h2 { margin: 0 0 10px; font-family: var(--font-display); font-size: 14px; line-height: 1.2; }
|
|
93
102
|
.issues-history-note { margin: 0; padding: 14px 18px; border-bottom: 1px solid var(--border-subtle); color: var(--text-dimmer); font-size: 11px; font-family: var(--font-mono); }
|
|
94
|
-
|
|
95
|
-
@media (max-width: 1023px) { #issues-panel, #issues-panel.issues-wide { position: fixed; inset: 0; z-index: 300; width: 100%; max-width: none; min-width: 0; height: 100%; margin: 0; border: 0; border-radius: 0; padding-top: var(--safe-top, 0px); padding-bottom: var(--safe-bottom, 0px); box-sizing: border-box; } #issues-panel button { min-height: 36px; } }
|
|
103
|
+
|
|
104
|
+
@media (max-width: 1023px) { #issues-panel, #issues-panel.issues-wide { position: fixed; inset: 0; z-index: 300; width: 100%; max-width: none; min-width: 0; height: 100%; margin: 0; border: 0; border-radius: 0; padding-top: var(--safe-top, 0px); padding-bottom: var(--safe-bottom, 0px); box-sizing: border-box; } #issues-panel button:not(.clayos-issue-link):not(.clayos-log-link) { min-height: 36px; } }
|
|
96
105
|
@media (max-width: 1023px) { #issues-panel [data-issues-wide], #issues-panel [data-issues-full] { display: none; } }
|
|
97
106
|
@media (max-width: 390px) {
|
|
98
107
|
#issues-panel header { gap: 4px; padding: 8px; }
|
|
@@ -100,7 +109,7 @@
|
|
|
100
109
|
#issues-panel [data-issues-wide], #issues-panel [data-issues-full] { display: none; }
|
|
101
110
|
.issues-filters { display: grid; grid-template-columns: 1fr 1fr; padding: 8px; }
|
|
102
111
|
.issues-filters .issues-search { grid-column: 1 / -1; width: 100%; box-sizing: border-box; }
|
|
103
|
-
.issues-content { padding:
|
|
112
|
+
.issues-content { padding: 0; }
|
|
104
113
|
.issue-row { padding: 12px !important; }
|
|
105
114
|
.issue-detail h2 { font-size: 20px; }
|
|
106
115
|
}
|
|
@@ -113,3 +122,27 @@
|
|
|
113
122
|
.issues-content > h2 { padding-right: 18px; padding-left: 18px; }
|
|
114
123
|
.issue-detail { padding: 24px 18px 38px; }
|
|
115
124
|
}
|
|
125
|
+
|
|
126
|
+
#issues-panel { font-family: var(--font-sans); font-size: 13px; }
|
|
127
|
+
#issues-panel .issues-title { font-size: 13px; }
|
|
128
|
+
.issues-notice:empty { display: none; }
|
|
129
|
+
.issues-notice:not(:empty) { padding: 10px 16px; border-bottom: 1px solid var(--border-subtle); line-height: 1.5; }
|
|
130
|
+
.issues-inline-update { display: inline-flex; align-items: center; gap: 7px; width: 100%; box-sizing: border-box; margin: 0 0 8px; padding: 7px 9px; border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border)); border-radius: 7px; background: color-mix(in srgb, var(--accent) 7%, transparent); color: var(--text-secondary); font-size: 11px; }
|
|
131
|
+
.issues-inline-update button { margin-left: auto; padding: 3px 8px !important; border-color: var(--accent) !important; color: var(--accent) !important; font-size: 11px !important; font-weight: 700; }
|
|
132
|
+
.issue-type { letter-spacing: .03em; flex-shrink: 0; }
|
|
133
|
+
#issues-panel .issue-actions { margin: 0 0 24px; gap: 6px; }
|
|
134
|
+
#issues-panel .issue-actions button, #issues-panel .issue-comment-actions button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 7px 10px; border-radius: 7px; font-size: 11px; font-weight: 600; background: var(--bg-alt); color: var(--text-secondary); }
|
|
135
|
+
#issues-panel .issue-actions .lucide { width: 13px; height: 13px; }
|
|
136
|
+
#issues-panel button.issue-primary { border-color: var(--accent); background: var(--accent); color: #fff; }
|
|
137
|
+
#issues-panel button.issue-primary:hover { background: var(--accent-hover, var(--accent)); }
|
|
138
|
+
#issues-panel .issue-actions button.issue-danger { margin-left: auto; color: var(--danger); background: transparent; }
|
|
139
|
+
#issues-panel #issue-form .issue-actions { margin-top: 18px; }
|
|
140
|
+
#issues-panel.panel-fullscreen .issue-detail { padding: clamp(28px, 5vw, 64px); }
|
|
141
|
+
.issues-load-more { padding: 16px; text-align: center; }
|
|
142
|
+
#issues-panel .issues-filters select { height: 36px; max-width: 42%; }
|
|
143
|
+
@media (max-width: 480px) {
|
|
144
|
+
.issues-filters .issues-search { flex-basis: 100%; }
|
|
145
|
+
#issues-panel .issues-filters select { flex: 1; max-width: none; }
|
|
146
|
+
.issue-row-head strong { flex-basis: 100%; }
|
|
147
|
+
#issues-panel .issue-actions button.issue-danger { margin-left: 0; }
|
|
148
|
+
}
|
|
@@ -194,6 +194,16 @@ export function stopUrgentBlink() {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
export function setActivity(text) {
|
|
197
|
+
var thinkingState = store.get('thinkingState');
|
|
198
|
+
if (text === "thinking" && thinkingState && thinkingState.active) {
|
|
199
|
+
// Live Thinking already owns the tail progress UI. Compaction and tool
|
|
200
|
+
// activity intentionally continue through so their distinct states show.
|
|
201
|
+
if (getActivityEl()) {
|
|
202
|
+
getActivityEl().remove();
|
|
203
|
+
setActivityEl(null);
|
|
204
|
+
}
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
197
207
|
if (text) {
|
|
198
208
|
if (!getActivityEl()) {
|
|
199
209
|
var _actEl = document.createElement("div");
|
|
@@ -1054,6 +1054,9 @@ export function processMessage(msg) {
|
|
|
1054
1054
|
case "thinking_start":
|
|
1055
1055
|
removeMatePreThinking();
|
|
1056
1056
|
startThinking();
|
|
1057
|
+
// The live Thinking entry is the progress affordance while reasoning
|
|
1058
|
+
// is streaming; avoid a second bouncing indicator at the same tail.
|
|
1059
|
+
setActivity(null);
|
|
1057
1060
|
break;
|
|
1058
1061
|
|
|
1059
1062
|
case "thinking_delta":
|
|
@@ -145,6 +145,26 @@ export function addToMessages(element) {
|
|
|
145
145
|
var messagesEl = getMessagesEl();
|
|
146
146
|
if (prependAnchor) messagesEl.insertBefore(element, prependAnchor);
|
|
147
147
|
else messagesEl.appendChild(element);
|
|
148
|
+
keepActiveThinkingAtTail();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Thinking is a single accumulating record for the active turn. Other live
|
|
152
|
+
// transcript entries may arrive between reasoning segments, so keep only the
|
|
153
|
+
// currently active record after the tail activity indicator.
|
|
154
|
+
export function keepActiveThinkingAtTail() {
|
|
155
|
+
if (prependAnchor || store.get('replayingHistory')) return;
|
|
156
|
+
var thinkingState = store.get('thinkingState');
|
|
157
|
+
var thinkingEl = thinkingState && thinkingState.active && thinkingState.el;
|
|
158
|
+
var messagesEl = getMessagesEl();
|
|
159
|
+
if (!thinkingEl || !messagesEl || thinkingEl.parentNode !== messagesEl) return;
|
|
160
|
+
if (activityEl && activityEl.parentNode === messagesEl) {
|
|
161
|
+
if (activityEl.nextSibling !== thinkingEl || messagesEl.lastElementChild !== thinkingEl) {
|
|
162
|
+
messagesEl.appendChild(activityEl);
|
|
163
|
+
messagesEl.appendChild(thinkingEl);
|
|
164
|
+
}
|
|
165
|
+
} else if (messagesEl.lastElementChild !== thinkingEl) {
|
|
166
|
+
messagesEl.appendChild(thinkingEl);
|
|
167
|
+
}
|
|
148
168
|
}
|
|
149
169
|
|
|
150
170
|
export function scrollToBottom() {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { store } from './store.js';
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { forwardPaneIssueReference } from './pane-bridge.js';
|
|
3
4
|
function accountKey(state) { return state.isMultiUserMode ? 'user:' + String(state.myUserId || '') : 'single'; }
|
|
4
5
|
function requestContext(state) { return { accountKey: accountKey(state), projectSlug: state.currentSlug || null, epoch: state.issuesEpoch || 0 }; }
|
|
5
6
|
function sameContext(request, state) {
|
|
@@ -42,12 +43,17 @@ function createIssueLink(ref, labelText) {
|
|
|
42
43
|
button.appendChild(meta);
|
|
43
44
|
}
|
|
44
45
|
var cache = store.get('issuesCache') || {};
|
|
45
|
-
if (cache[ref])
|
|
46
|
+
if (cache[ref]) updateIssueLink(button, cache[ref]);
|
|
46
47
|
setTimeout(function () { resolve(ref); }, 0);
|
|
47
48
|
return button;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
export function enhanceClayIssueLinks(root) {
|
|
52
|
+
root.querySelectorAll('a').forEach(function (anchor) {
|
|
53
|
+
var ref = anchor.getAttribute('data-clay-record-ref') || anchor.getAttribute('href');
|
|
54
|
+
if (anchor.closest('pre, code, button') || !isExactClayIssueReference(ref)) return;
|
|
55
|
+
anchor.replaceWith(createIssueLink(ref.trim(), anchor.textContent.trim()));
|
|
56
|
+
});
|
|
51
57
|
var codeNodes = root.querySelectorAll("code");
|
|
52
58
|
for (var codeIndex = 0; codeIndex < codeNodes.length; codeIndex++) {
|
|
53
59
|
var code = codeNodes[codeIndex];
|
|
@@ -80,6 +86,20 @@ export function enhanceClayIssueLinks(root) {
|
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
|
|
89
|
+
export function updateIssueLink(button, target) {
|
|
90
|
+
var status = target.status || 'open';
|
|
91
|
+
var title = target.title || 'Issue';
|
|
92
|
+
button.replaceChildren();
|
|
93
|
+
var label = document.createElement('span');
|
|
94
|
+
label.textContent = 'Issue';
|
|
95
|
+
var meta = document.createElement('small');
|
|
96
|
+
meta.textContent = title;
|
|
97
|
+
button.append(label, meta);
|
|
98
|
+
button.dataset.status = status;
|
|
99
|
+
button.title = title + ' · ' + status.replace(/_/g, ' ');
|
|
100
|
+
button.setAttribute('aria-label', 'Open Project Issue: ' + button.title);
|
|
101
|
+
}
|
|
102
|
+
|
|
83
103
|
function resolve(ref) {
|
|
84
104
|
var ws = getWs();
|
|
85
105
|
var requests = store.get('issueLinkRequests') || {};
|
|
@@ -101,7 +121,7 @@ export function handleIssueReference(msg) {
|
|
|
101
121
|
}
|
|
102
122
|
document.querySelectorAll('[data-issue-ref]').forEach(function (button) {
|
|
103
123
|
if (button.dataset.issueRef !== msg.ref) return;
|
|
104
|
-
if (msg.target)
|
|
124
|
+
if (msg.target) updateIssueLink(button, msg.target);
|
|
105
125
|
else button.title = msg.error || 'Issue unavailable';
|
|
106
126
|
});
|
|
107
127
|
if (store.get('issueLinkOpenRef') === msg.ref) {
|
|
@@ -111,6 +131,7 @@ export function handleIssueReference(msg) {
|
|
|
111
131
|
return true;
|
|
112
132
|
}
|
|
113
133
|
function navigate(target) {
|
|
134
|
+
if (forwardPaneIssueReference(target.ref, target.projectSlug)) return;
|
|
114
135
|
if (target.projectSlug !== store.get('currentSlug')) { window.location.assign('/p/' + encodeURIComponent(target.projectSlug) + '/?issue=' + encodeURIComponent(target.ref)); return; }
|
|
115
136
|
import('./issues.js').then(function (module) { module.openIssues(target.ref); });
|
|
116
137
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { enhanceClayIssueLinks } from './clay-issue-links.js';
|
|
1
|
+
import { enhanceClayIssueLinks, isExactClayIssueReference } from './clay-issue-links.js';
|
|
2
|
+
|
|
3
|
+
export function renderClayRecordLink(href, text) {
|
|
4
|
+
if (!isExactClayLogReference(href) && !isExactClayIssueReference(href)) return null;
|
|
5
|
+
return '<a href="#" data-clay-record-ref="' + href.trim() + '">' + text + '</a>';
|
|
6
|
+
}
|
|
2
7
|
export function parseClayLogReferences(text) {
|
|
3
8
|
var pattern = /\[clayos\/(log:[A-Za-z0-9_-]{24})(?:\s+[\u2014-]\s+([^\]\n]{1,80}))?\]|(^|[^A-Za-z0-9_/:])(log:[A-Za-z0-9_-]{24})(?![A-Za-z0-9_-])/g;
|
|
4
9
|
var results = [];
|
|
@@ -39,6 +44,11 @@ function createLogLink(ref, labelText) {
|
|
|
39
44
|
|
|
40
45
|
export function enhanceClayLogLinks(root) {
|
|
41
46
|
enhanceClayIssueLinks(root);
|
|
47
|
+
root.querySelectorAll('a').forEach(function (anchor) {
|
|
48
|
+
var ref = anchor.getAttribute('data-clay-record-ref') || anchor.getAttribute('href');
|
|
49
|
+
if (anchor.closest('pre, code, button') || !isExactClayLogReference(ref)) return;
|
|
50
|
+
anchor.replaceWith(createLogLink(ref.trim(), anchor.textContent.trim()));
|
|
51
|
+
});
|
|
42
52
|
var codeNodes = root.querySelectorAll("code");
|
|
43
53
|
for (var codeIndex = 0; codeIndex < codeNodes.length; codeIndex++) {
|
|
44
54
|
var code = codeNodes[codeIndex];
|
|
@@ -1,24 +1,50 @@
|
|
|
1
1
|
import { escapeHtml } from './utils.js';
|
|
2
2
|
import { renderMarkdown } from './markdown.js';
|
|
3
|
+
import { authorLine, relativeTime } from './project-logs-render.js';
|
|
3
4
|
export function statusLabel(status) { return { open: 'Open', in_progress: 'In progress', resolved: 'Resolved', closed: 'Closed' }[status] || 'Issue'; }
|
|
4
5
|
function esc(value) { return escapeHtml(String(value || '')); }
|
|
5
6
|
function options(values, selected) { return values.map(function (value) { return '<option value="' + value + '"' + (value === selected ? ' selected' : '') + '>' + esc(statusLabel(value) === 'Issue' ? value.replace(/_/g, ' ') : statusLabel(value)) + '</option>'; }).join(''); }
|
|
7
|
+
function label(value) { return value ? value.charAt(0).toUpperCase() + value.slice(1).replace(/_/g, ' ') : ''; }
|
|
8
|
+
function statusChip(status) { return '<span class="issue-status" data-status="' + esc(status) + '">' + statusLabel(status) + '</span>'; }
|
|
9
|
+
function priorityChip(priority) { return priority && priority !== 'normal' ? '<span class="project-log-priority" data-priority="' + esc(priority) + '">' + esc(label(priority)) + '</span>' : ''; }
|
|
10
|
+
function action(attribute, icon, text, extra) { return '<button type="button" ' + attribute + (extra || '') + '><i data-lucide="' + icon + '"></i>' + text + '</button>'; }
|
|
11
|
+
function prose(text, className) { return '<div class="markdown-body ' + (className || '') + '">' + renderMarkdown(text || '') + '</div>'; }
|
|
6
12
|
export function renderIssueList(result) {
|
|
7
13
|
var filtered = !!(result.query || result.status || result.type);
|
|
8
14
|
if (!result.issues.length) return '<div class="issues-empty"><i data-lucide="circle-dot"></i><h2>' + (filtered ? 'No matching issues' : 'No issues yet') + '</h2><p>' + (filtered ? 'Try a different search or filter.' : 'Manage issues through Driver chat.') + '</p></div>';
|
|
9
15
|
return result.issues.map(function (entry) {
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
var meta = [authorLine(entry), relativeTime(entry.updatedAt || entry.createdAt)];
|
|
17
|
+
if (entry.revision > 1) meta.push('v' + entry.revision);
|
|
18
|
+
if (entry.commentCount) meta.push(entry.commentCount + (entry.commentCount === 1 ? ' comment' : ' comments'));
|
|
19
|
+
return '<button type="button" class="issue-row" data-open-issue="' + esc(entry.ref) + '">' +
|
|
20
|
+
'<div class="issue-row-head"><span class="issue-type">' + esc(entry.type) + '</span>' + priorityChip(entry.priority) + '<strong>' + esc(entry.title) + '</strong></div>' +
|
|
21
|
+
(entry.summary ? '<p>' + esc(entry.summary) + '</p>' : '') +
|
|
22
|
+
'<div class="issue-row-meta">' + statusChip(entry.status) + '<span>' + esc(meta.filter(Boolean).join(' · ')) + '</span></div></button>';
|
|
23
|
+
}).join('') + (result.nextCursor ? '<div class="issues-load-more"><button data-issues-more="' + esc(result.nextCursor) + '">Load more</button></div>' : '');
|
|
24
|
+
}
|
|
25
|
+
function renderComment(comment) {
|
|
26
|
+
var actor = comment.author || {};
|
|
27
|
+
var review = comment.review;
|
|
28
|
+
return '<article class="issue-comment"><div class="issue-comment-meta">' + esc(actor.displayName || actor.userId || 'Project member') + ' · ' + esc(new Date(comment.at).toLocaleString()) + '</div>' +
|
|
29
|
+
prose(comment.body, 'issue-comment-body') + (review ? '<div class="issue-comment-review"><span>' + esc(label(review.action)) + '</span>' + prose(review.response) + '</div>' : '') + '</article>';
|
|
12
30
|
}
|
|
13
31
|
export function renderIssueDetail(entry) {
|
|
14
|
-
var
|
|
15
|
-
var
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
'<
|
|
20
|
-
|
|
21
|
-
(
|
|
32
|
+
var comments = (entry.comments || []).map(renderComment).join('');
|
|
33
|
+
var closed = entry.status === 'resolved' || entry.status === 'closed';
|
|
34
|
+
var time = entry.updatedAt || entry.createdAt;
|
|
35
|
+
var byline = [authorLine(entry), time ? new Date(time).toLocaleString() : '', 'v' + entry.revision].filter(Boolean).join(' · ');
|
|
36
|
+
return '<article class="issue-detail"><div class="issue-doc-chips"><span class="issue-type">' + esc(entry.type) + '</span>' + priorityChip(entry.priority) + statusChip(entry.status) + '</div>' +
|
|
37
|
+
'<h2 class="issue-doc-title">' + esc(entry.title) + '</h2>' + prose(entry.summary, 'issue-doc-summary') + '<div class="issue-byline">' + esc(byline) + '</div>' +
|
|
38
|
+
'<div class="issue-actions">' + action('data-issue-work', 'play', 'Start work', ' class="issue-primary"' + (closed ? ' disabled title="Reopen this issue to start work"' : '')) +
|
|
39
|
+
action('data-issue-edit', 'pencil', 'Edit') + action('data-issue-history', 'history', 'History') + action('data-issue-copy', 'link', 'Copy reference') +
|
|
40
|
+
(entry.canDelete ? action('data-issue-delete', 'trash-2', 'Delete', ' class="issue-danger"') : '') + '</div>' +
|
|
41
|
+
prose(entry.body, 'issue-doc-body') +
|
|
42
|
+
(entry.verifiedCommit ? '<section class="issue-evidence"><h3>Resolution</h3>' + prose(entry.resolutionSummary) + '<code>' + esc(entry.verifiedCommit.sha) + '</code><p>' + esc(entry.verifiedCommit.subject) + '</p></section>' : '') +
|
|
43
|
+
(entry.closeReason ? '<section class="issue-evidence"><h3>Closure</h3><p>' + esc(label(entry.closeReason)) + '</p></section>' : '') +
|
|
44
|
+
((entry.linkedWorkSessions || []).length ? '<section class="issue-evidence"><h3>Linked work sessions</h3>' + entry.linkedWorkSessions.map(function (link) { return action('data-issue-session="' + esc(link.sessionId) + '"', 'messages-square', 'Open Driver session'); }).join('') + '</section>' : '') +
|
|
45
|
+
((entry.resolutionHistory || []).length ? '<section class="issue-evidence"><h3>Resolution history</h3>' + entry.resolutionHistory.map(function (item) { return prose(item.resolutionSummary) + '<code>' + esc(item.verifiedCommit && item.verifiedCommit.sha) + '</code>'; }).join('') + '</section>' : '') +
|
|
46
|
+
'<section class="issue-discussion"><h3>Discussion' + (comments ? ' <span>' + entry.comments.length + '</span>' : '') + '</h3>' + (comments || '<p class="issue-discussion-empty">No comments yet.</p>') +
|
|
47
|
+
'<form id="issue-comment-form"><label for="issue-comment-input">Add a comment</label><textarea id="issue-comment-input" rows="3" required placeholder="Add context, a correction, or a question."></textarea><p class="issue-comment-status" role="status"></p><div class="issue-comment-actions"><button type="submit" class="issue-primary">Comment</button></div></form></section></article>';
|
|
22
48
|
}
|
|
23
49
|
export function renderIssueForm(entry) {
|
|
24
50
|
var item = entry || {};
|
|
@@ -3,16 +3,74 @@ import { getWs } from './ws-ref.js';
|
|
|
3
3
|
import { showToast } from './utils.js';
|
|
4
4
|
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
5
5
|
import { refreshIcons } from './icons.js';
|
|
6
|
-
import {
|
|
6
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
7
|
+
import { updateIssueLink } from './clay-issue-links.js';
|
|
8
|
+
import { renderIssueList, renderIssueDetail, renderIssueForm, renderIssueHistory } from './issues-render.js';
|
|
7
9
|
function panel() { return document.getElementById('issues-panel'); }
|
|
8
10
|
function content() { return panel().querySelector('.issues-content'); }
|
|
11
|
+
function clearUpdateNotice() {
|
|
12
|
+
store.set({ issuesUpdateAvailable: null });
|
|
13
|
+
var action = content() && content().querySelector('[data-issue-update-notice]');
|
|
14
|
+
if (action) action.remove();
|
|
15
|
+
}
|
|
16
|
+
function renderContextualUpdate() {
|
|
17
|
+
var update = store.get('issuesUpdateAvailable');
|
|
18
|
+
var issueContent = content();
|
|
19
|
+
var actions = issueContent && (issueContent.querySelector('.issue-detail .issue-actions') || issueContent.querySelector('#issue-form .issue-actions'));
|
|
20
|
+
if (!actions || !update) return;
|
|
21
|
+
var existing = actions.querySelector('[data-issue-update-notice]');
|
|
22
|
+
if (existing) existing.remove();
|
|
23
|
+
var notice = document.createElement('span');
|
|
24
|
+
notice.className = 'issues-inline-update';
|
|
25
|
+
notice.dataset.issueUpdateNotice = 'true';
|
|
26
|
+
notice.appendChild(document.createTextNode(store.get('issuesView') === 'edit' ? 'Updated remotely · Finish editing to refresh' : 'Updated remotely'));
|
|
27
|
+
var refresh = document.createElement('button');
|
|
28
|
+
refresh.type = 'button';
|
|
29
|
+
refresh.dataset.issueRefresh = update.ref;
|
|
30
|
+
refresh.textContent = 'Refresh';
|
|
31
|
+
refresh.disabled = store.get('issuesView') === 'edit';
|
|
32
|
+
if (refresh.disabled) refresh.title = 'Finish editing to refresh';
|
|
33
|
+
notice.appendChild(refresh);
|
|
34
|
+
actions.insertBefore(notice, actions.firstChild);
|
|
35
|
+
}
|
|
36
|
+
function captureDraft() {
|
|
37
|
+
var state = store.snap();
|
|
38
|
+
if (state.issuesView !== 'detail' && state.issuesView !== 'edit') return null;
|
|
39
|
+
var draft = { ref: entryRef(), view: state.issuesView, comment: null };
|
|
40
|
+
var comment = panel() && panel().querySelector('#issue-comment-input');
|
|
41
|
+
if (comment) draft.comment = comment.value;
|
|
42
|
+
return draft;
|
|
43
|
+
}
|
|
44
|
+
function restoreCommentDraft(draft) {
|
|
45
|
+
if (!draft || draft.comment === null) return;
|
|
46
|
+
var input = panel().querySelector('#issue-comment-input');
|
|
47
|
+
if (input) input.value = draft.comment;
|
|
48
|
+
}
|
|
49
|
+
function canRestoreCommentDraft(draft, ref) {
|
|
50
|
+
return !!draft && draft.ref === ref;
|
|
51
|
+
}
|
|
52
|
+
function hasNewerDraft(item) {
|
|
53
|
+
return (item.intent === 'detail' || item.intent === 'refresh') && item.viewGeneration !== (store.get('issuesViewGeneration') || 0);
|
|
54
|
+
}
|
|
55
|
+
function noteIssueUpdate(ref, revision) {
|
|
56
|
+
var current = store.get('issuesUpdateAvailable');
|
|
57
|
+
if (!current || current.ref !== ref || Number(revision || 0) >= Number(current.revision || 0)) {
|
|
58
|
+
store.set({ issuesUpdateAvailable: { ref: ref, revision: Number(revision || 0) } });
|
|
59
|
+
}
|
|
60
|
+
renderContextualUpdate();
|
|
61
|
+
}
|
|
62
|
+
function refreshSelectedIssue(ref) {
|
|
63
|
+
var selected = store.get('issuesSelected');
|
|
64
|
+
if (!selected || selected.ref !== ref || store.get('issuesView') === 'edit') return;
|
|
65
|
+
store.set({ issuesViewRequest: request('issue_read', { ref: ref }, 'refresh') });
|
|
66
|
+
}
|
|
9
67
|
function bumpViewGeneration() { store.set({ issuesViewGeneration: (store.get('issuesViewGeneration') || 0) + 1 }); }
|
|
10
68
|
function request(action, args, intent) {
|
|
11
69
|
var ws = getWs();
|
|
12
70
|
if (!ws || ws.readyState !== 1) { showToast('Reconnect to use Issues', 'warn'); return; }
|
|
13
71
|
var id = 'issues-' + crypto.randomUUID();
|
|
14
72
|
var pending = Object.assign({}, store.get('issuesRequests') || {});
|
|
15
|
-
pending[id] = { action: action, intent: intent, slug: store.get('currentSlug'), sourceRef: entryRef(), view: store.get('issuesView'), viewGeneration: store.get('issuesViewGeneration') || 0 };
|
|
73
|
+
pending[id] = { action: action, intent: intent, slug: store.get('currentSlug'), sourceRef: entryRef(), view: store.get('issuesView'), viewGeneration: store.get('issuesViewGeneration') || 0, draft: captureDraft() };
|
|
16
74
|
store.set({ issuesRequests: pending });
|
|
17
75
|
ws.send(JSON.stringify({ type: action, args: args || {}, requestId: id }));
|
|
18
76
|
return id;
|
|
@@ -39,7 +97,9 @@ function detail(entry) {
|
|
|
39
97
|
panel().querySelector('.issues-filters').hidden = true;
|
|
40
98
|
panel().querySelector('[data-issues-back]').classList.remove('hidden');
|
|
41
99
|
content().innerHTML = renderIssueDetail(entry);
|
|
100
|
+
enhanceClayLogLinks(content());
|
|
42
101
|
refreshIcons();
|
|
102
|
+
renderContextualUpdate();
|
|
43
103
|
}
|
|
44
104
|
function entryRef() {
|
|
45
105
|
var entry = store.get('issuesSelected');
|
|
@@ -87,6 +147,7 @@ export function closeIssues() {
|
|
|
87
147
|
export function openIssues(ref) {
|
|
88
148
|
if (!panel() || store.get('isMate')) return;
|
|
89
149
|
panel().querySelector('.issues-notice').textContent = '';
|
|
150
|
+
clearUpdateNotice();
|
|
90
151
|
bumpViewGeneration();
|
|
91
152
|
claimRightWorkbench("issues");
|
|
92
153
|
panel().classList.remove('hidden');
|
|
@@ -102,7 +163,7 @@ export function initIssues() {
|
|
|
102
163
|
registerRightWorkbench("issues", closeIssues);
|
|
103
164
|
var el = document.createElement('section');
|
|
104
165
|
el.id = 'issues-panel'; el.className = 'hidden'; el.setAttribute('aria-label', 'Project Issues');
|
|
105
|
-
el.innerHTML = '<header class="issues-topbar"><button class="issues-icon-btn" data-issues-back aria-label="Back to issues" title="Back to issue list"><i data-lucide="arrow-left"></i></button><span class="issues-title"><i data-lucide="circle-dot"></i>Issues</span><span class="issues-subtitle">Manage issues through Driver chat.</span><div class="issues-window-actions"><button class="issues-icon-btn" data-issues-wide aria-label="Widen Issues" title="Widen Issues" aria-pressed="false"><i data-lucide="chevrons-left-right"></i></button><button class="issues-icon-btn" data-issues-full aria-label="Toggle Issues fullscreen" title="Toggle fullscreen" aria-pressed="false"><i data-lucide="maximize-2"></i></button><button class="issues-icon-btn" data-issues-close aria-label="Close Issues" title="Close Issues"><i data-lucide="x"></i></button></div></header><div class="issues-filters"><label class="issues-search"><i data-lucide="search"></i><input name="query" type="search" placeholder="Search issues" aria-label="Search issues"></label><select name="filter-status" aria-label="Filter status"><option value="">All statuses</option><option value="open">Open</option><option value="in_progress">In progress</option><option value="resolved">Resolved</option><option value="closed">Closed</option></select><select name="filter-type" aria-label="Filter type"><option value="">All types</option><option>
|
|
166
|
+
el.innerHTML = '<header class="issues-topbar"><button class="issues-icon-btn" data-issues-back aria-label="Back to issues" title="Back to issue list"><i data-lucide="arrow-left"></i></button><span class="issues-title"><i data-lucide="circle-dot"></i>Issues</span><span class="issues-subtitle">Manage issues through Driver chat.</span><div class="issues-window-actions"><button class="issues-icon-btn" data-issues-wide aria-label="Widen Issues" title="Widen Issues" aria-pressed="false"><i data-lucide="chevrons-left-right"></i></button><button class="issues-icon-btn" data-issues-full aria-label="Toggle Issues fullscreen" title="Toggle fullscreen" aria-pressed="false"><i data-lucide="maximize-2"></i></button><button class="issues-icon-btn" data-issues-close aria-label="Close Issues" title="Close Issues"><i data-lucide="x"></i></button></div></header><div class="issues-filters"><label class="issues-search"><i data-lucide="search"></i><input name="query" type="search" placeholder="Search issues" aria-label="Search issues"></label><select name="filter-status" aria-label="Filter status"><option value="">All statuses</option><option value="open">Open</option><option value="in_progress">In progress</option><option value="resolved">Resolved</option><option value="closed">Closed</option></select><select name="filter-type" aria-label="Filter type"><option value="">All types</option><option value="bug">Bug</option><option value="feature">Feature</option><option value="plan">Plan</option></select></div><p class="issues-notice" role="status"></p><main class="issues-content" role="region" aria-live="polite"></main><div class="issues-delete-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="issues-delete-heading"><div class="issues-delete-card"><h2 id="issues-delete-heading">Delete this issue?</h2><p>This removes the issue from the board but preserves its history.</p><p class="issues-delete-title"></p><p class="issues-delete-status" role="status"></p><div><button type="button" data-issue-delete-cancel>Cancel</button><button type="button" data-issue-delete-confirm>Delete issue</button></div></div></div>';
|
|
106
167
|
host.appendChild(el);
|
|
107
168
|
var button = document.getElementById('issues-btn');
|
|
108
169
|
if (button) button.addEventListener('click', function () { if (store.get('issuesOpen')) closeIssues(); else openIssues(); });
|
|
@@ -118,6 +179,7 @@ export function initIssues() {
|
|
|
118
179
|
else if (target.hasAttribute('data-issue-delete-confirm')) confirmDelete();
|
|
119
180
|
else if (target.hasAttribute('data-issue-edit')) showIssueEdit(entry);
|
|
120
181
|
else if (target.hasAttribute('data-issue-cancel')) detail(entry);
|
|
182
|
+
else if (target.dataset.issueRefresh) refreshSelectedIssue(target.dataset.issueRefresh);
|
|
121
183
|
else if (target.dataset.openIssue) openIssues(target.dataset.openIssue);
|
|
122
184
|
else if (target.hasAttribute('data-issues-more')) listing(target.dataset.issuesMore);
|
|
123
185
|
else if (target.hasAttribute('data-issue-history')) { bumpViewGeneration(); store.set({ issuesView: 'history', issuesViewRequest: request('issue_history', { ref: entry.ref }, 'history') }); }
|
|
@@ -173,6 +235,7 @@ function showIssueEdit(entry) {
|
|
|
173
235
|
}
|
|
174
236
|
formEl.addEventListener('change', syncFields);
|
|
175
237
|
syncFields();
|
|
238
|
+
renderContextualUpdate();
|
|
176
239
|
}
|
|
177
240
|
export function handleIssuesMessage(msg) {
|
|
178
241
|
if (msg.type === 'issue_commented') {
|
|
@@ -202,8 +265,11 @@ export function handleIssuesMessage(msg) {
|
|
|
202
265
|
}
|
|
203
266
|
if (msg.type === 'issue_updated') {
|
|
204
267
|
var cache = Object.assign({}, store.get('issuesCache') || {}); cache[msg.ref] = Object.assign({}, cache[msg.ref] || {}, msg, { projectSlug: store.get('currentSlug') }); store.set({ issuesCache: cache });
|
|
205
|
-
document.querySelectorAll('[data-issue-ref]').forEach(function (node) { if (node.dataset.issueRef === msg.ref)
|
|
206
|
-
|
|
268
|
+
document.querySelectorAll('[data-issue-ref]').forEach(function (node) { if (node.dataset.issueRef === msg.ref) updateIssueLink(node, msg); });
|
|
269
|
+
var selected = store.get('issuesSelected');
|
|
270
|
+
var view = store.get('issuesView');
|
|
271
|
+
if (store.get('issuesOpen') && selected && selected.ref === msg.ref && (view === 'detail' || view === 'edit')) noteIssueUpdate(msg.ref, msg.revision);
|
|
272
|
+
else if (store.get('issuesOpen') && view === 'list') listing();
|
|
207
273
|
return true;
|
|
208
274
|
}
|
|
209
275
|
if (msg.type !== 'issues_result') return false;
|
|
@@ -239,6 +305,16 @@ export function handleIssuesMessage(msg) {
|
|
|
239
305
|
return true;
|
|
240
306
|
}
|
|
241
307
|
panel().querySelector('.issues-notice').textContent = '';
|
|
308
|
+
if (hasNewerDraft(item)) {
|
|
309
|
+
if (!msg.error && msg.result && msg.result.ref) {
|
|
310
|
+
var refreshedCache = Object.assign({}, store.get('issuesCache') || {});
|
|
311
|
+
refreshedCache[msg.result.ref] = Object.assign({}, msg.result, { canDelete: msg.canDelete === true, projectSlug: store.get('currentSlug') });
|
|
312
|
+
store.set({ issuesCache: refreshedCache });
|
|
313
|
+
if (Number(msg.result.revision || 0) > Number((store.get('issuesSelected') || {}).revision || 0)) noteIssueUpdate(msg.result.ref, msg.result.revision);
|
|
314
|
+
}
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
if (item.intent !== 'detail' && item.intent !== 'refresh') clearUpdateNotice();
|
|
242
318
|
if (item.intent === 'work') { closeIssues(); return true; }
|
|
243
319
|
if (item.intent === 'comment') {
|
|
244
320
|
var commented = Object.assign({}, msg.result, { canDelete: msg.canDelete === true });
|
|
@@ -262,7 +338,15 @@ export function handleIssuesMessage(msg) {
|
|
|
262
338
|
status: panel().querySelector('[name="filter-status"]').value, type: panel().querySelector('[name="filter-type"]').value });
|
|
263
339
|
store.set({ issuesRows: result.issues }); content().innerHTML = renderIssueList(result); refreshIcons();
|
|
264
340
|
} else if (item.intent === 'history') { content().innerHTML = renderIssueHistory(msg.result); refreshIcons(); }
|
|
265
|
-
else if (item.intent === 'revision') { content().innerHTML = '<p class="issues-history-note">Historical revision · use Back to return to current issues</p>' + renderIssueDetail(msg.result); content().querySelectorAll('.issue-actions, [data-issue-session]').forEach(function (node) { node.remove(); }); refreshIcons(); }
|
|
266
|
-
else
|
|
341
|
+
else if (item.intent === 'revision') { content().innerHTML = '<p class="issues-history-note">Historical revision · use Back to return to current issues</p>' + renderIssueDetail(msg.result); content().querySelectorAll('.issue-actions, [data-issue-session], .issue-discussion form').forEach(function (node) { node.remove(); }); enhanceClayLogLinks(content()); refreshIcons(); }
|
|
342
|
+
else if (item.intent === 'save') {
|
|
343
|
+
detail(Object.assign({}, msg.result, { canDelete: msg.canDelete === true }));
|
|
344
|
+
} else {
|
|
345
|
+
var available = store.get('issuesUpdateAvailable');
|
|
346
|
+
if (available && available.ref === msg.result.ref && Number(msg.result.revision || 0) >= Number(available.revision || 0)) clearUpdateNotice();
|
|
347
|
+
var refreshed = Object.assign({}, msg.result, { canDelete: msg.canDelete === true });
|
|
348
|
+
detail(refreshed);
|
|
349
|
+
if (canRestoreCommentDraft(item.draft, msg.result.ref) && (item.intent === 'detail' || item.intent === 'refresh')) restoreCommentDraft(item.draft);
|
|
350
|
+
}
|
|
267
351
|
return true;
|
|
268
352
|
}
|
|
@@ -2,7 +2,7 @@ import { copyToClipboard, escapeHtml } from './utils.js';
|
|
|
2
2
|
import { refreshIcons } from './icons.js';
|
|
3
3
|
import { getMermaidThemeVars } from './theme.js';
|
|
4
4
|
import { store } from './store.js';
|
|
5
|
-
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
5
|
+
import { enhanceClayLogLinks, renderClayRecordLink } from './clay-log-links.js';
|
|
6
6
|
import { renderLocalFileLink, enhanceClayFileLinks } from './clay-file-links.js';
|
|
7
7
|
|
|
8
8
|
// Initialize markdown parser
|
|
@@ -12,6 +12,8 @@ marked.use({ gfm: true, breaks: false });
|
|
|
12
12
|
marked.use({
|
|
13
13
|
renderer: {
|
|
14
14
|
link({ href, title, text }) {
|
|
15
|
+
var recordLink = renderClayRecordLink(href, text);
|
|
16
|
+
if (recordLink) return recordLink;
|
|
15
17
|
var localLink = renderLocalFileLink(href, title, text);
|
|
16
18
|
if (localLink) return localLink;
|
|
17
19
|
var isExternal = href && (href.startsWith('http://') || href.startsWith('https://'));
|
|
@@ -36,6 +36,12 @@ export function forwardPaneMarkdownPresentation(message) {
|
|
|
36
36
|
return true;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export function forwardPaneIssueReference(ref, projectSlug) {
|
|
40
|
+
if (!store.get('paneMode') || window.parent === window) return false;
|
|
41
|
+
window.parent.postMessage({ type: "clay-pane-open-issue", ref: ref, projectSlug: projectSlug || store.get('currentSlug') }, window.location.origin);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
39
45
|
// Toggle without setContextView: panes use a transient override and must not
|
|
40
46
|
// rewrite the user's server-persisted main-view preference.
|
|
41
47
|
function togglePaneContextPanel() {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { openIssues } from './issues.js';
|
|
3
|
+
|
|
4
|
+
function isIssueReference(ref) {
|
|
5
|
+
return typeof ref === "string" && /^issue:[A-Za-z0-9_-]{24}$/.test(ref);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isPaneSource(host, source) {
|
|
9
|
+
var frames = host.querySelectorAll(".split-pane-frame");
|
|
10
|
+
for (var i = 0; i < frames.length; i++) {
|
|
11
|
+
if (frames[i].contentWindow === source) return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function handlePaneIssueMessage(event, host) {
|
|
17
|
+
if (!event || event.origin !== window.location.origin || !host) return false;
|
|
18
|
+
var msg = event.data;
|
|
19
|
+
if (!msg || msg.type !== "clay-pane-open-issue") return false;
|
|
20
|
+
if (!isIssueReference(msg.ref) || !isPaneSource(host, event.source)) return true;
|
|
21
|
+
|
|
22
|
+
var issueTarget = store.get('issuesCache') && store.get('issuesCache')[msg.ref];
|
|
23
|
+
var projectSlug = (issueTarget && issueTarget.projectSlug) || msg.projectSlug || store.get('currentSlug');
|
|
24
|
+
if (projectSlug === store.get('currentSlug')) openIssues(msg.ref);
|
|
25
|
+
else window.location.assign('/p/' + encodeURIComponent(projectSlug) + '/?issue=' + encodeURIComponent(msg.ref));
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
@@ -43,7 +43,7 @@ function actorLabel(actor) {
|
|
|
43
43
|
// object (userId, sessionKey, vendor), but a session-authored entry must not
|
|
44
44
|
// read as if the human owner wrote it: the Project Driver did. The owner is
|
|
45
45
|
// only the account the session ran under.
|
|
46
|
-
function authorLine(entry) {
|
|
46
|
+
export function authorLine(entry) {
|
|
47
47
|
var actor = (entry && (entry.updatedBy || entry.createdBy)) || {};
|
|
48
48
|
if (actor.type === "system") return actor.displayName || "Scheduled Tasks";
|
|
49
49
|
if (actor.type === "session") {
|
|
@@ -63,7 +63,7 @@ function formatTime(value) {
|
|
|
63
63
|
catch (e) { return ""; }
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
function relativeTime(value) {
|
|
66
|
+
export function relativeTime(value) {
|
|
67
67
|
if (!value) return "";
|
|
68
68
|
var delta = Date.now() - value;
|
|
69
69
|
if (delta < 0) return formatTime(value);
|
|
@@ -14,6 +14,7 @@ import { presentMarkdownEdit } from './filebrowser.js';
|
|
|
14
14
|
import { autoStartLoginIfNeeded } from './vendor-login.js';
|
|
15
15
|
import { isCurrentProjectSessionReady } from './split-session-boundary.js';
|
|
16
16
|
import { splitPanesMatchProject, hasCurrentSplitGroup, reconcileRestoredSplit } from './project-activation.js';
|
|
17
|
+
import { handlePaneIssueMessage } from './pane-issue-bridge.js';
|
|
17
18
|
|
|
18
19
|
var host = null;
|
|
19
20
|
var nativeApp = null;
|
|
@@ -291,6 +292,7 @@ function handlePaneMessage(event) {
|
|
|
291
292
|
if (msg.message) autoStartLoginIfNeeded(msg.message);
|
|
292
293
|
return;
|
|
293
294
|
}
|
|
295
|
+
if (handlePaneIssueMessage(event, host)) return;
|
|
294
296
|
if (msg.type !== "clay-pane-context") return;
|
|
295
297
|
var frames = host.querySelectorAll(".split-pane-frame");
|
|
296
298
|
for (var i = 0; i < frames.length; i++) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { escapeHtml } from './utils.js';
|
|
2
2
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
3
|
import { store } from './store.js';
|
|
4
|
-
import { addToMessages, scrollToBottom } from './chat-render-runtime.js';
|
|
4
|
+
import { addToMessages, keepActiveThinkingAtTail, scrollToBottom } from './chat-render-runtime.js';
|
|
5
5
|
import { prepareThinkingView, bindThinkingView, updateThinkingView, beginThinkingView, setThinkingViewLive, finishThinkingView } from './thinking-view.js';
|
|
6
6
|
|
|
7
7
|
function getThinkingState() {
|
|
@@ -92,6 +92,7 @@ export function startThinkingSegment() {
|
|
|
92
92
|
segmentText: "",
|
|
93
93
|
startTime: Date.now(),
|
|
94
94
|
});
|
|
95
|
+
if (!store.get('replayingHistory')) keepActiveThinkingAtTail();
|
|
95
96
|
beginThinkingView(thinkingState.el, !store.get('replayingHistory') && !isMateThinking());
|
|
96
97
|
showMateActivity(thinkingState.el, true, !!thinkingState.detailsText.trim());
|
|
97
98
|
refreshIcons();
|
|
@@ -109,6 +110,7 @@ export function appendThinkingText(text) {
|
|
|
109
110
|
detailsText: thinkingState.detailsText + separator + text,
|
|
110
111
|
latestText: segmentText.trim() ? segmentText : thinkingState.latestText,
|
|
111
112
|
});
|
|
113
|
+
if (!store.get('replayingHistory')) keepActiveThinkingAtTail();
|
|
112
114
|
updateThinkingView(thinkingState.el, thinkingState.segmentText || thinkingState.latestText, thinkingState.detailsText);
|
|
113
115
|
maybeScrollToBottom();
|
|
114
116
|
}
|
|
@@ -156,6 +158,7 @@ export function restoreThinkingState(saved) {
|
|
|
156
158
|
export function resumeThinkingAfterReplay(processing) {
|
|
157
159
|
var thinkingState = getThinkingState();
|
|
158
160
|
if (!thinkingState || !thinkingState.active || !processing) return;
|
|
161
|
+
keepActiveThinkingAtTail();
|
|
159
162
|
setThinkingViewLive(thinkingState.el, !isMateThinking());
|
|
160
163
|
showMateActivity(thinkingState.el, true, !!thinkingState.detailsText.trim());
|
|
161
164
|
}
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -132,6 +132,7 @@ function createSDKBridge(opts) {
|
|
|
132
132
|
var clayPort = opts.clayPort || 2633;
|
|
133
133
|
var clayTls = opts.clayTls || false;
|
|
134
134
|
var clayAuthToken = opts.clayAuthToken || null;
|
|
135
|
+
var clayBridgeToken = opts.clayBridgeToken || null;
|
|
135
136
|
var getSessionToolDefs = opts.getSessionToolDefs || function () { return []; };
|
|
136
137
|
var canUseSessionTools = opts.canUseSessionTools || function () { return true; };
|
|
137
138
|
var queryToolSnapshots = new WeakMap();
|
|
@@ -155,6 +156,7 @@ function createSDKBridge(opts) {
|
|
|
155
156
|
if (clayTls) bridgeArgs.push("--tls");
|
|
156
157
|
var env = [];
|
|
157
158
|
if (clayAuthToken) env.push({ name: "CLAY_AUTH_TOKEN", value: clayAuthToken });
|
|
159
|
+
if (clayBridgeToken) env.push({ name: "CLAY_BRIDGE_TOKEN", value: clayBridgeToken });
|
|
158
160
|
return [{
|
|
159
161
|
name: "clay-tools",
|
|
160
162
|
command: process.execPath,
|
|
@@ -176,6 +178,7 @@ function createSDKBridge(opts) {
|
|
|
176
178
|
if (clayTls) bridgeArgs.push("--tls");
|
|
177
179
|
var bridgeEnv = {};
|
|
178
180
|
if (clayAuthToken) bridgeEnv.CLAY_AUTH_TOKEN = clayAuthToken;
|
|
181
|
+
if (clayBridgeToken) bridgeEnv.CLAY_BRIDGE_TOKEN = clayBridgeToken;
|
|
179
182
|
return { name: "clay-session-tools", command: process.execPath, args: bridgeArgs, env: bridgeEnv };
|
|
180
183
|
}
|
|
181
184
|
|
|
@@ -1384,6 +1387,7 @@ function createSDKBridge(opts) {
|
|
|
1384
1387
|
clayPort: clayPort,
|
|
1385
1388
|
clayTls: clayTls,
|
|
1386
1389
|
clayAuthToken: clayAuthToken,
|
|
1390
|
+
clayBridgeToken: clayBridgeToken,
|
|
1387
1391
|
slug: slug,
|
|
1388
1392
|
env: sessionRuntimeEnv,
|
|
1389
1393
|
});
|
|
@@ -1398,6 +1402,7 @@ function createSDKBridge(opts) {
|
|
|
1398
1402
|
clayPort: clayPort,
|
|
1399
1403
|
clayTls: clayTls,
|
|
1400
1404
|
clayAuthToken: clayAuthToken,
|
|
1405
|
+
clayBridgeToken: clayBridgeToken,
|
|
1401
1406
|
slug: slug,
|
|
1402
1407
|
env: sessionRuntimeEnv,
|
|
1403
1408
|
});
|
|
@@ -1795,6 +1800,11 @@ function createSDKBridge(opts) {
|
|
|
1795
1800
|
sendAndRecord(session, { type: "informational", level: "info", content: "Model changed from " + change.fromModel + " to " + change.toModel + " (" + change.source + ")." });
|
|
1796
1801
|
},
|
|
1797
1802
|
cwd: cwd,
|
|
1803
|
+
slug: slug,
|
|
1804
|
+
clayPort: clayPort,
|
|
1805
|
+
clayTls: clayTls,
|
|
1806
|
+
clayAuthToken: clayAuthToken,
|
|
1807
|
+
clayBridgeToken: clayBridgeToken,
|
|
1798
1808
|
linuxUser: linuxUser || undefined,
|
|
1799
1809
|
env: sessionRuntimeEnv,
|
|
1800
1810
|
skillOptions: getSessionSkillOptions(session),
|
|
@@ -2254,6 +2264,7 @@ function createSDKBridge(opts) {
|
|
|
2254
2264
|
clayTls: clayTls,
|
|
2255
2265
|
clayAuthToken: clayAuthToken,
|
|
2256
2266
|
slug: slug,
|
|
2267
|
+
clayBridgeToken: clayBridgeToken,
|
|
2257
2268
|
});
|
|
2258
2269
|
|
|
2259
2270
|
// Skill and slash-command catalogs are session-owned. The warmup has
|
package/lib/server.js
CHANGED
|
@@ -945,9 +945,9 @@ function createServer(opts) {
|
|
|
945
945
|
return;
|
|
946
946
|
}
|
|
947
947
|
|
|
948
|
-
// Auth check for project routes
|
|
949
|
-
//
|
|
950
|
-
//
|
|
948
|
+
// Auth check for project routes. The local MCP bridge gets a narrowly
|
|
949
|
+
// scoped project capability from its parent process; localhost alone is
|
|
950
|
+
// not sufficient because browser/local callers must not reach the bridge.
|
|
951
951
|
var projectUrlForAuth = stripPrefix(req.url.split("?")[0], slug);
|
|
952
952
|
// req.socket.remoteAddress may differ between HTTP/HTTPS (TLSSocket wraps net.Socket).
|
|
953
953
|
// Also check req.connection.remoteAddress for compatibility.
|
|
@@ -958,10 +958,18 @@ function createServer(opts) {
|
|
|
958
958
|
var isMcpBridgeLocal = projectUrlForAuth === "/api/mcp-bridge"
|
|
959
959
|
&& req.method === "POST"
|
|
960
960
|
&& isLocalhost;
|
|
961
|
+
var isMcpBridgeAuthenticated = isMcpBridgeLocal
|
|
962
|
+
&& typeof ctx.isMcpBridgeAuthenticated === "function"
|
|
963
|
+
&& ctx.isMcpBridgeAuthenticated(req);
|
|
961
964
|
if (projectUrlForAuth === "/api/mcp-bridge") {
|
|
962
|
-
console.log("[server] MCP bridge auth: method=" + req.method + " addr=" + remoteAddr + "
|
|
965
|
+
console.log("[server] MCP bridge auth: method=" + req.method + " addr=" + remoteAddr + " capability=" + isMcpBridgeAuthenticated);
|
|
963
966
|
}
|
|
964
|
-
if (
|
|
967
|
+
if (projectUrlForAuth === "/api/mcp-bridge" && !isMcpBridgeAuthenticated) {
|
|
968
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
969
|
+
res.end('{"error":"MCP bridge capability required"}');
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (!isMcpBridgeAuthenticated && !isRequestAuthed(req)) {
|
|
965
973
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
966
974
|
res.end(auth.getAuthPage());
|
|
967
975
|
return;
|
|
@@ -971,7 +979,7 @@ function createServer(opts) {
|
|
|
971
979
|
res.setHeader("Set-Cookie", "clay_last_project=" + slug + "; Path=/; SameSite=Strict; Max-Age=31536000" + (tlsOptions ? "; Secure" : ""));
|
|
972
980
|
|
|
973
981
|
// Multi-user: check project access for HTTP requests
|
|
974
|
-
if (users.isMultiUser()) {
|
|
982
|
+
if (users.isMultiUser() && !isMcpBridgeAuthenticated) {
|
|
975
983
|
var httpUser = getMultiUserFromReq(req);
|
|
976
984
|
if (!httpUser || !canUserAccessSlug(httpUser.id, slug)) {
|
|
977
985
|
res.writeHead(302, { "Location": "/" });
|
|
@@ -1325,6 +1333,7 @@ function createServer(opts) {
|
|
|
1325
1333
|
projectOwnerId: projectOwnerId || null,
|
|
1326
1334
|
projectKnowledgeId: extra.projectKnowledgeId || (worktreeMeta && worktreeMeta.projectKnowledgeId) || null,
|
|
1327
1335
|
worktreeMeta: worktreeMeta || null,
|
|
1336
|
+
mcpBridgeToken: extra.mcpBridgeToken || null,
|
|
1328
1337
|
isMate: extra.isMate || false,
|
|
1329
1338
|
mateId: extra.mateId || (extra.isMate ? path.basename(cwd) : null),
|
|
1330
1339
|
mateDisplayName: extra.mateDisplayName || "",
|
|
@@ -1808,6 +1808,7 @@ function createCodexAdapter(opts) {
|
|
|
1808
1808
|
var clayPort = effectiveInitOpts.clayPort || process.env.CLAY_PORT || 2633;
|
|
1809
1809
|
var clayTls = effectiveInitOpts.clayTls || false;
|
|
1810
1810
|
var clayAuthToken = effectiveInitOpts.clayAuthToken || "";
|
|
1811
|
+
var clayBridgeToken = effectiveInitOpts.clayBridgeToken || "";
|
|
1811
1812
|
var claySlug = effectiveInitOpts.slug || _slug || "";
|
|
1812
1813
|
try {
|
|
1813
1814
|
if (require("fs").existsSync(bridgePath)) {
|
|
@@ -1815,6 +1816,7 @@ function createCodexAdapter(opts) {
|
|
|
1815
1816
|
if (clayTls) bridgeArgs.push("--tls");
|
|
1816
1817
|
var bridgeEnv = {};
|
|
1817
1818
|
if (clayAuthToken) bridgeEnv.CLAY_AUTH_TOKEN = clayAuthToken;
|
|
1819
|
+
if (clayBridgeToken) bridgeEnv.CLAY_BRIDGE_TOKEN = clayBridgeToken;
|
|
1818
1820
|
mcpServerConfig["clay-tools"] = {
|
|
1819
1821
|
command: process.execPath,
|
|
1820
1822
|
args: bridgeArgs,
|
package/lib/yoke/index.js
CHANGED
|
@@ -379,7 +379,11 @@ function createAdapters(opts) {
|
|
|
379
379
|
|
|
380
380
|
if (installed.codex && supportsConfiguredIsolation("codex")) {
|
|
381
381
|
try {
|
|
382
|
-
adapters.codex = createAdapter({
|
|
382
|
+
adapters.codex = createAdapter({
|
|
383
|
+
vendor: "codex", cwd: opts.cwd, slug: opts.slug, osUsers: !!opts.osUsers,
|
|
384
|
+
clayPort: opts.clayPort, clayTls: opts.clayTls,
|
|
385
|
+
clayAuthToken: opts.clayAuthToken, clayBridgeToken: opts.clayBridgeToken,
|
|
386
|
+
});
|
|
383
387
|
auth.codex = true;
|
|
384
388
|
} catch (e) {
|
|
385
389
|
console.error("[yoke] Failed to create adapter for codex:", e.message);
|
|
@@ -58,6 +58,7 @@ var CLAY_MCP_PATH = claySlug
|
|
|
58
58
|
|
|
59
59
|
// --- Auth ---
|
|
60
60
|
var clayAuthToken = process.env.CLAY_AUTH_TOKEN || "";
|
|
61
|
+
var clayBridgeToken = process.env.CLAY_BRIDGE_TOKEN || "";
|
|
61
62
|
|
|
62
63
|
// --- Tool cache ---
|
|
63
64
|
var _tools = []; // [{ server, name, description, inputSchema }]
|
|
@@ -79,6 +80,7 @@ function postJson(urlPath, body) {
|
|
|
79
80
|
if (clayAuthToken) {
|
|
80
81
|
headers["Cookie"] = "relay_auth=" + clayAuthToken;
|
|
81
82
|
}
|
|
83
|
+
if (clayBridgeToken) headers["X-Clay-Bridge-Token"] = clayBridgeToken;
|
|
82
84
|
|
|
83
85
|
var reqOpts = {
|
|
84
86
|
hostname: parsed.hostname,
|
|
@@ -95,6 +97,15 @@ function postJson(urlPath, body) {
|
|
|
95
97
|
res.on("data", function (chunk) { chunks.push(chunk); });
|
|
96
98
|
res.on("end", function () {
|
|
97
99
|
var respBody = Buffer.concat(chunks).toString("utf8");
|
|
100
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
101
|
+
var statusError = "Clay MCP bridge HTTP " + res.statusCode;
|
|
102
|
+
try {
|
|
103
|
+
var errorBody = JSON.parse(respBody);
|
|
104
|
+
if (errorBody && errorBody.error) statusError += ": " + errorBody.error;
|
|
105
|
+
} catch (e) {}
|
|
106
|
+
reject(new Error(statusError));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
98
109
|
try {
|
|
99
110
|
resolve(JSON.parse(respBody));
|
|
100
111
|
} catch (e) {
|
|
@@ -122,8 +133,7 @@ function postJson(urlPath, body) {
|
|
|
122
133
|
function fetchTools() {
|
|
123
134
|
return postJson(CLAY_MCP_PATH, { action: "list_tools", sessionId: claySessionId, queryGeneration: clayQueryGeneration, sessionOnly: sessionOnly }).then(function (resp) {
|
|
124
135
|
if (resp.error) {
|
|
125
|
-
|
|
126
|
-
return [];
|
|
136
|
+
throw new Error(resp.error);
|
|
127
137
|
}
|
|
128
138
|
_tools = resp.tools || [];
|
|
129
139
|
_toolsFetched = true;
|
|
@@ -135,7 +145,8 @@ function fetchTools() {
|
|
|
135
145
|
return _tools;
|
|
136
146
|
}).catch(function (err) {
|
|
137
147
|
log("Error fetching tools: " + err.message);
|
|
138
|
-
|
|
148
|
+
_toolsFetched = false;
|
|
149
|
+
throw err;
|
|
139
150
|
});
|
|
140
151
|
}
|
|
141
152
|
|
|
@@ -194,7 +205,7 @@ function handleInitialize(id, params) {
|
|
|
194
205
|
});
|
|
195
206
|
|
|
196
207
|
// Pre-fetch tools after handshake
|
|
197
|
-
fetchTools();
|
|
208
|
+
fetchTools().catch(function () {});
|
|
198
209
|
}
|
|
199
210
|
|
|
200
211
|
function handleToolsList(id, params) {
|
|
@@ -210,7 +221,9 @@ function handleToolsList(id, params) {
|
|
|
210
221
|
}
|
|
211
222
|
|
|
212
223
|
// Always re-fetch: remote MCP servers may connect after initial fetch
|
|
213
|
-
fetchTools().then(respond)
|
|
224
|
+
fetchTools().then(respond).catch(function (err) {
|
|
225
|
+
sendError(id, -32001, "Clay tool discovery failed: " + err.message);
|
|
226
|
+
});
|
|
214
227
|
}
|
|
215
228
|
|
|
216
229
|
function handleToolsCall(id, params) {
|
package/package.json
CHANGED