clay-server 4.4.0 → 4.5.0-beta.1
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/LICENSES/README.md +2 -0
- package/lib/project-issues.js +11 -0
- package/lib/project-session-github.js +93 -0
- package/lib/project.js +32 -2
- package/lib/public/app.js +4 -0
- package/lib/public/css/base.css +3 -57
- package/lib/public/css/filebrowser.css +1 -1
- package/lib/public/css/fonts.css +14 -0
- package/lib/public/css/git-panel.css +2 -2
- package/lib/public/css/git-placard.css +1 -1
- package/lib/public/css/mobile-nav.css +13 -13
- package/lib/public/css/noto-sans-kr.css +126 -0
- package/lib/public/css/overlays.css +4 -4
- package/lib/public/css/profile.css +4 -4
- package/lib/public/css/server-settings.css +1 -1
- package/lib/public/css/session-github.css +112 -0
- package/lib/public/css/sidebar.css +2 -2
- package/lib/public/css/sticky-notes.css +3 -3
- package/lib/public/css/title-bar.css +26 -5
- package/lib/public/fonts/geist/Geist-Variable.woff2 +0 -0
- package/lib/public/fonts/geist/LICENSE.txt +92 -0
- package/lib/public/fonts/noto-sans-kr/LICENSE.txt +93 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-0-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-1-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-10-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-100-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-101-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-102-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-103-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-104-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-105-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-106-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-107-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-108-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-109-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-11-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-110-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-111-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-112-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-113-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-114-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-115-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-116-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-117-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-118-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-119-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-12-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-13-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-14-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-15-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-16-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-17-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-18-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-19-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-2-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-20-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-21-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-22-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-23-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-24-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-25-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-26-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-27-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-28-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-29-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-3-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-30-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-31-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-32-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-33-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-34-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-35-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-36-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-37-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-38-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-39-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-4-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-40-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-41-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-42-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-43-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-44-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-45-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-46-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-47-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-48-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-49-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-5-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-50-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-51-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-52-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-53-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-54-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-55-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-56-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-57-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-58-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-59-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-6-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-60-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-61-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-62-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-63-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-64-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-65-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-66-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-67-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-68-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-69-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-7-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-70-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-71-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-72-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-73-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-74-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-75-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-76-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-77-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-78-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-79-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-8-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-80-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-81-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-82-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-83-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-84-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-85-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-86-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-87-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-88-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-89-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-9-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-90-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-91-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-92-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-93-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-94-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-95-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-96-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-97-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-98-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-99-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-cyrillic-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-latin-ext-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-latin-wght-normal.woff2 +0 -0
- package/lib/public/fonts/noto-sans-kr/noto-sans-kr-vietnamese-wght-normal.woff2 +0 -0
- package/lib/public/modules/app-messages.js +2 -0
- package/lib/public/modules/git-panel.js +6 -2
- package/lib/public/modules/issues-render.js +2 -0
- package/lib/public/modules/markdown.js +3 -4
- package/lib/public/modules/session-github.js +153 -0
- package/lib/public/modules/sidebar-mobile.js +8 -1
- package/lib/public/modules/sidebar-sessions.js +2 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +11 -0
- package/lib/session-github.js +48 -0
- package/lib/sessions.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { githubLinkTarget } from './github-links.js';
|
|
4
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
5
|
+
import { escapeHtml as escapeText } from './utils.js';
|
|
6
|
+
|
|
7
|
+
function escapeHtml(value) {
|
|
8
|
+
return escapeText(String(value)).replace(/"/g, '"').replace(/'/g, ''');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function safeLinks(session) {
|
|
12
|
+
return (session && Array.isArray(session.githubLinks) ? session.githubLinks : []).filter(function (link) {
|
|
13
|
+
var target = githubLinkTarget(link.url);
|
|
14
|
+
return target && (target.kind === 'issue' || target.kind === 'pull');
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function label(link) {
|
|
18
|
+
return (link.kind === 'pr' ? 'PR #' : 'Issue #') + link.number;
|
|
19
|
+
}
|
|
20
|
+
function anchor(url, title, content, className) {
|
|
21
|
+
return '<a class="' + className + '" href="' + escapeHtml(url) + '" target="_blank" rel="noopener noreferrer" title="' + escapeHtml(title) + '" aria-label="' + escapeHtml(title) + '">' + content + '</a>';
|
|
22
|
+
}
|
|
23
|
+
export function orderedGithubWork(session) {
|
|
24
|
+
var links = safeLinks(session).slice().reverse();
|
|
25
|
+
var latestIssue = links.find(function (link) { return link.kind === 'issue'; });
|
|
26
|
+
if (latestIssue) links = [latestIssue].concat(links.filter(function (link) { return link !== latestIssue; }));
|
|
27
|
+
return links;
|
|
28
|
+
}
|
|
29
|
+
export function githubWorkMarkup(session, detailed, expanded) {
|
|
30
|
+
var links = orderedGithubWork(session);
|
|
31
|
+
if (!links.length) return '';
|
|
32
|
+
return '<span class="session-github-links' + (detailed ? ' github-work-details' : '') + '">' + (detailed || expanded ? links : links.slice(0, 1)).map(function (link) {
|
|
33
|
+
var state = ['open', 'closed', 'merged', 'draft'].indexOf(link.state) >= 0 ? link.state : 'unknown';
|
|
34
|
+
var icon = link.kind === 'pr' ? (state === 'merged' ? 'git-merge' : state === 'closed' ? 'git-pull-request-closed' : state === 'draft' ? 'git-pull-request-draft' : 'git-pull-request') : (state === 'closed' ? 'circle-check' : 'circle-dot');
|
|
35
|
+
var description = link.repository + ' · ' + label(link) + ' · ' + link.title + ' · ' + state + (link.checkedAt ? ' · Checked ' + new Date(link.checkedAt).toLocaleString() : '') + (link.stale ? ' (last known status; refresh unavailable)' : '');
|
|
36
|
+
var html = anchor(link.url, description, iconHtml('github', 'github-work-brand') + iconHtml(icon, 'github-work-status') + '<span><span class="github-work-kind">' + (link.kind === 'pr' ? 'PR ' : 'Issue ') + '</span>#' + escapeHtml(String(link.number)) + '</span>', 'session-github-link github-kind-' + (link.kind === 'pr' ? 'pr' : 'issue') + ' github-state-' + (link.stale ? 'unknown' : state));
|
|
37
|
+
if (expanded) html = '<span class="github-work-entry">' + html + '<span class="github-work-entry-title">' + escapeHtml(link.title || '') + '</span><span class="github-work-entry-meta">' + escapeHtml(link.repository + ' · ' + state + (link.stale ? ' · Last known status' : '')) + '</span></span>';
|
|
38
|
+
if (detailed && link.kind === 'pr') {
|
|
39
|
+
html += anchor(link.url + '/files', 'View PR changes on GitHub', 'Changes', 'github-work-action');
|
|
40
|
+
html += anchor(link.url + '#pullrequestreview', 'View PR reviews on GitHub', escapeHtml((link.review || 'Reviews').replace(/_/g, ' ').toLowerCase()), 'github-work-action');
|
|
41
|
+
(link.checks || []).forEach(function (check) {
|
|
42
|
+
if (/^https:\/\//i.test(check.url)) html += anchor(check.url, check.name + ': ' + check.state, escapeHtml(check.name + ' · ' + check.state.toLowerCase()), 'github-work-action');
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return html;
|
|
46
|
+
}).join('') + (!detailed && !expanded && links.length > 1 ? '<button type="button" class="github-work-more" aria-label="Show all ' + links.length + ' linked GitHub items" aria-haspopup="dialog" aria-expanded="false" data-github-links="' + escapeHtml(JSON.stringify(safeLinks(session))) + '">+' + (links.length - 1) + '</button>' : '') + '</span>';
|
|
47
|
+
}
|
|
48
|
+
export function appendGithubWork(row, session, title) {
|
|
49
|
+
var html = githubWorkMarkup(session, false);
|
|
50
|
+
bindGithubWorkPopover();
|
|
51
|
+
if (!html) return;
|
|
52
|
+
var column = document.createElement('span');
|
|
53
|
+
column.className = 'session-work-column';
|
|
54
|
+
row.insertBefore(column, title);
|
|
55
|
+
column.appendChild(title);
|
|
56
|
+
var links = document.createElement('span');
|
|
57
|
+
links.innerHTML = html;
|
|
58
|
+
links.addEventListener('click', function (event) { if (event.target.closest('a')) event.stopPropagation(); });
|
|
59
|
+
links.addEventListener('contextmenu', function (event) { event.stopPropagation(); });
|
|
60
|
+
column.appendChild(links);
|
|
61
|
+
}
|
|
62
|
+
export function syncGithubSessions(sessions) {
|
|
63
|
+
bindGithubWorkPopover();
|
|
64
|
+
store.set({ githubWorkSessions: sessions || [], githubWorkProject: store.get('currentSlug') });
|
|
65
|
+
var active = (sessions || []).find(function (session) { return session.active; });
|
|
66
|
+
var title = document.getElementById('header-title');
|
|
67
|
+
var old = document.getElementById('header-github-links');
|
|
68
|
+
var html = title && !store.get('dmMode') && !store.get('splitPanes') && active ? githubWorkMarkup(active, false) : '';
|
|
69
|
+
if (!html) { if (old) old.remove(); }
|
|
70
|
+
else if (!old || old.dataset.markup !== html) {
|
|
71
|
+
var element = old || document.createElement('span');
|
|
72
|
+
element.id = 'header-github-links';
|
|
73
|
+
element.dataset.markup = html;
|
|
74
|
+
element.innerHTML = html;
|
|
75
|
+
if (!old) title.insertAdjacentElement('afterend', element);
|
|
76
|
+
}
|
|
77
|
+
refreshIcons();
|
|
78
|
+
refreshGithubWork();
|
|
79
|
+
}
|
|
80
|
+
export function currentGithubWorkMarkup() {
|
|
81
|
+
if (store.get('githubWorkProject') !== store.get('currentSlug')) return '';
|
|
82
|
+
var session = (store.get('githubWorkSessions') || []).find(function (item) { return item.active; });
|
|
83
|
+
return githubWorkMarkup(session, true);
|
|
84
|
+
}
|
|
85
|
+
export function refreshGithubWork() {
|
|
86
|
+
if (document.hidden || store.get('dmMode') || !store.get('connected')) return;
|
|
87
|
+
if (store.get('githubWorkProject') !== store.get('currentSlug')) return;
|
|
88
|
+
var session = (store.get('githubWorkSessions') || []).find(function (item) { return item.active; });
|
|
89
|
+
if (!safeLinks(session).length) return;
|
|
90
|
+
var key = store.get('currentSlug') + ':' + session.id;
|
|
91
|
+
var previous = store.get('githubWorkRefresh') || {};
|
|
92
|
+
if (previous.key === key && Date.now() - previous.at < 60000) return;
|
|
93
|
+
var ws = getWs();
|
|
94
|
+
if (!ws || ws.readyState !== 1) return;
|
|
95
|
+
store.set({ githubWorkRefresh: { key: key, at: Date.now() } });
|
|
96
|
+
ws.send(JSON.stringify({ type: 'session_github_refresh' }));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function resetGithubWork() {
|
|
100
|
+
closeGithubWorkPopover();
|
|
101
|
+
store.set({ githubWorkSessions: [], githubWorkProject: null, githubWorkRefresh: null });
|
|
102
|
+
var header = document.getElementById('header-github-links');
|
|
103
|
+
if (header) header.remove();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function closeGithubWorkPopover() {
|
|
107
|
+
var popup = document.getElementById('github-work-popover');
|
|
108
|
+
if (popup) popup.hidePopover();
|
|
109
|
+
}
|
|
110
|
+
function bindGithubWorkPopover() {
|
|
111
|
+
if (store.get('githubWorkPopoverBound')) return;
|
|
112
|
+
store.set({ githubWorkPopoverBound: true });
|
|
113
|
+
document.addEventListener('click', function (event) {
|
|
114
|
+
var button = event.target.closest('.github-work-more');
|
|
115
|
+
if (!button) return;
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
event.stopPropagation();
|
|
118
|
+
var existing = document.getElementById('github-work-popover');
|
|
119
|
+
var same = existing && button.getAttribute('aria-expanded') === 'true';
|
|
120
|
+
if (existing) { existing.hidePopover(); existing.remove(); }
|
|
121
|
+
if (same) return;
|
|
122
|
+
var links;
|
|
123
|
+
try { links = JSON.parse(button.dataset.githubLinks); } catch (error) { return; }
|
|
124
|
+
var popup = document.createElement('div');
|
|
125
|
+
popup.id = 'github-work-popover';
|
|
126
|
+
popup.className = 'github-work-popover';
|
|
127
|
+
popup.setAttribute('popover', 'auto');
|
|
128
|
+
popup.setAttribute('role', 'dialog');
|
|
129
|
+
popup.setAttribute('aria-label', 'Linked GitHub work');
|
|
130
|
+
popup.innerHTML = '<strong>Linked GitHub work</strong>' + githubWorkMarkup({ githubLinks: links }, false, true);
|
|
131
|
+
document.body.appendChild(popup);
|
|
132
|
+
button.setAttribute('aria-expanded', 'true');
|
|
133
|
+
popup.addEventListener('toggle', function (toggle) {
|
|
134
|
+
if (toggle.newState === 'closed') {
|
|
135
|
+
button.setAttribute('aria-expanded', 'false');
|
|
136
|
+
if (document.activeElement === document.body && button.isConnected) button.focus();
|
|
137
|
+
popup.remove();
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
popup.showPopover();
|
|
141
|
+
var rect = button.getBoundingClientRect();
|
|
142
|
+
var box = popup.getBoundingClientRect();
|
|
143
|
+
popup.style.left = Math.max(8, Math.min(rect.left, window.innerWidth - box.width - 8)) + 'px';
|
|
144
|
+
popup.style.top = Math.max(8, rect.bottom + box.height + 8 <= window.innerHeight ? rect.bottom + 6 : rect.top - box.height - 6) + 'px';
|
|
145
|
+
var first = popup.querySelector('a');
|
|
146
|
+
if (first) first.focus({ preventScroll: true });
|
|
147
|
+
refreshIcons();
|
|
148
|
+
}, true);
|
|
149
|
+
window.addEventListener('resize', closeGithubWorkPopover);
|
|
150
|
+
store.subscribe(function (state, previous) {
|
|
151
|
+
if (state.currentSlug !== previous.currentSlug || state.activeSessionId !== previous.activeSessionId || state.dmMode !== previous.dmMode) closeGithubWorkPopover();
|
|
152
|
+
});
|
|
153
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appendGithubWork } from './session-github.js';
|
|
1
2
|
// sidebar-mobile.js - Mobile sheet overlays, tab bar, and mobile-specific rendering
|
|
2
3
|
// Extracted from sidebar.js (PR-38)
|
|
3
4
|
|
|
@@ -384,7 +385,7 @@ function renderSheetSessions(listEl) {
|
|
|
384
385
|
// Helper: create a mobile session item element
|
|
385
386
|
function createMobileSessionItem(s, options) {
|
|
386
387
|
var itemOptions = options || {};
|
|
387
|
-
var el = document.createElement("
|
|
388
|
+
var el = document.createElement("div");
|
|
388
389
|
el.className = "mobile-session-item" + (s.active ? " active" : "");
|
|
389
390
|
if (itemOptions.worker) el.classList.add("mobile-worker-item");
|
|
390
391
|
|
|
@@ -404,9 +405,15 @@ function createMobileSessionItem(s, options) {
|
|
|
404
405
|
}
|
|
405
406
|
|
|
406
407
|
var titleSpan = document.createElement("span");
|
|
408
|
+
titleSpan.tabIndex = 0;
|
|
409
|
+
titleSpan.setAttribute("role", "button");
|
|
410
|
+
titleSpan.addEventListener("keydown", function (event) {
|
|
411
|
+
if (event.key === "Enter" || event.key === " ") { event.preventDefault(); el.click(); }
|
|
412
|
+
});
|
|
407
413
|
titleSpan.className = "mobile-session-title";
|
|
408
414
|
titleSpan.appendChild(document.createTextNode(s.title || "New Session"));
|
|
409
415
|
el.appendChild(titleSpan);
|
|
416
|
+
appendGithubWork(el, s, titleSpan);
|
|
410
417
|
|
|
411
418
|
if (itemOptions.worker) {
|
|
412
419
|
var generation = document.createElement("span");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appendGithubWork } from './session-github.js';
|
|
1
2
|
// sidebar-sessions.js - Session list, search, presence, countdown, CLI picker
|
|
2
3
|
// Extracted from sidebar.js (PR-35)
|
|
3
4
|
|
|
@@ -1367,6 +1368,7 @@ function renderSessionItem(s, options) {
|
|
|
1367
1368
|
textHtml += highlightMatch(s.title || "New Session", searchQuery);
|
|
1368
1369
|
textSpan.innerHTML = textHtml;
|
|
1369
1370
|
el.appendChild(textSpan);
|
|
1371
|
+
appendGithubWork(el, s, textSpan);
|
|
1370
1372
|
|
|
1371
1373
|
if (itemOptions.worker) {
|
|
1372
1374
|
var workerLabel = document.createElement("span");
|
package/lib/public/style.css
CHANGED
package/lib/sdk-bridge.js
CHANGED
|
@@ -567,6 +567,17 @@ function createSDKBridge(opts) {
|
|
|
567
567
|
return { behavior: "allow", updatedInput: input };
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
+
// Session search is read-only; its handler enforces project and session access.
|
|
571
|
+
if (toolName === "search_other_driver_sessions"
|
|
572
|
+
|| toolName === "mcp__clay-handoff__search_other_driver_sessions") {
|
|
573
|
+
return { behavior: "allow", updatedInput: input };
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Local work associations verify GitHub data but never mutate GitHub.
|
|
577
|
+
if (["link_session_github", "get_session_github", "unlink_session_github"].some(function (name) {
|
|
578
|
+
return toolName === name || toolName === "mcp__clay-github__" + name;
|
|
579
|
+
})) return { behavior: "allow", updatedInput: input };
|
|
580
|
+
|
|
570
581
|
// Auto-approve safe browser MCP tools.
|
|
571
582
|
// Only watch/unwatch: user explicitly chose which tab to share.
|
|
572
583
|
// Everything else (screenshot, read_page, list_tabs, etc.) can expose
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Verified GitHub references owned by a work session, never inferred from mentions.
|
|
2
|
+
var execFile = require("child_process").execFile;
|
|
3
|
+
var wrapSpawnAsUser = require("./os-users").wrapSpawnAsUser;
|
|
4
|
+
|
|
5
|
+
function parseReference(value) {
|
|
6
|
+
var match = typeof value === "string" && value.match(/^https:\/\/github\.com\/([a-zA-Z0-9][a-zA-Z0-9-]{0,38})\/([a-zA-Z0-9][a-zA-Z0-9._-]{0,99})\/(issues|pull)\/([1-9][0-9]*)(?:[?#].*)?$/);
|
|
7
|
+
if (!match || !Number.isSafeInteger(Number(match[4]))) throw new Error("Use a GitHub issue or pull request URL.");
|
|
8
|
+
return { repository: match[1] + "/" + match[2], kind: match[3] === "pull" ? "pr" : "issue", number: Number(match[4]), url: "https://github.com/" + match[1] + "/" + match[2] + "/" + match[3] + "/" + match[4] };
|
|
9
|
+
}
|
|
10
|
+
function runGh(cwd, args, identity) {
|
|
11
|
+
return new Promise(function (resolve, reject) {
|
|
12
|
+
var env = Object.assign({}, identity ? require("./build-user-env").buildUserEnv(identity) : process.env, { GH_PROMPT_DISABLED: "1", GH_HOST: "github.com" });
|
|
13
|
+
if (identity) {
|
|
14
|
+
// Never let the daemon's GitHub credentials cross an OS-user boundary.
|
|
15
|
+
delete env.GH_TOKEN; delete env.GITHUB_TOKEN; delete env.GH_ENTERPRISE_TOKEN; delete env.GITHUB_ENTERPRISE_TOKEN;
|
|
16
|
+
delete env.GH_CONFIG_DIR; delete env.XDG_CONFIG_HOME;
|
|
17
|
+
env.HOME = identity.home; env.USER = identity.user; env.LOGNAME = identity.user;
|
|
18
|
+
}
|
|
19
|
+
var options = { cwd: cwd, env: env, timeout: 15000, maxBuffer: 512 * 1024, encoding: "utf8" };
|
|
20
|
+
if (identity) { options.uid = identity.uid; options.gid = identity.gid; }
|
|
21
|
+
var wrapped = wrapSpawnAsUser("gh", args, options);
|
|
22
|
+
execFile(wrapped.command, wrapped.args, wrapped.options, function (error, stdout) {
|
|
23
|
+
if (error) return reject(new Error("GitHub is unavailable. Check gh authentication and repository access."));
|
|
24
|
+
try { resolve(JSON.parse(stdout)); } catch (e) { reject(new Error("GitHub returned an invalid response.")); }
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async function readReference(cwd, url, identity, run) {
|
|
29
|
+
var ref = parseReference(url);
|
|
30
|
+
var fields = "title,state,url" + (ref.kind === "pr" ? ",isDraft,headRefName,reviewDecision,statusCheckRollup" : "");
|
|
31
|
+
var data = await (run || runGh)(cwd, [ref.kind === "pr" ? "pr" : "issue", "view", String(ref.number), "--repo", ref.repository, "--json", fields], identity);
|
|
32
|
+
var verified = parseReference(data.url);
|
|
33
|
+
if (verified.url.toLowerCase() !== ref.url.toLowerCase()) throw new Error("GitHub returned a different reference.");
|
|
34
|
+
ref.title = String(data.title || "").slice(0, 200);
|
|
35
|
+
ref.state = data.state === "MERGED" ? "merged" : data.state === "CLOSED" ? "closed" : data.state === "OPEN" ? (data.isDraft ? "draft" : "open") : "unknown";
|
|
36
|
+
ref.checkedAt = Date.now();
|
|
37
|
+
if (ref.kind === "pr") {
|
|
38
|
+
ref.branch = String(data.headRefName || "").slice(0, 200);
|
|
39
|
+
ref.review = ["APPROVED", "CHANGES_REQUESTED", "REVIEW_REQUIRED"].indexOf(data.reviewDecision) >= 0 ? data.reviewDecision : "";
|
|
40
|
+
ref.checks = (data.statusCheckRollup || []).slice(0, 30).map(function (check) {
|
|
41
|
+
var target = check.detailsUrl || check.targetUrl || "";
|
|
42
|
+
if (!/^https:\/\//i.test(target)) target = "";
|
|
43
|
+
return { name: String(check.name || check.context || "Check").slice(0, 100), state: String(check.conclusion || check.state || check.status || "PENDING").slice(0, 40), url: target };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return ref;
|
|
47
|
+
}
|
|
48
|
+
module.exports = { parseReference: parseReference, readReference: readReference };
|
package/lib/sessions.js
CHANGED
|
@@ -176,6 +176,7 @@ function createSessionManager(opts) {
|
|
|
176
176
|
titleManuallySet: session.titleManuallySet === true,
|
|
177
177
|
createdAt: session.createdAt,
|
|
178
178
|
};
|
|
179
|
+
if (session.githubLinks) metaObj.githubLinks = session.githubLinks;
|
|
179
180
|
if (session.ownerId) metaObj.ownerId = session.ownerId;
|
|
180
181
|
if (session.vendor) metaObj.vendor = session.vendor;
|
|
181
182
|
if (session.codexDynamicToolCatalogVersion) metaObj.codexDynamicToolCatalogVersion = session.codexDynamicToolCatalogVersion;
|
|
@@ -343,6 +344,7 @@ function createSessionManager(opts) {
|
|
|
343
344
|
title: m.title || "",
|
|
344
345
|
titleProvisional: m.titleProvisional === true,
|
|
345
346
|
titleAutoGenerated: m.titleAutoGenerated === true,
|
|
347
|
+
githubLinks: Array.isArray(m.githubLinks) ? m.githubLinks.slice(0, 8) : [],
|
|
346
348
|
titleManuallySet: m.titleManuallySet === true || legacyTitleProtected,
|
|
347
349
|
createdAt: m.createdAt || Date.now(),
|
|
348
350
|
lastActivity: loaded[i].mtime || m.createdAt || Date.now(),
|
|
@@ -604,6 +606,7 @@ function createSessionManager(opts) {
|
|
|
604
606
|
id: s.localId,
|
|
605
607
|
cliSessionId: s.cliSessionId || null,
|
|
606
608
|
title: s.title || "New Session",
|
|
609
|
+
githubLinks: s.githubLinks || [],
|
|
607
610
|
active: isActive,
|
|
608
611
|
isProcessing: s.isProcessing,
|
|
609
612
|
// Sidebar badge: user work only. Ambient housekeeping is surfaced in the
|
package/package.json
CHANGED