clay-server 4.8.0-beta.2 → 4.8.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/linear-api.js +62 -0
- package/lib/linear-store.js +52 -0
- package/lib/project-linear-launch.js +70 -0
- package/lib/project-session-linear.js +197 -0
- package/lib/project.js +40 -2
- package/lib/public/css/linear.css +35 -0
- package/lib/public/css/right-workbench.css +3 -3
- package/lib/public/css/settings-ui.css +295 -0
- package/lib/public/index.html +20 -0
- package/lib/public/modules/app-messages.js +4 -0
- package/lib/public/modules/app-projects.js +1 -1
- package/lib/public/modules/issues.js +4 -1
- package/lib/public/modules/linear-panel.js +120 -0
- package/lib/public/modules/linear-settings.js +61 -0
- package/lib/public/modules/project-settings.js +19 -1
- package/lib/public/modules/session-github.js +15 -5
- package/lib/public/modules/sticky-notes.js +23 -6
- package/lib/public/modules/user-settings.js +4 -0
- package/lib/public/style.css +2 -0
- package/lib/sdk-bridge.js +9 -1
- package/lib/server-linear.js +50 -0
- package/lib/server.js +5 -0
- package/lib/sessions.js +5 -0
- package/package.json +1 -1
|
@@ -9,7 +9,8 @@ function escapeHtml(value) {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function safeLinks(session) {
|
|
12
|
-
return (session && Array.isArray(session.githubLinks) ? session.githubLinks : []).filter(function (link) {
|
|
12
|
+
return (session && Array.isArray(session.githubLinks) ? session.githubLinks : []).concat(session && Array.isArray(session.linearLinks) ? session.linearLinks : []).filter(function (link) {
|
|
13
|
+
if (link.provider === 'linear') return /^https:\/\/linear\.app\/[a-zA-Z0-9_-]+\/issue\/[A-Z][A-Z0-9_]*-[1-9][0-9]*$/.test(link.url);
|
|
13
14
|
var target = githubLinkTarget(link.url);
|
|
14
15
|
return target && (target.kind === 'issue' || target.kind === 'pull');
|
|
15
16
|
});
|
|
@@ -27,7 +28,7 @@ export function orderedGithubWork(session) {
|
|
|
27
28
|
return links;
|
|
28
29
|
}
|
|
29
30
|
function compactGithubWork(links, sessionRow) {
|
|
30
|
-
if (!sessionRow) return links.slice(0, 1);
|
|
31
|
+
if (!sessionRow && !links.some(function (link) { return link.provider === 'linear'; })) return links.slice(0, 1);
|
|
31
32
|
var latestIssue = links.find(function (link) { return link.kind === 'issue'; });
|
|
32
33
|
var latestPr = links.find(function (link) { return link.kind === 'pr'; });
|
|
33
34
|
return [latestIssue, latestPr].filter(Boolean);
|
|
@@ -37,6 +38,10 @@ export function githubWorkMarkup(session, detailed, expanded, sessionRow) {
|
|
|
37
38
|
if (!links.length) return '';
|
|
38
39
|
var displayed = detailed || expanded ? links : compactGithubWork(links, sessionRow);
|
|
39
40
|
return '<span class="session-github-links' + (detailed ? ' github-work-details' : '') + (sessionRow ? ' session-row-github-links' : '') + '">' + displayed.map(function (link) {
|
|
41
|
+
if (link.provider === 'linear') {
|
|
42
|
+
var description = link.identifier + ' · ' + link.title + ' · ' + link.stateName + (link.stale ? ' · Last known status' : '');
|
|
43
|
+
return anchor(link.url, description, iconHtml('circle-dot', 'github-work-status') + '<span class="linear-work-id">' + escapeHtml(link.identifier) + '</span><span class="linear-work-title">' + escapeHtml(link.title) + '</span><span class="linear-work-state">' + escapeHtml(link.stateName) + '</span>', 'session-github-link session-linear-link');
|
|
44
|
+
}
|
|
40
45
|
var state = ['open', 'closed', 'merged', 'draft'].indexOf(link.state) >= 0 ? link.state : 'unknown';
|
|
41
46
|
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');
|
|
42
47
|
var description = link.repository + ' · ' + label(link) + ' · ' + link.title + ' · ' + state + (link.checkedAt ? ' · Checked ' + new Date(link.checkedAt).toLocaleString() : '') + (link.stale ? ' (last known status; refresh unavailable)' : '');
|
|
@@ -50,7 +55,7 @@ export function githubWorkMarkup(session, detailed, expanded, sessionRow) {
|
|
|
50
55
|
});
|
|
51
56
|
}
|
|
52
57
|
return html;
|
|
53
|
-
}).join('') + (!detailed && !expanded && links.length > displayed.length ? '<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 - displayed.length) + '</button>' : '') + '</span>';
|
|
58
|
+
}).join('') + (!detailed && !expanded && links.length > displayed.length ? '<button type="button" class="github-work-more" aria-label="Show all ' + links.length + ' linked ' + (links.some(function (item) { return item.provider === 'linear'; }) ? 'work items' : 'GitHub items') + '" aria-haspopup="dialog" aria-expanded="false" data-github-links="' + escapeHtml(JSON.stringify(safeLinks(session))) + '">+' + (links.length - displayed.length) + '</button>' : '') + '</span>';
|
|
54
59
|
}
|
|
55
60
|
export function appendGithubWork(row, session, title) {
|
|
56
61
|
var html = githubWorkMarkup(session, false, false, true);
|
|
@@ -109,6 +114,8 @@ export function refreshGithubWork() {
|
|
|
109
114
|
if (!ws || ws.readyState !== 1) return;
|
|
110
115
|
store.set({ githubWorkRefresh: { key: key, at: Date.now() } });
|
|
111
116
|
ws.send(JSON.stringify({ type: 'session_github_refresh', sessionIds: ids }));
|
|
117
|
+
var linearIds = ids.filter(function (id) { return (store.get('githubWorkSessions') || []).some(function (item) { return item.id === id && (item.linearLinks || []).length; }); });
|
|
118
|
+
if (linearIds.length) ws.send(JSON.stringify({ type: 'session_linear_refresh', sessionIds: linearIds }));
|
|
112
119
|
}
|
|
113
120
|
|
|
114
121
|
export function resetGithubWork() {
|
|
@@ -126,6 +133,8 @@ function bindGithubWorkPopover() {
|
|
|
126
133
|
if (store.get('githubWorkPopoverBound')) return;
|
|
127
134
|
store.set({ githubWorkPopoverBound: true });
|
|
128
135
|
document.addEventListener('click', function (event) {
|
|
136
|
+
var linear = event.target.closest('.session-linear-link');
|
|
137
|
+
if (linear && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) { event.preventDefault(); event.stopPropagation(); closeGithubWorkPopover(); import('./linear-panel.js').then(function (module) { module.openLinear(linear.href); }); return; }
|
|
129
138
|
var button = event.target.closest('.github-work-more');
|
|
130
139
|
if (!button) return;
|
|
131
140
|
event.preventDefault();
|
|
@@ -141,8 +150,9 @@ function bindGithubWorkPopover() {
|
|
|
141
150
|
popup.className = 'github-work-popover';
|
|
142
151
|
popup.setAttribute('popover', 'auto');
|
|
143
152
|
popup.setAttribute('role', 'dialog');
|
|
144
|
-
|
|
145
|
-
popup.
|
|
153
|
+
var workTitle = links.some(function (link) { return link.provider === 'linear'; }) ? 'Linked work' : 'Linked GitHub work';
|
|
154
|
+
popup.setAttribute('aria-label', workTitle);
|
|
155
|
+
popup.innerHTML = '<strong>' + workTitle + '</strong>' + githubWorkMarkup({ githubLinks: links }, false, true);
|
|
146
156
|
document.body.appendChild(popup);
|
|
147
157
|
button.setAttribute('aria-expanded', 'true');
|
|
148
158
|
popup.addEventListener('toggle', function (toggle) {
|
|
@@ -195,16 +195,33 @@ export function initStickyNotes() {
|
|
|
195
195
|
export function showNotes(restoreTemporary) {
|
|
196
196
|
notesVisible = true;
|
|
197
197
|
if (restoreTemporary !== false) store.set({ notesTemporarilyHidden: false });
|
|
198
|
-
|
|
199
|
-
if (container) container.classList.remove("hidden");
|
|
198
|
+
applyCanvasVisibility();
|
|
200
199
|
notes.forEach(function (entry) { if (entry && entry.el) syncFlightState(entry.el); });
|
|
201
200
|
syncFlightControls();
|
|
202
201
|
}
|
|
203
202
|
|
|
203
|
+
// Project Settings owns the screen while open. Suppression is separate from
|
|
204
|
+
// intent: `notesVisible` keeps tracking show/hide requests and updates, and only
|
|
205
|
+
// the canvas element is held hidden, so release shows the latest intended state.
|
|
206
|
+
var notesSuppressed = false;
|
|
207
|
+
function applyCanvasVisibility() {
|
|
208
|
+
var container = document.getElementById("sticky-notes-container");
|
|
209
|
+
if (container) container.classList.toggle("hidden", !notesVisible || notesSuppressed);
|
|
210
|
+
}
|
|
211
|
+
export function holdNotesForSettings() {
|
|
212
|
+
notesSuppressed = true;
|
|
213
|
+
closeColorPicker();
|
|
214
|
+
closeFormatToolbar();
|
|
215
|
+
applyCanvasVisibility();
|
|
216
|
+
}
|
|
217
|
+
export function releaseNotesForSettings() {
|
|
218
|
+
notesSuppressed = false;
|
|
219
|
+
applyCanvasVisibility();
|
|
220
|
+
}
|
|
221
|
+
|
|
204
222
|
export function hideNotes() {
|
|
205
223
|
notesVisible = false;
|
|
206
|
-
|
|
207
|
-
if (container) container.classList.add("hidden");
|
|
224
|
+
applyCanvasVisibility();
|
|
208
225
|
closeColorPicker();
|
|
209
226
|
notes.forEach(function (entry) { if (entry && entry.el) syncFlightState(entry.el, true); });
|
|
210
227
|
syncFlightControls();
|
|
@@ -290,7 +307,7 @@ export function handleNotesList(msg) {
|
|
|
290
307
|
// explicit view state and must survive reconnects and list updates.
|
|
291
308
|
if (openCount() > 0 && !notesVisible) {
|
|
292
309
|
notesVisible = true;
|
|
293
|
-
|
|
310
|
+
applyCanvasVisibility();
|
|
294
311
|
}
|
|
295
312
|
if (created) reclampAllNotes();
|
|
296
313
|
syncFlightControls();
|
|
@@ -314,7 +331,7 @@ export function handleNoteCreated(msg) {
|
|
|
314
331
|
// Show container if hidden, unless the user explicitly flew the notes out.
|
|
315
332
|
if (!notesVisible && !store.get("notesTemporarilyHidden")) {
|
|
316
333
|
notesVisible = true;
|
|
317
|
-
|
|
334
|
+
applyCanvasVisibility();
|
|
318
335
|
}
|
|
319
336
|
reclampAllNotes();
|
|
320
337
|
syncFlightControls();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { initLinearSettings, loadLinearConnection, clearLinearKeyInput } from './linear-settings.js';
|
|
1
2
|
// user-settings.js — Modal dialog for user settings
|
|
2
3
|
// Account management and logout
|
|
3
4
|
|
|
@@ -41,6 +42,7 @@ export function initUserSettings(appCtx) {
|
|
|
41
42
|
if (!settingsEl || !openBtn) return;
|
|
42
43
|
|
|
43
44
|
initCapsulePreference();
|
|
45
|
+
initLinearSettings();
|
|
44
46
|
|
|
45
47
|
navItems = settingsEl.querySelectorAll('.us-nav-item');
|
|
46
48
|
sections = settingsEl.querySelectorAll('.us-section');
|
|
@@ -297,6 +299,7 @@ export function initUserSettings(appCtx) {
|
|
|
297
299
|
// settings tab (e.g. the disabled-Mates picker button passing 'us-mates').
|
|
298
300
|
// Falls back to Account so existing callers and the icon click are unchanged.
|
|
299
301
|
export function openUserSettings(initialSection) {
|
|
302
|
+
loadLinearConnection();
|
|
300
303
|
settingsEl.classList.remove('hidden');
|
|
301
304
|
openBtn.classList.add('active');
|
|
302
305
|
refreshIcons(settingsEl);
|
|
@@ -306,6 +309,7 @@ export function openUserSettings(initialSection) {
|
|
|
306
309
|
}
|
|
307
310
|
|
|
308
311
|
export function closeUserSettings() {
|
|
312
|
+
clearLinearKeyInput();
|
|
309
313
|
settingsEl.classList.add('hidden');
|
|
310
314
|
openBtn.classList.remove('active');
|
|
311
315
|
}
|
package/lib/public/style.css
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
@import url("css/session-github.css");
|
|
2
2
|
@import url("css/vendor-login-browser.css");
|
|
3
3
|
@import url("css/issues.css");
|
|
4
|
+
@import url("css/linear.css");
|
|
4
5
|
@import url("css/base.css?v=20260828-type1");
|
|
5
6
|
@import url("css/icon-strip.css?v=20260828-surfaces4");
|
|
6
7
|
@import url("css/title-bar.css?v=20260828-project-header1");
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
@import url("css/stt.css");
|
|
46
47
|
@import url("css/profile.css");
|
|
47
48
|
@import url("css/user-settings.css?v=20260828-type1");
|
|
49
|
+
@import url("css/settings-ui.css");
|
|
48
50
|
@import url("css/admin.css");
|
|
49
51
|
@import url("css/session-search.css");
|
|
50
52
|
@import url("css/tooltip.css");
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -553,6 +553,9 @@ function createSDKBridge(opts) {
|
|
|
553
553
|
return toolName === name || toolName === "mcp__clay-github__" + name;
|
|
554
554
|
})) return { behavior: "allow", updatedInput: input };
|
|
555
555
|
|
|
556
|
+
if (["link_session_linear", "get_session_linear", "unlink_session_linear", "read_linear_issue", "search_linear_issues"].some(function (name) {
|
|
557
|
+
return toolName === name || toolName === "mcp__clay-linear__" + name;
|
|
558
|
+
})) return { behavior: "allow", updatedInput: input };
|
|
556
559
|
// Shared-browser handlers enforce session access and live control ownership.
|
|
557
560
|
// All actions use the same tool; do not duplicate its in-app handoff UI.
|
|
558
561
|
if (toolName === "shared_browser" || toolName === "mcp__clay-shared-browser__shared_browser") {
|
|
@@ -1109,6 +1112,7 @@ function createSDKBridge(opts) {
|
|
|
1109
1112
|
if (codexCatalogCandidate.userInputVersion) session.codexUserInputToolCatalogVersion = codexCatalogCandidate.userInputVersion;
|
|
1110
1113
|
if (codexCatalogCandidate.sharedBrowserVersion) session.codexSharedBrowserToolCatalogVersion = codexCatalogCandidate.sharedBrowserVersion;
|
|
1111
1114
|
if (codexCatalogCandidate.issuesVersion) session.codexIssuesToolCatalogVersion = codexCatalogCandidate.issuesVersion;
|
|
1115
|
+
if (codexCatalogCandidate.linearVersion) session.codexLinearToolCatalogVersion = codexCatalogCandidate.linearVersion;
|
|
1112
1116
|
sm.saveSessionFile(session);
|
|
1113
1117
|
codexCatalogCandidate = null;
|
|
1114
1118
|
}
|
|
@@ -1790,6 +1794,9 @@ function createSDKBridge(opts) {
|
|
|
1790
1794
|
// delivered by this provider.
|
|
1791
1795
|
var sessionToolDefs = getSessionToolDefs(session) || [];
|
|
1792
1796
|
var queryGeneration = Number(session._sdkQueryGeneration || 0);
|
|
1797
|
+
var hasLinearCatalog = sessionToolDefs.some(function (tool) { return tool.name === "link_session_linear"; });
|
|
1798
|
+
var recoverCodexLinearTools = sessionAdapter.vendor === "codex" && !!session.cliSessionId
|
|
1799
|
+
&& hasLinearCatalog && Number(session.codexLinearToolCatalogVersion || 0) < 1 && canUseSessionTools(session) === true;
|
|
1793
1800
|
var hasIssueCatalog = false;
|
|
1794
1801
|
for (var issueToolIndex = 0; issueToolIndex < sessionToolDefs.length; issueToolIndex++) {
|
|
1795
1802
|
if (sessionToolDefs[issueToolIndex].name === "create_issue") {
|
|
@@ -1973,7 +1980,7 @@ function createSDKBridge(opts) {
|
|
|
1973
1980
|
dynamicTools: sessionToolDefs,
|
|
1974
1981
|
// Codex persists dynamic tools with a thread. A stale authorized Driver
|
|
1975
1982
|
// gets this exact-query bridge; its handlers still reauthorize live.
|
|
1976
|
-
sessionMcpServer: (recoverCodexIssueTools || recoverCodexQuestionTools || recoverCodexBrowserTools) ? getCodexSessionMcpServer(session, queryGeneration) : undefined,
|
|
1983
|
+
sessionMcpServer: (recoverCodexIssueTools || recoverCodexQuestionTools || recoverCodexBrowserTools || recoverCodexLinearTools) ? getCodexSessionMcpServer(session, queryGeneration) : undefined,
|
|
1977
1984
|
callDynamicTool: function (toolName, args) {
|
|
1978
1985
|
var toolDef = findSessionToolDef(toolName);
|
|
1979
1986
|
if (toolDef && typeof toolDef.handler === "function") {
|
|
@@ -2192,6 +2199,7 @@ function createSDKBridge(opts) {
|
|
|
2192
2199
|
if (requiredCatalogTools.ask_user_questions) {
|
|
2193
2200
|
codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, { generation: queryGeneration, userInputVersion: 1 });
|
|
2194
2201
|
}
|
|
2202
|
+
if (hasLinearCatalog) codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, { generation: queryGeneration, linearVersion: 1 });
|
|
2195
2203
|
if (hasSharedBrowserCatalog) {
|
|
2196
2204
|
codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, { generation: queryGeneration, sharedBrowserVersion: 3 });
|
|
2197
2205
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
var attachStore = require('./linear-store').attachLinearStore;
|
|
2
|
+
var api = require('./linear-api');
|
|
3
|
+
function attachLinear(ctx) {
|
|
4
|
+
var storage = attachStore(ctx.CONFIG_DIR), pending = new Map();
|
|
5
|
+
function owner(req) {
|
|
6
|
+
if (!ctx.isRequestAuthed(req)) return null;
|
|
7
|
+
if (!ctx.users.isMultiUser()) return 'default';
|
|
8
|
+
var user = ctx.getMultiUserFromReq(req);
|
|
9
|
+
return user && user.id;
|
|
10
|
+
}
|
|
11
|
+
function json(res, status, body) { res.writeHead(status, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); res.end(JSON.stringify(body)); }
|
|
12
|
+
function handleRequest(req, res, url) {
|
|
13
|
+
if (url !== '/api/linear/connection') return false;
|
|
14
|
+
var id = owner(req);
|
|
15
|
+
if (!id) { json(res, 401, { error: 'Sign in to connect Linear.' }); return true; }
|
|
16
|
+
var origin = req.headers.origin;
|
|
17
|
+
try { if (req.headers['sec-fetch-site'] === 'cross-site' || (origin && new URL(origin).host !== req.headers.host)) throw new Error(); }
|
|
18
|
+
catch (error) { json(res, 403, { error: 'Invalid request origin.' }); return true; }
|
|
19
|
+
if (req.method === 'GET') {
|
|
20
|
+
try { json(res, 200, storage.view(id)); } catch (error) { json(res, 500, { error: 'Could not read Linear connection.' }); }
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (req.method !== 'PUT' && req.method !== 'DELETE') { json(res, 405, { error: 'Method not allowed.' }); return true; }
|
|
24
|
+
// A disconnect or newer save invalidates an older validation in flight.
|
|
25
|
+
var operation = {}; pending.set(id, operation);
|
|
26
|
+
var chunks = [], size = 0;
|
|
27
|
+
req.on('data', function (chunk) { size += chunk.length; if (size <= 8192) chunks.push(chunk); });
|
|
28
|
+
req.on('end', async function () {
|
|
29
|
+
try {
|
|
30
|
+
if (size > 8192) throw new Error('Request is too large.');
|
|
31
|
+
var token = null, workspace = null;
|
|
32
|
+
if (req.method === 'PUT') {
|
|
33
|
+
var body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
34
|
+
if (typeof body.key !== 'string' || !body.key.trim() || body.key.length > 4096 || /[\r\n]/.test(body.key)) throw new Error('Enter a valid Linear API key.');
|
|
35
|
+
token = body.key.trim();
|
|
36
|
+
var data = await (ctx.query || api.query)(token, 'query ClayConnection { organization { id name urlKey } }');
|
|
37
|
+
if (!data.organization || !data.organization.id) throw new Error('Linear workspace is unavailable.');
|
|
38
|
+
workspace = { id: data.organization.id, name: data.organization.name, slug: data.organization.urlKey };
|
|
39
|
+
}
|
|
40
|
+
if (pending.get(id) !== operation || owner(req) !== id || res.destroyed) throw new Error('Connection changed. Please try again.');
|
|
41
|
+
json(res, 200, storage.save(id, token, workspace));
|
|
42
|
+
} catch (error) { if (!res.destroyed) json(res, 400, { error: 'Could not update Linear connection. Check your API key and try again.' }); }
|
|
43
|
+
finally { if (pending.get(id) === operation) pending.delete(id); }
|
|
44
|
+
});
|
|
45
|
+
req.on('error', function () { if (pending.get(id) === operation) pending.delete(id); });
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return { handleRequest: handleRequest, storage: storage };
|
|
49
|
+
}
|
|
50
|
+
module.exports = { attachLinear: attachLinear };
|
package/lib/server.js
CHANGED
|
@@ -443,6 +443,9 @@ function createServer(opts) {
|
|
|
443
443
|
getMultiUserFromReq: getMultiUserFromReq,
|
|
444
444
|
});
|
|
445
445
|
|
|
446
|
+
var linear = require('./server-linear').attachLinear({ users: users, getMultiUserFromReq: getMultiUserFromReq,
|
|
447
|
+
isRequestAuthed: isRequestAuthed, CONFIG_DIR: CONFIG_DIR });
|
|
448
|
+
|
|
446
449
|
var speech = serverSpeech.attachSpeech({ users: users, getMultiUserFromReq: getMultiUserFromReq,
|
|
447
450
|
isRequestAuthed: isRequestAuthed, CONFIG_DIR: CONFIG_DIR, getAllowedOrigins: getAllowedOrigins });
|
|
448
451
|
|
|
@@ -798,6 +801,7 @@ function createServer(opts) {
|
|
|
798
801
|
return;
|
|
799
802
|
}
|
|
800
803
|
|
|
804
|
+
if (linear.handleRequest(req, res, fullUrl)) return;
|
|
801
805
|
if (speech.handleRequest(req, res, fullUrl)) return;
|
|
802
806
|
if (networkSettings.handleRequest(req, res, fullUrl)) return;
|
|
803
807
|
if (settings.handleRequest(req, res, fullUrl)) return;
|
|
@@ -1352,6 +1356,7 @@ function createServer(opts) {
|
|
|
1352
1356
|
workspaceQueryService: workspaceQueryService,
|
|
1353
1357
|
projectLogsService: projectLogsService,
|
|
1354
1358
|
issuesService: issuesService,
|
|
1359
|
+
linearStorage: linear.storage,
|
|
1355
1360
|
mateKnowledgeService: mateKnowledgeService,
|
|
1356
1361
|
durableScheduler: durableScheduler,
|
|
1357
1362
|
canAccessProjectSlug: function (userId, targetSlug) {
|
package/lib/sessions.js
CHANGED
|
@@ -177,10 +177,12 @@ function createSessionManager(opts) {
|
|
|
177
177
|
createdAt: session.createdAt,
|
|
178
178
|
};
|
|
179
179
|
if (session.githubLinks) metaObj.githubLinks = session.githubLinks;
|
|
180
|
+
if (session.linearLinks) metaObj.linearLinks = session.linearLinks;
|
|
180
181
|
if (session.ownerId) metaObj.ownerId = session.ownerId;
|
|
181
182
|
if (session.vendor) metaObj.vendor = session.vendor;
|
|
182
183
|
if (session.codexDynamicToolCatalogVersion) metaObj.codexDynamicToolCatalogVersion = session.codexDynamicToolCatalogVersion;
|
|
183
184
|
if (session.codexUserInputToolCatalogVersion) metaObj.codexUserInputToolCatalogVersion = session.codexUserInputToolCatalogVersion;
|
|
185
|
+
if (session.codexLinearToolCatalogVersion) metaObj.codexLinearToolCatalogVersion = session.codexLinearToolCatalogVersion;
|
|
184
186
|
if (session.codexSharedBrowserToolCatalogVersion) metaObj.codexSharedBrowserToolCatalogVersion = session.codexSharedBrowserToolCatalogVersion;
|
|
185
187
|
if (session.codexIssuesToolCatalogVersion) metaObj.codexIssuesToolCatalogVersion = session.codexIssuesToolCatalogVersion;
|
|
186
188
|
if (session.model) metaObj.model = session.model;
|
|
@@ -347,6 +349,7 @@ function createSessionManager(opts) {
|
|
|
347
349
|
titleProvisional: m.titleProvisional === true,
|
|
348
350
|
titleAutoGenerated: m.titleAutoGenerated === true,
|
|
349
351
|
githubLinks: Array.isArray(m.githubLinks) ? m.githubLinks.slice(0, 8) : [],
|
|
352
|
+
linearLinks: Array.isArray(m.linearLinks) ? m.linearLinks.slice(0, 8) : [],
|
|
350
353
|
titleManuallySet: m.titleManuallySet === true || legacyTitleProtected,
|
|
351
354
|
createdAt: m.createdAt || Date.now(),
|
|
352
355
|
lastActivity: loaded[i].mtime || m.createdAt || Date.now(),
|
|
@@ -357,6 +360,7 @@ function createSessionManager(opts) {
|
|
|
357
360
|
if (m.vendor) session.vendor = m.vendor;
|
|
358
361
|
session.codexDynamicToolCatalogVersion = Number(m.codexDynamicToolCatalogVersion || 0);
|
|
359
362
|
session.codexUserInputToolCatalogVersion = Number(m.codexUserInputToolCatalogVersion || 0);
|
|
363
|
+
session.codexLinearToolCatalogVersion = Number(m.codexLinearToolCatalogVersion || 0);
|
|
360
364
|
session.codexSharedBrowserToolCatalogVersion = Number(m.codexSharedBrowserToolCatalogVersion || 0);
|
|
361
365
|
session.codexIssuesToolCatalogVersion = Number(m.codexIssuesToolCatalogVersion || 0);
|
|
362
366
|
session.model = m.model || deriveSessionModel(loaded[i].history);
|
|
@@ -611,6 +615,7 @@ function createSessionManager(opts) {
|
|
|
611
615
|
cliSessionId: s.cliSessionId || null,
|
|
612
616
|
title: s.title || "New Session",
|
|
613
617
|
githubLinks: s.githubLinks || [],
|
|
618
|
+
linearLinks: s.linearLinks || [],
|
|
614
619
|
active: isActive,
|
|
615
620
|
isProcessing: s.isProcessing,
|
|
616
621
|
// Sidebar badge: user work only. Ambient housekeeping is surfaced in the
|
package/package.json
CHANGED