clay-server 4.0.0-beta.17 → 4.0.0-beta.18
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-logs-mcp-server.js +10 -4
- package/lib/project-logs-service.js +29 -0
- package/lib/project-logs.js +5 -1
- package/lib/project.js +1 -1
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/sticky-notes.css +7 -0
- package/lib/public/index.html +2 -2
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/chat-bubble-renderer.js +1 -1
- package/lib/public/modules/clay-log-links.js +70 -0
- package/lib/public/modules/command-palette.js +52 -15
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/project-logs.js +7 -0
- package/lib/public/modules/sidebar-session-hierarchy.js +16 -7
- package/lib/public/modules/sidebar-sessions.js +3 -1
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +2 -0
- package/lib/public/modules/sticky-notes-card.js +2 -0
- package/lib/public/modules/sticky-notes-editor.js +3 -0
- package/lib/public/modules/sticky-notes.js +2 -0
- package/lib/public/style.css +1 -1
- package/lib/sdk-bridge.js +20 -10
- package/lib/server-global-ws.js +4 -0
- package/lib/server-home-chat-events.js +3 -0
- package/lib/server-home-chat.js +9 -2
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server.js +8 -3
- package/lib/ws-schema.js +2 -0
- package/package.json +1 -1
|
@@ -22,6 +22,11 @@ var LOGS_CONTRACT =
|
|
|
22
22
|
"Do not paste raw conversation transcripts, log command-by-command narration, trivial confirmations, or speculation. Repository history may show the code change but usually does not preserve the user's intent, constraints, verification, or unfinished state, so it is not a substitute for the work log. " +
|
|
23
23
|
"Every write is attributed and permanently revision-tracked, so keep entries concise, concrete, and true while retaining enough context for a clean Driver handoff.";
|
|
24
24
|
|
|
25
|
+
var CLAY_READ_CONTRACT =
|
|
26
|
+
"When the user asks Clay about a project's prior work, decisions, defects, status, rationale, or unfinished work, identify the relevant project and search its Project Logs before answering. " +
|
|
27
|
+
"Use conversation history as additional evidence when useful, but do not substitute it for the ledger's durable project record. Do not search Logs for unrelated general questions. " +
|
|
28
|
+
"When citing a returned log in user-visible text, write [clayos/<ref> — short label] so Clay Studio renders an owner-validated Log control.";
|
|
29
|
+
|
|
25
30
|
// User learning moments are a durable project asset, so capturing them is a
|
|
26
31
|
// default rather than an option. This category is about a change in the user's
|
|
27
32
|
// conceptual model, never knowledge the Driver acquired while doing the work.
|
|
@@ -236,7 +241,7 @@ function globalTools(bound) {
|
|
|
236
241
|
return [
|
|
237
242
|
{
|
|
238
243
|
name: "list_project_logs",
|
|
239
|
-
description: LOGS_CONTRACT + " List logs for one project the current user is authorized to see. Read-only, and available only to authoritative builtin Clay.",
|
|
244
|
+
description: LOGS_CONTRACT + " " + CLAY_READ_CONTRACT + " List logs for one project the current user is authorized to see. Read-only, and available only to authoritative builtin Clay.",
|
|
240
245
|
inputSchema: buildShape({
|
|
241
246
|
projectSlug: { type: "string", description: "Exact project slug." },
|
|
242
247
|
kind: { type: "string", description: "Optional category filter, matched exactly against this project's vocabulary. The response lists the categories currently in use." },
|
|
@@ -250,7 +255,7 @@ function globalTools(bound) {
|
|
|
250
255
|
},
|
|
251
256
|
{
|
|
252
257
|
name: "search_project_logs",
|
|
253
|
-
description: LOGS_CONTRACT + " Search logs for one project the current user is authorized to see. Read-only, and available only to authoritative builtin Clay.",
|
|
258
|
+
description: LOGS_CONTRACT + " " + CLAY_READ_CONTRACT + " Search logs for one project the current user is authorized to see. Read-only, and available only to authoritative builtin Clay.",
|
|
254
259
|
inputSchema: buildShape({
|
|
255
260
|
projectSlug: { type: "string", description: "Exact project slug." },
|
|
256
261
|
query: { type: "string", description: "Search query." },
|
|
@@ -264,7 +269,7 @@ function globalTools(bound) {
|
|
|
264
269
|
},
|
|
265
270
|
{
|
|
266
271
|
name: "read_project_log_revision",
|
|
267
|
-
description: LOGS_CONTRACT + " Read one entry as of a given revision. Read-only, and available only to authoritative builtin Clay.",
|
|
272
|
+
description: LOGS_CONTRACT + " " + CLAY_READ_CONTRACT + " Read one entry as of a given revision. Read-only, and available only to authoritative builtin Clay.",
|
|
268
273
|
inputSchema: buildShape({
|
|
269
274
|
projectSlug: { type: "string", description: "Exact project slug." },
|
|
270
275
|
ref: { type: "string", description: REF_DESCRIPTION },
|
|
@@ -274,7 +279,7 @@ function globalTools(bound) {
|
|
|
274
279
|
},
|
|
275
280
|
{
|
|
276
281
|
name: "read_project_log",
|
|
277
|
-
description: LOGS_CONTRACT + " Read one log entry from an authorized project, including its summary and any comments people have added. Read-only, and available only to authoritative builtin Clay.",
|
|
282
|
+
description: LOGS_CONTRACT + " " + CLAY_READ_CONTRACT + " Read one log entry from an authorized project, including its summary and any comments people have added. Read-only, and available only to authoritative builtin Clay.",
|
|
278
283
|
inputSchema: buildShape({
|
|
279
284
|
projectSlug: { type: "string", description: "Exact project slug." },
|
|
280
285
|
ref: { type: "string", description: REF_DESCRIPTION },
|
|
@@ -312,6 +317,7 @@ function createMcpServer(adapter, bound, includeGlobal) {
|
|
|
312
317
|
|
|
313
318
|
module.exports = {
|
|
314
319
|
LOGS_CONTRACT: LOGS_CONTRACT,
|
|
320
|
+
CLAY_READ_CONTRACT: CLAY_READ_CONTRACT,
|
|
315
321
|
LEARNING_CONTRACT: LEARNING_CONTRACT,
|
|
316
322
|
ATTENTION_CONTRACT: ATTENTION_CONTRACT,
|
|
317
323
|
REVIEW_CONTRACT: REVIEW_CONTRACT,
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
var builtinMates = require("./builtin-mates");
|
|
23
23
|
var logsStore = require("./project-logs-store");
|
|
24
|
+
var LOG_REF_PATTERN = /^log:[A-Za-z0-9_-]{24}$/;
|
|
24
25
|
|
|
25
26
|
function readOnly() {
|
|
26
27
|
throw new Error("Project Logs are read-only for builtin Clay.");
|
|
@@ -396,6 +397,33 @@ function attachProjectLogsService(ctx) {
|
|
|
396
397
|
return storeForStatus(statusFor(principal, slug));
|
|
397
398
|
}
|
|
398
399
|
|
|
400
|
+
function resolveLogNavigation(args) {
|
|
401
|
+
var principal = revalidate();
|
|
402
|
+
var ref = args && args.ref;
|
|
403
|
+
if (!LOG_REF_PATTERN.test(ref || "")) throw new Error("Invalid log reference.");
|
|
404
|
+
var projects = getProjects();
|
|
405
|
+
var seenScopes = new Set();
|
|
406
|
+
var match = null;
|
|
407
|
+
if (!projects || typeof projects.forEach !== "function") throw new Error("Project Logs are unavailable.");
|
|
408
|
+
projects.forEach(function (project, slug) {
|
|
409
|
+
if (!project || typeof project.getStatus !== "function") return;
|
|
410
|
+
var status = project.getStatus();
|
|
411
|
+
if (!authorizedProject(principal, status)) return;
|
|
412
|
+
var governing = effectiveStatus(status);
|
|
413
|
+
var scope = governing && (governing.projectKnowledgeId || governing.path);
|
|
414
|
+
if (!scope || seenScopes.has(scope)) return;
|
|
415
|
+
seenScopes.add(scope);
|
|
416
|
+
var entry = null;
|
|
417
|
+
try { entry = storeForStatus(status).read(ref, false); } catch (e) { return; }
|
|
418
|
+
if (!entry) return;
|
|
419
|
+
var targetSlug = status.isWorktree && status.parentSlug && projectFor(status.parentSlug) ? status.parentSlug : slug;
|
|
420
|
+
if (match && match.projectSlug !== targetSlug) throw new Error("Log reference is ambiguous.");
|
|
421
|
+
match = { projectSlug: targetSlug, ref: ref };
|
|
422
|
+
});
|
|
423
|
+
if (!match) throw new Error("Log entry not found.");
|
|
424
|
+
return match;
|
|
425
|
+
}
|
|
426
|
+
|
|
399
427
|
return {
|
|
400
428
|
isClay: true,
|
|
401
429
|
canWrite: false,
|
|
@@ -413,6 +441,7 @@ function attachProjectLogsService(ctx) {
|
|
|
413
441
|
logHistory: function (args) { return target(args).history(args && args.ref, args || {}); },
|
|
414
442
|
commentLog: readOnly,
|
|
415
443
|
readLogRevision: function (args) { return target(args).readRevision(args && args.ref, args && args.revision); },
|
|
444
|
+
resolveLogNavigation: resolveLogNavigation,
|
|
416
445
|
listLogFeedback: readOnly,
|
|
417
446
|
reviewLogComment: readOnly,
|
|
418
447
|
revertLog: readOnly,
|
package/lib/project-logs.js
CHANGED
|
@@ -366,7 +366,11 @@ function attachProjectLogs(ctx) {
|
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
function getSystemPrompt(session) {
|
|
369
|
-
if (
|
|
369
|
+
if (!service) return "";
|
|
370
|
+
if (isMate) {
|
|
371
|
+
if (!includeGlobal() || (session && !sessionBinding(session))) return "";
|
|
372
|
+
return SYSTEM_PROMPT_LABEL + "\n" + logsMcp.CLAY_READ_CONTRACT;
|
|
373
|
+
}
|
|
370
374
|
if (session && !sessionBinding(session)) return "";
|
|
371
375
|
return SYSTEM_PROMPT_LABEL + "\n" + logsMcp.LOGS_CONTRACT +
|
|
372
376
|
"\n" + logsMcp.LEARNING_CONTRACT +
|
package/lib/project.js
CHANGED
|
@@ -1319,7 +1319,7 @@ function createProjectContext(opts) {
|
|
|
1319
1319
|
if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg);
|
|
1320
1320
|
return;
|
|
1321
1321
|
}
|
|
1322
|
-
if (msg.type === "home_clay_ask" || msg.type === "home_clay_session_resolve") {
|
|
1322
|
+
if (msg.type === "home_clay_ask" || msg.type === "home_clay_session_resolve" || msg.type === "home_clay_log_resolve") {
|
|
1323
1323
|
if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
|
|
1324
1324
|
return;
|
|
1325
1325
|
}
|
|
@@ -486,7 +486,8 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
|
|
|
486
486
|
.search-clay-activity-item small { color: var(--text-dimmer); font-size: 10px; line-height: 1.35; }
|
|
487
487
|
@keyframes searchClaySpin { to { transform: rotate(360deg); } }
|
|
488
488
|
|
|
489
|
-
.clayos-session-link
|
|
489
|
+
.clayos-session-link,
|
|
490
|
+
.clayos-log-link {
|
|
490
491
|
display: inline-flex;
|
|
491
492
|
align-items: center;
|
|
492
493
|
gap: 6px;
|
|
@@ -503,7 +504,8 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
|
|
|
503
504
|
vertical-align: middle;
|
|
504
505
|
cursor: pointer;
|
|
505
506
|
}
|
|
506
|
-
.clayos-session-link::before
|
|
507
|
+
.clayos-session-link::before,
|
|
508
|
+
.clayos-log-link::before {
|
|
507
509
|
content: "\2197";
|
|
508
510
|
display: grid;
|
|
509
511
|
place-items: center;
|
|
@@ -514,13 +516,21 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
|
|
|
514
516
|
color: var(--accent);
|
|
515
517
|
font-size: 12px;
|
|
516
518
|
}
|
|
517
|
-
.clayos-
|
|
518
|
-
.clayos-session-link >
|
|
519
|
-
.clayos-
|
|
520
|
-
.clayos-session-link
|
|
521
|
-
.clayos-
|
|
522
|
-
.clayos-session-link
|
|
523
|
-
.clayos-
|
|
519
|
+
.clayos-log-link::before { content: "\2261"; }
|
|
520
|
+
.clayos-session-link > span,
|
|
521
|
+
.clayos-log-link > span { font-weight: 620; }
|
|
522
|
+
.clayos-session-link > small,
|
|
523
|
+
.clayos-log-link > small { color: var(--text-muted); font-size: 10px; }
|
|
524
|
+
.clayos-session-link:hover,
|
|
525
|
+
.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)); }
|
|
526
|
+
.clayos-session-link:focus-visible,
|
|
527
|
+
.clayos-log-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
528
|
+
.clayos-session-link.is-loading,
|
|
529
|
+
.clayos-log-link.is-loading { opacity: 0.62; cursor: progress; }
|
|
530
|
+
.clayos-session-link.is-loading::before,
|
|
531
|
+
.clayos-log-link.is-loading::before { animation: searchClaySpin 1.2s linear infinite; }
|
|
532
|
+
.clayos-session-link.is-error,
|
|
533
|
+
.clayos-log-link.is-error { border-color: color-mix(in srgb, var(--warning) 48%, var(--border)); }
|
|
524
534
|
|
|
525
535
|
.search-clay-pending {
|
|
526
536
|
align-self: flex-start;
|
|
@@ -85,9 +85,13 @@
|
|
|
85
85
|
min-height: 80px;
|
|
86
86
|
border: 1px solid color-mix(in srgb, var(--note-border) calc(var(--note-opacity, 0.64) * 100%), transparent);
|
|
87
87
|
background: color-mix(in srgb, var(--note-surface) calc(var(--note-opacity, 0.64) * 100%), transparent);
|
|
88
|
+
-webkit-backdrop-filter: blur(7px) saturate(0.92) contrast(1.02);
|
|
89
|
+
backdrop-filter: blur(7px) saturate(0.92) contrast(1.02);
|
|
88
90
|
color: var(--note-ink);
|
|
89
91
|
transition: box-shadow 0.15s;
|
|
90
92
|
overflow: hidden;
|
|
93
|
+
contain: layout style;
|
|
94
|
+
will-change: transform, backdrop-filter;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
.sticky-note:hover {
|
|
@@ -798,8 +802,11 @@
|
|
|
798
802
|
|
|
799
803
|
@media (prefers-reduced-transparency: reduce) {
|
|
800
804
|
.sticky-note {
|
|
805
|
+
-webkit-backdrop-filter: none;
|
|
806
|
+
backdrop-filter: none;
|
|
801
807
|
border-color: var(--note-border);
|
|
802
808
|
background: var(--note-surface);
|
|
809
|
+
will-change: transform;
|
|
803
810
|
}
|
|
804
811
|
}
|
|
805
812
|
|
package/lib/public/index.html
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
(function(){try{var k="clay-theme-vars",v=localStorage.getItem(k),r=document.documentElement;if(v){var o=JSON.parse(v),p;for(p in o)r.style.setProperty(p,o[p]);var vt=localStorage.getItem(k.replace("-vars","-variant"));if(vt==="light"){r.classList.add("light-theme");r.classList.remove("dark-theme")}else{r.classList.add("dark-theme");r.classList.remove("light-theme")}var m=document.querySelector('meta[name="theme-color"]');if(m&&o["--bg"])m.setAttribute("content",o["--bg"])}else{var sl=window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches;if(sl){r.classList.add("light-theme");r.classList.remove("dark-theme")}}}catch(e){}})();
|
|
32
32
|
</script>
|
|
33
33
|
<script>if(window.navigator.standalone||window.matchMedia("(display-mode:standalone)").matches){document.documentElement.classList.add("pwa-standalone")}</script>
|
|
34
|
-
<link rel="stylesheet" href="style.css?v=
|
|
34
|
+
<link rel="stylesheet" href="style.css?v=20260908-log-links2">
|
|
35
35
|
<style>
|
|
36
36
|
@media(max-width:768px){
|
|
37
37
|
/* User messages: vertical stack, avatar on top, right-aligned */
|
|
@@ -2337,7 +2337,7 @@
|
|
|
2337
2337
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"></script>
|
|
2338
2338
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0/lib/addon-web-links.min.js"></script>
|
|
2339
2339
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0/lib/addon-webgl.min.js"></script>
|
|
2340
|
-
<script type="module" src="app.js?v=
|
|
2340
|
+
<script type="module" src="app.js?v=20260908-log-links2"></script>
|
|
2341
2341
|
<div id="pwa-install-modal" class="pwa-modal hidden">
|
|
2342
2342
|
<div class="pwa-modal-backdrop"></div>
|
|
2343
2343
|
<div class="pwa-modal-card">
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
3
|
import { showToast, copyToClipboard, escapeHtml } from './utils.js';
|
|
4
4
|
|
|
5
|
-
function showConfirmDialog(message, onConfirm) {
|
|
5
|
+
function showConfirmDialog(message, actionLabel, onConfirm) {
|
|
6
6
|
var modal = document.createElement("div");
|
|
7
7
|
modal.className = "admin-modal-overlay";
|
|
8
|
-
var html = '<div class="admin-modal">' +
|
|
8
|
+
var html = '<div class="admin-modal" role="dialog" aria-modal="true" aria-label="Confirm action">' +
|
|
9
9
|
'<div class="admin-modal-body" style="padding:20px 16px 16px">' +
|
|
10
10
|
'<p class="admin-modal-desc" style="margin:0;font-size:14px;color:var(--text)">' + escapeHtml(message) + '</p>' +
|
|
11
11
|
'</div>' +
|
|
12
12
|
'<div class="admin-modal-footer">' +
|
|
13
|
-
'<button class="admin-modal-save admin-modal-confirm-danger">
|
|
13
|
+
'<button class="admin-modal-save admin-modal-confirm-danger">' + escapeHtml(actionLabel) + '</button>' +
|
|
14
14
|
'<button class="admin-modal-cancel">Cancel</button>' +
|
|
15
15
|
'</div></div>';
|
|
16
16
|
modal.innerHTML = html;
|
|
@@ -198,9 +198,9 @@ function renderUsersTab(body) {
|
|
|
198
198
|
var userId = this.dataset.userId;
|
|
199
199
|
var user = cachedUsers.find(function (u) { return u.id === userId; });
|
|
200
200
|
var name = user ? (user.displayName || user.username) : "this user";
|
|
201
|
-
|
|
201
|
+
showConfirmDialog("Remove " + name + "? This cannot be undone.", "Remove user", function () {
|
|
202
202
|
removeUser(userId, body);
|
|
203
|
-
}
|
|
203
|
+
});
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
206
|
}
|
|
@@ -509,7 +509,7 @@ function renderInvitesTab(body) {
|
|
|
509
509
|
revokeBtns[k].addEventListener("click", function () {
|
|
510
510
|
var code = this.dataset.code;
|
|
511
511
|
var btn = this;
|
|
512
|
-
showConfirmDialog("Revoke this invite? The link will no longer work.", function () {
|
|
512
|
+
showConfirmDialog("Revoke this invite? The link will no longer work.", "Revoke", function () {
|
|
513
513
|
btn.disabled = true;
|
|
514
514
|
fetch("/api/admin/invites/" + encodeURIComponent(code), { method: "DELETE" })
|
|
515
515
|
.then(function (r) { return r.json(); })
|
|
@@ -751,12 +751,12 @@ function renderSmtpTab(body, cfg) {
|
|
|
751
751
|
var removeBtn = body.querySelector("#smtp-remove");
|
|
752
752
|
if (removeBtn) {
|
|
753
753
|
removeBtn.addEventListener("click", function () {
|
|
754
|
-
|
|
754
|
+
showConfirmDialog("Remove SMTP configuration? Users will need to use PIN login.", "Remove SMTP", function () {
|
|
755
755
|
apiPost("/api/admin/smtp", { host: "", user: "", pass: "", from: "" }).then(function () {
|
|
756
756
|
showToast("SMTP configuration removed");
|
|
757
757
|
loadSmtpTab(body);
|
|
758
758
|
});
|
|
759
|
-
}
|
|
759
|
+
});
|
|
760
760
|
});
|
|
761
761
|
}
|
|
762
762
|
}
|
|
@@ -105,7 +105,7 @@ export function addAssistantCopyHandler(row, rawText) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
row.addEventListener("click", function (event) {
|
|
108
|
-
if (event.target.closest("a, pre, code")) return;
|
|
108
|
+
if (event.target.closest("a, button, pre, code")) return;
|
|
109
109
|
var selection = window.getSelection();
|
|
110
110
|
if (selection && selection.toString().length > 0) return;
|
|
111
111
|
if (!primed) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export function parseClayLogReferences(text) {
|
|
2
|
+
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;
|
|
3
|
+
var results = [];
|
|
4
|
+
var match;
|
|
5
|
+
while ((match = pattern.exec(text || ""))) {
|
|
6
|
+
results.push({
|
|
7
|
+
start: match.index,
|
|
8
|
+
end: pattern.lastIndex,
|
|
9
|
+
prefix: match[3] || "",
|
|
10
|
+
ref: match[1] || match[4],
|
|
11
|
+
label: match[2] ? match[2].trim() : "",
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return results;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isExactClayLogReference(text) {
|
|
18
|
+
return /^log:[A-Za-z0-9_-]{24}$/.test((text || "").trim());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createLogLink(ref, labelText) {
|
|
22
|
+
var button = document.createElement("button");
|
|
23
|
+
button.type = "button";
|
|
24
|
+
button.className = "clayos-log-link";
|
|
25
|
+
button.contentEditable = "false";
|
|
26
|
+
button.dataset.logRef = ref;
|
|
27
|
+
button.setAttribute("aria-label", "Open Project Log" + (labelText ? " " + labelText : ""));
|
|
28
|
+
var label = document.createElement("span");
|
|
29
|
+
label.textContent = "Log";
|
|
30
|
+
button.appendChild(label);
|
|
31
|
+
if (labelText) {
|
|
32
|
+
var meta = document.createElement("small");
|
|
33
|
+
meta.textContent = labelText;
|
|
34
|
+
button.appendChild(meta);
|
|
35
|
+
}
|
|
36
|
+
return button;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function enhanceClayLogLinks(root) {
|
|
40
|
+
var codeNodes = root.querySelectorAll("code");
|
|
41
|
+
for (var codeIndex = 0; codeIndex < codeNodes.length; codeIndex++) {
|
|
42
|
+
var code = codeNodes[codeIndex];
|
|
43
|
+
var codeText = (code.textContent || "").trim();
|
|
44
|
+
if (code.closest("pre") || !isExactClayLogReference(codeText)) continue;
|
|
45
|
+
code.parentNode.replaceChild(createLogLink(codeText, ""), code);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
49
|
+
var nodes = [];
|
|
50
|
+
var node;
|
|
51
|
+
while ((node = walker.nextNode())) {
|
|
52
|
+
if (!node.parentElement || node.parentElement.closest("code, pre, a, button")) continue;
|
|
53
|
+
if (parseClayLogReferences(node.nodeValue).length) nodes.push(node);
|
|
54
|
+
}
|
|
55
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
56
|
+
var text = nodes[i].nodeValue || "";
|
|
57
|
+
var matches = parseClayLogReferences(text);
|
|
58
|
+
var fragment = document.createDocumentFragment();
|
|
59
|
+
var offset = 0;
|
|
60
|
+
for (var j = 0; j < matches.length; j++) {
|
|
61
|
+
var match = matches[j];
|
|
62
|
+
fragment.appendChild(document.createTextNode(text.slice(offset, match.start)));
|
|
63
|
+
if (match.prefix) fragment.appendChild(document.createTextNode(match.prefix));
|
|
64
|
+
fragment.appendChild(createLogLink(match.ref, match.label));
|
|
65
|
+
offset = match.end;
|
|
66
|
+
}
|
|
67
|
+
fragment.appendChild(document.createTextNode(text.slice(offset)));
|
|
68
|
+
nodes[i].parentNode.replaceChild(fragment, nodes[i]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -6,6 +6,7 @@ import { showHomeHub, minimizeHomeHub } from './app-home-hub.js';
|
|
|
6
6
|
import { openHomeConversation } from './home-mate-chat.js';
|
|
7
7
|
import { startSearchClayChat, attachSearchClayChat, detachSearchClayChat, getSearchClayChatSummary } from './search-clay-chat.js';
|
|
8
8
|
import { getWs } from './ws-ref.js';
|
|
9
|
+
import { openProjectLog } from './project-logs.js';
|
|
9
10
|
|
|
10
11
|
function formatRelativeDate(ts) {
|
|
11
12
|
if (!ts) return "";
|
|
@@ -27,6 +28,7 @@ var ctx;
|
|
|
27
28
|
var paletteEl = null, inputEl = null, resultsEl = null, footerEl = null;
|
|
28
29
|
var activeIndex = -1, items = [], debounceTimer = null, abortCtrl = null;
|
|
29
30
|
var pendingNav = null, cachedHomeData = [], cachedVersion = null;
|
|
31
|
+
var pendingLogNav = null;
|
|
30
32
|
var searchResults = [], searchQuery = "", searchPending = false, chatMode = false;
|
|
31
33
|
|
|
32
34
|
export function initCommandPalette(value) {
|
|
@@ -52,43 +54,62 @@ export function initCommandPalette(value) {
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
function handleClaySessionLinkClick(event) {
|
|
55
|
-
var link = event.target.closest(".clayos-session-link");
|
|
57
|
+
var link = event.target.closest(".clayos-session-link, .clayos-log-link");
|
|
56
58
|
if (!link || link.classList.contains("is-loading")) return;
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
event.stopPropagation();
|
|
61
|
+
if (link.classList.contains("clayos-log-link") && !link.closest(".search-clay-chat, #home-mate-chat")) {
|
|
62
|
+
openProjectLog(link.dataset.logRef);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
57
65
|
var ws = getWs();
|
|
58
66
|
if (!ws || ws.readyState !== 1) {
|
|
59
67
|
link.classList.add("is-error");
|
|
60
|
-
link.title = "Reconnect to open this
|
|
68
|
+
link.title = "Reconnect to open this item";
|
|
61
69
|
return;
|
|
62
70
|
}
|
|
63
|
-
var
|
|
64
|
-
|
|
71
|
+
var isLog = link.classList.contains("clayos-log-link");
|
|
72
|
+
var requestId = "clayos-" + (isLog ? "log-" : "session-") + Date.now() + "-" + Math.random().toString(36).slice(2, 9);
|
|
73
|
+
if (isLog) link.dataset.logRequest = requestId;
|
|
74
|
+
else link.dataset.sessionRequest = requestId;
|
|
65
75
|
link.classList.remove("is-error");
|
|
66
76
|
link.classList.add("is-loading");
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
var request = { requestId: requestId, surface: link.closest(".search-clay-chat") ? "search" : "home" };
|
|
78
|
+
if (isLog) {
|
|
79
|
+
request.type = "home_clay_log_resolve";
|
|
80
|
+
request.ref = link.dataset.logRef;
|
|
81
|
+
} else {
|
|
82
|
+
request.type = "home_clay_session_resolve";
|
|
83
|
+
request.sessionRef = link.dataset.sessionRef;
|
|
84
|
+
}
|
|
85
|
+
ws.send(JSON.stringify(request));
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
export function handleClaySessionTarget(msg) {
|
|
76
|
-
if (!msg || msg.type !== "home_clay_session_target") return false;
|
|
77
|
-
var
|
|
89
|
+
if (!msg || (msg.type !== "home_clay_session_target" && msg.type !== "home_clay_log_target")) return false;
|
|
90
|
+
var isLog = msg.type === "home_clay_log_target";
|
|
91
|
+
var links = document.querySelectorAll(isLog ? ".clayos-log-link[data-log-request]" : ".clayos-session-link[data-session-request]");
|
|
78
92
|
var link = null;
|
|
79
|
-
for (var i = 0; i < links.length; i++)
|
|
93
|
+
for (var i = 0; i < links.length; i++) {
|
|
94
|
+
if ((isLog ? links[i].dataset.logRequest : links[i].dataset.sessionRequest) === msg.requestId) link = links[i];
|
|
95
|
+
}
|
|
80
96
|
if (link) {
|
|
81
97
|
link.classList.remove("is-loading");
|
|
82
|
-
delete link.dataset.
|
|
98
|
+
if (isLog) delete link.dataset.logRequest;
|
|
99
|
+
else delete link.dataset.sessionRequest;
|
|
83
100
|
}
|
|
84
101
|
if (msg.status !== "ready" || !msg.target) {
|
|
85
102
|
if (link) {
|
|
86
103
|
link.classList.add("is-error");
|
|
87
|
-
link.title = msg.error || "
|
|
104
|
+
link.title = msg.error || "Item not available";
|
|
88
105
|
}
|
|
89
106
|
return true;
|
|
90
107
|
}
|
|
91
108
|
closeCommandPalette();
|
|
109
|
+
if (isLog) {
|
|
110
|
+
navigateToLog(msg.target);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
92
113
|
if (msg.target.isMate && msg.target.mateId && msg.target.homeSessionId) {
|
|
93
114
|
showHomeHub();
|
|
94
115
|
openHomeConversation(msg.target.mateId, msg.target.homeSessionId);
|
|
@@ -129,6 +150,11 @@ export function closeCommandPalette() {
|
|
|
129
150
|
export function setPaletteVersion(version) { cachedVersion = version; }
|
|
130
151
|
|
|
131
152
|
export function handlePaletteSessionSwitch() {
|
|
153
|
+
if (pendingLogNav && ctx.currentSlug && ctx.currentSlug() === pendingLogNav.projectSlug) {
|
|
154
|
+
var logNav = pendingLogNav;
|
|
155
|
+
pendingLogNav = null;
|
|
156
|
+
openProjectLog(logNav.ref);
|
|
157
|
+
}
|
|
132
158
|
if (!pendingNav) return;
|
|
133
159
|
var nav = pendingNav;
|
|
134
160
|
pendingNav = null;
|
|
@@ -138,6 +164,17 @@ export function handlePaletteSessionSwitch() {
|
|
|
138
164
|
}
|
|
139
165
|
}
|
|
140
166
|
|
|
167
|
+
function navigateToLog(target) {
|
|
168
|
+
closeCommandPalette();
|
|
169
|
+
minimizeHomeHub();
|
|
170
|
+
if (ctx.currentSlug && ctx.currentSlug() === target.projectSlug) {
|
|
171
|
+
openProjectLog(target.ref);
|
|
172
|
+
} else {
|
|
173
|
+
pendingLogNav = { projectSlug: target.projectSlug, ref: target.ref };
|
|
174
|
+
ctx.switchProject(target.projectSlug);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
141
178
|
function buildDOM() {
|
|
142
179
|
paletteEl = document.createElement("div");
|
|
143
180
|
paletteEl.className = "cmd-palette hidden";
|
|
@@ -2,6 +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
6
|
|
|
6
7
|
// Initialize markdown parser
|
|
7
8
|
marked.use({ gfm: true, breaks: false });
|
|
@@ -89,6 +90,7 @@ export function enhanceClaySessionLinks(root) {
|
|
|
89
90
|
fragment.appendChild(document.createTextNode(text.slice(offset)));
|
|
90
91
|
nodes[i].parentNode.replaceChild(fragment, nodes[i]);
|
|
91
92
|
}
|
|
93
|
+
enhanceClayLogLinks(root);
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
/**
|
|
@@ -336,6 +336,13 @@ export function openProjectLogs() {
|
|
|
336
336
|
requestList();
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
export function openProjectLog(ref) {
|
|
340
|
+
if (typeof ref !== "string" || !/^log:[A-Za-z0-9_-]{24}$/.test(ref)) return false;
|
|
341
|
+
openProjectLogs();
|
|
342
|
+
requestEntry(ref);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
339
346
|
export function closeProjectLogs() {
|
|
340
347
|
if (!store.get('projectLogsOpen')) return;
|
|
341
348
|
if (panel) {
|
|
@@ -55,6 +55,10 @@ export function hierarchyItemMatches(item, matchIds) {
|
|
|
55
55
|
return true;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function isDesktopHierarchyToggleEvent(event) {
|
|
59
|
+
return !!(event && event.target && typeof event.target.closest === "function" && event.target.closest(".session-driver-toggle"));
|
|
60
|
+
}
|
|
61
|
+
|
|
58
62
|
function expanded(surface, key, workers, matchIds) {
|
|
59
63
|
var state = expansionBySurface[surface];
|
|
60
64
|
if (state.has(key)) return state.get(key);
|
|
@@ -93,20 +97,25 @@ export function renderDesktopDriverHierarchy(root, renderSession, rerender, matc
|
|
|
93
97
|
control.setAttribute("aria-controls", childrenId);
|
|
94
98
|
control.setAttribute("aria-label", (isExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
|
|
95
99
|
control.innerHTML = iconHtml("chevron-right");
|
|
100
|
+
var children = document.createElement("div");
|
|
101
|
+
children.id = childrenId;
|
|
102
|
+
children.className = "session-worker-children";
|
|
103
|
+
children.setAttribute("role", "group");
|
|
104
|
+
children.setAttribute("aria-label", "Workers for " + (root.driver.title || "New Session"));
|
|
105
|
+
children.hidden = !isExpanded;
|
|
96
106
|
control.addEventListener("click", function (event) {
|
|
97
107
|
event.preventDefault();
|
|
98
108
|
event.stopPropagation();
|
|
99
|
-
|
|
109
|
+
var nextExpanded = !isExpanded;
|
|
110
|
+
expansionBySurface.desktop.set(key, nextExpanded);
|
|
111
|
+
control.setAttribute("aria-expanded", String(nextExpanded));
|
|
112
|
+
control.setAttribute("aria-label", (nextExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
|
|
113
|
+
children.hidden = !nextExpanded;
|
|
114
|
+
isExpanded = nextExpanded;
|
|
100
115
|
});
|
|
101
116
|
row.insertBefore(control, row.firstChild);
|
|
102
117
|
wrapper.appendChild(row);
|
|
103
118
|
|
|
104
|
-
var children = document.createElement("div");
|
|
105
|
-
children.id = childrenId;
|
|
106
|
-
children.className = "session-worker-children";
|
|
107
|
-
children.setAttribute("role", "group");
|
|
108
|
-
children.setAttribute("aria-label", "Workers for " + (root.driver.title || "New Session"));
|
|
109
|
-
children.hidden = !isExpanded;
|
|
110
119
|
var current = currentWorkerIds();
|
|
111
120
|
for (var i = 0; i < root.workers.length; i++) {
|
|
112
121
|
if (!visibleWorker(root.workers[i], root.driver, matchIds)) continue;
|
|
@@ -18,6 +18,7 @@ import { groupedSessionIds } from './split-group-helpers.js';
|
|
|
18
18
|
import { openPairDialog } from './split-pair-ui.js';
|
|
19
19
|
import {
|
|
20
20
|
hierarchyItemMatches,
|
|
21
|
+
isDesktopHierarchyToggleEvent,
|
|
21
22
|
prepareSidebarHierarchy,
|
|
22
23
|
renderDesktopDriverHierarchy,
|
|
23
24
|
renderDesktopOrphanHierarchy
|
|
@@ -1333,7 +1334,8 @@ function renderSessionItem(s, options) {
|
|
|
1333
1334
|
appendSessionCloseButton(el, s);
|
|
1334
1335
|
|
|
1335
1336
|
el.addEventListener("click", (function (id) {
|
|
1336
|
-
return function () {
|
|
1337
|
+
return function (event) {
|
|
1338
|
+
if (isDesktopHierarchyToggleEvent(event)) return;
|
|
1337
1339
|
if (getWs() && store.get('connected')) {
|
|
1338
1340
|
var pendingQuery = searchQuery || "";
|
|
1339
1341
|
getWs().send(JSON.stringify({ type: "switch_session", id: id }));
|
|
@@ -58,6 +58,11 @@ function nodeToMarkdown(node) {
|
|
|
58
58
|
return "\n" + inner;
|
|
59
59
|
case "P": return "\n" + inner;
|
|
60
60
|
case "A": return node.getAttribute("href") || inner;
|
|
61
|
+
case "BUTTON":
|
|
62
|
+
if (node.classList.contains("clayos-log-link") && node.dataset && node.dataset.logRef) {
|
|
63
|
+
return "`" + node.dataset.logRef + "`";
|
|
64
|
+
}
|
|
65
|
+
return inner;
|
|
61
66
|
case "SPAN":
|
|
62
67
|
if (node.classList.contains("sn-check")) {
|
|
63
68
|
return node.classList.contains("checked") ? "- [x]" : "- [ ]";
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { store } from './store.js';
|
|
12
12
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
13
13
|
import { renderMiniMarkdown, getTitle } from './sticky-note-markdown.js';
|
|
14
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
14
15
|
import { send } from './sticky-notes-shared.js';
|
|
15
16
|
import { listNoteData, focusNoteOnCanvas, hideNotes, showNotes } from './sticky-notes.js';
|
|
16
17
|
|
|
@@ -205,6 +206,7 @@ function buildCard(data) {
|
|
|
205
206
|
var bodyLines = (data.text || "").split("\n").slice(1).join("\n").trim();
|
|
206
207
|
if (bodyLines) {
|
|
207
208
|
body.innerHTML = renderMiniMarkdown("_\n" + bodyLines).replace('<div class="sn-title">_</div>', "");
|
|
209
|
+
enhanceClayLogLinks(body);
|
|
208
210
|
}
|
|
209
211
|
card.appendChild(body);
|
|
210
212
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
9
9
|
import { VENDOR_AVATARS } from './app-rendering.js';
|
|
10
10
|
import { getTitle, renderMiniMarkdown } from './sticky-note-markdown.js';
|
|
11
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
11
12
|
import {
|
|
12
13
|
NOTE_COLORS,
|
|
13
14
|
isClosedNote,
|
|
@@ -139,6 +140,7 @@ export function renderNote(data) {
|
|
|
139
140
|
rendered.spellcheck = true;
|
|
140
141
|
if (data.text && data.text.trim()) {
|
|
141
142
|
rendered.innerHTML = renderMiniMarkdown(data.text);
|
|
143
|
+
enhanceClayLogLinks(rendered);
|
|
142
144
|
} else {
|
|
143
145
|
rendered.classList.add("is-empty");
|
|
144
146
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
9
9
|
import { extractMarkdown, renderMiniMarkdown } from './sticky-note-markdown.js';
|
|
10
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
10
11
|
import { debouncedTextUpdate, syncTitle } from './sticky-notes-shared.js';
|
|
11
12
|
|
|
12
13
|
var formatToolbarEl = null;
|
|
@@ -183,6 +184,7 @@ export function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
183
184
|
syncTitle(noteEl, md);
|
|
184
185
|
if (md.trim()) {
|
|
185
186
|
rendered.innerHTML = renderMiniMarkdown(md);
|
|
187
|
+
enhanceClayLogLinks(rendered);
|
|
186
188
|
rendered.classList.remove("is-empty");
|
|
187
189
|
} else {
|
|
188
190
|
rendered.innerHTML = "";
|
|
@@ -217,6 +219,7 @@ export function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
217
219
|
textarea.value = md;
|
|
218
220
|
if (md.trim()) {
|
|
219
221
|
rendered.innerHTML = renderMiniMarkdown(md);
|
|
222
|
+
enhanceClayLogLinks(rendered);
|
|
220
223
|
rendered.classList.remove("is-empty");
|
|
221
224
|
} else {
|
|
222
225
|
rendered.innerHTML = "";
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
9
9
|
import { renderMiniMarkdown } from './sticky-note-markdown.js';
|
|
10
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
10
11
|
import { isClosedNote, send, clampPos, clearNoteTimers, syncTitle } from './sticky-notes-shared.js';
|
|
11
12
|
import { renderNote, closeColorPicker } from './sticky-notes-card.js';
|
|
12
13
|
import { closeFormatToolbar, isFormatToolbarOpen } from './sticky-notes-editor.js';
|
|
@@ -240,6 +241,7 @@ function patchNote(entry, next) {
|
|
|
240
241
|
if (textarea) textarea.value = nextText;
|
|
241
242
|
if (nextText.trim()) {
|
|
242
243
|
rendered.innerHTML = renderMiniMarkdown(nextText);
|
|
244
|
+
enhanceClayLogLinks(rendered);
|
|
243
245
|
rendered.classList.remove("is-empty");
|
|
244
246
|
} else {
|
|
245
247
|
rendered.innerHTML = "";
|
package/lib/public/style.css
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
@import url("css/session-search.css");
|
|
42
42
|
@import url("css/tooltip.css");
|
|
43
43
|
@import url("css/mates.css?v=20260407");
|
|
44
|
-
@import url("css/command-palette.css?v=
|
|
44
|
+
@import url("css/command-palette.css?v=20260908-log-links2");
|
|
45
45
|
@import url("css/mention.css");
|
|
46
46
|
@import url("css/debate.css");
|
|
47
47
|
@import url("css/home-debate-planning.css");
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -1623,6 +1623,21 @@ function createSDKBridge(opts) {
|
|
|
1623
1623
|
if (selectedUserInputMode === "fallback") {
|
|
1624
1624
|
sessionToolDefs = sessionToolDefs.concat(yoke.userInput.fallbackToolDefs(sessionUserInputHandler));
|
|
1625
1625
|
}
|
|
1626
|
+
function findSessionToolDef(toolName) {
|
|
1627
|
+
// Codex restores a thread's original dynamic-tool catalog on resume, but
|
|
1628
|
+
// pair state can change after this query starts. Resolve live session
|
|
1629
|
+
// handlers first so the dispatcher cannot lag behind that catalog. Keep
|
|
1630
|
+
// the query snapshot as a fallback for query-bound tools such as the
|
|
1631
|
+
// structured-input fallback, whose handler exists only for this turn.
|
|
1632
|
+
var liveToolDefs = getSessionToolDefs(session) || [];
|
|
1633
|
+
for (var liveIndex = 0; liveIndex < liveToolDefs.length; liveIndex++) {
|
|
1634
|
+
if (liveToolDefs[liveIndex].name === toolName) return liveToolDefs[liveIndex];
|
|
1635
|
+
}
|
|
1636
|
+
for (var snapshotIndex = 0; snapshotIndex < sessionToolDefs.length; snapshotIndex++) {
|
|
1637
|
+
if (sessionToolDefs[snapshotIndex].name === toolName) return sessionToolDefs[snapshotIndex];
|
|
1638
|
+
}
|
|
1639
|
+
return null;
|
|
1640
|
+
}
|
|
1626
1641
|
|
|
1627
1642
|
// Give fresh queries an immediate provisional label. The visible user
|
|
1628
1643
|
// message in history wins over `text`, which may contain an internal
|
|
@@ -1663,10 +1678,9 @@ function createSDKBridge(opts) {
|
|
|
1663
1678
|
toolServerDescriptors: extractMcpDescriptors(mergedMcpServers) || undefined,
|
|
1664
1679
|
dynamicTools: sessionToolDefs,
|
|
1665
1680
|
callDynamicTool: function (toolName, args) {
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
}
|
|
1681
|
+
var toolDef = findSessionToolDef(toolName);
|
|
1682
|
+
if (toolDef && typeof toolDef.handler === "function") {
|
|
1683
|
+
return toolDef.handler(args || {});
|
|
1670
1684
|
}
|
|
1671
1685
|
return Promise.reject(new Error("Session tool not found: " + toolName));
|
|
1672
1686
|
},
|
|
@@ -1674,12 +1688,8 @@ function createSDKBridge(opts) {
|
|
|
1674
1688
|
abortController: linuxUser ? undefined : session.abortController,
|
|
1675
1689
|
canUseTool: function(toolName, input, toolOpts) {
|
|
1676
1690
|
var permissionName = toolName;
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
permissionName = sessionToolDefs[pti].permissionName;
|
|
1680
|
-
break;
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1691
|
+
var permissionToolDef = findSessionToolDef(toolName);
|
|
1692
|
+
if (permissionToolDef && permissionToolDef.permissionName) permissionName = permissionToolDef.permissionName;
|
|
1683
1693
|
return handleCanUseTool(session, permissionName, input, toolOpts);
|
|
1684
1694
|
},
|
|
1685
1695
|
onUserInputRequest: function(request, respond) {
|
package/lib/server-global-ws.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// Bound to the slug-less `/ws` endpoint by lib/server.js. Only handles the
|
|
8
8
|
// small set of messages needed to bootstrap into a project context:
|
|
9
9
|
// - ping -> pong keep-alive
|
|
10
|
+
// - home_* -> user-scoped Home preferences
|
|
10
11
|
// - browse_dir -> directory picker for the add-project modal
|
|
11
12
|
// - add_project -> register an existing directory
|
|
12
13
|
// - create_project -> make a new empty project
|
|
@@ -37,6 +38,7 @@ function attachGlobalWs(opts) {
|
|
|
37
38
|
var onAddProject = opts.onAddProject;
|
|
38
39
|
var onCreateProject = opts.onCreateProject;
|
|
39
40
|
var onCloneProject = opts.onCloneProject;
|
|
41
|
+
var onHomePreferenceMessage = opts.onHomePreferenceMessage;
|
|
40
42
|
|
|
41
43
|
function handleMessage(ws, msg) {
|
|
42
44
|
if (!msg || typeof msg !== "object") return;
|
|
@@ -46,6 +48,8 @@ function attachGlobalWs(opts) {
|
|
|
46
48
|
return;
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
if (typeof onHomePreferenceMessage === "function" && onHomePreferenceMessage(ws, msg)) return;
|
|
52
|
+
|
|
49
53
|
// --- Directory picker for the add-project modal ---
|
|
50
54
|
if (msg.type === "browse_dir") {
|
|
51
55
|
var rawPath = (msg.path || "").replace(/^~/, config.REAL_HOME);
|
|
@@ -228,6 +228,9 @@ function searchToolLabel(name, input) {
|
|
|
228
228
|
if (name === "list_project_sessions") return "Reviewing project conversations";
|
|
229
229
|
if (name === "list_projects") return "Reviewing your projects";
|
|
230
230
|
if (name === "search_project_history") return query ? "Searching a project for \u201c" + query + "\u201d" : "Searching a project";
|
|
231
|
+
if (name === "search_project_logs") return query ? "Searching project logs for \u201c" + query + "\u201d" : "Searching project logs";
|
|
232
|
+
if (name === "list_project_logs") return "Reviewing project logs";
|
|
233
|
+
if (name === "read_project_log" || name === "read_project_log_revision" || name === "project_log_history") return "Reading a project log";
|
|
231
234
|
return "Using " + (cleanActivityText(name, 64) || "a workspace tool");
|
|
232
235
|
}
|
|
233
236
|
|
package/lib/server-home-chat.js
CHANGED
|
@@ -204,7 +204,12 @@ function attachHomeChat(deps) {
|
|
|
204
204
|
|
|
205
205
|
var homeModels = attachHomeModels({ users: users, mates: mates, projects: projects, sendMessage: sendMessage });
|
|
206
206
|
var homeDebates = attachHomeDebates({ mates: mates, findMateProject: findMateProject, ownsSession: ownsSession, sessionReference: sessionReference, sendMessage: sendMessage });
|
|
207
|
-
var homeClaySessionLinks = attachHomeClaySessionLinks({
|
|
207
|
+
var homeClaySessionLinks = attachHomeClaySessionLinks({
|
|
208
|
+
projects: projects,
|
|
209
|
+
workspaceQueryService: deps.workspaceQueryService,
|
|
210
|
+
projectLogsService: deps.projectLogsService,
|
|
211
|
+
sendMessage: sendMessage,
|
|
212
|
+
});
|
|
208
213
|
|
|
209
214
|
function sendHistory(ws, found, session, requestId) {
|
|
210
215
|
if (ws.readyState !== 1) return;
|
|
@@ -383,7 +388,7 @@ function attachHomeChat(deps) {
|
|
|
383
388
|
|
|
384
389
|
function handleMessage(ws, msg) {
|
|
385
390
|
if (!msg || typeof msg.type !== "string") return false;
|
|
386
|
-
if (msg.type !== "home_debates_list" && msg.type !== "home_clay_ask" && msg.type !== "home_clay_session_resolve" && msg.type !== "home_mate_present" && msg.type !== "home_mate_open" && msg.type !== "home_mate_sessions_list" && msg.type !== "home_mate_session_open" && msg.type !== "home_mate_send" && msg.type !== "home_mate_new_session" && msg.type !== "home_mate_debate_plan" && msg.type !== "home_debate_proposal_response" && msg.type !== "home_debate_question_response" && msg.type !== "home_debate_control" && msg.type !== "home_mate_creation_plan" && msg.type !== "home_mate_creation_question_response" && msg.type !== "home_mate_creation_proposal_response" && msg.type !== "home_mate_close" && msg.type !== "home_mate_memory_list" && msg.type !== "home_mate_knowledge_list" && msg.type !== "home_mate_models_get" && msg.type !== "home_mate_model_set") {
|
|
391
|
+
if (msg.type !== "home_debates_list" && msg.type !== "home_clay_ask" && msg.type !== "home_clay_session_resolve" && msg.type !== "home_clay_log_resolve" && msg.type !== "home_mate_present" && msg.type !== "home_mate_open" && msg.type !== "home_mate_sessions_list" && msg.type !== "home_mate_session_open" && msg.type !== "home_mate_send" && msg.type !== "home_mate_new_session" && msg.type !== "home_mate_debate_plan" && msg.type !== "home_debate_proposal_response" && msg.type !== "home_debate_question_response" && msg.type !== "home_debate_control" && msg.type !== "home_mate_creation_plan" && msg.type !== "home_mate_creation_question_response" && msg.type !== "home_mate_creation_proposal_response" && msg.type !== "home_mate_close" && msg.type !== "home_mate_memory_list" && msg.type !== "home_mate_knowledge_list" && msg.type !== "home_mate_models_get" && msg.type !== "home_mate_model_set") {
|
|
387
392
|
return false;
|
|
388
393
|
}
|
|
389
394
|
|
|
@@ -402,6 +407,7 @@ function attachHomeChat(deps) {
|
|
|
402
407
|
if (users.isMultiUser() && !userId) {
|
|
403
408
|
if (msg.type === "home_debates_list") sendMessage(ws, { type: "home_debates_state", requestId: msg.requestId || null, status: "error", debates: [], error: "Sign in to load your debates." });
|
|
404
409
|
else if (msg.type === "home_clay_session_resolve") sendMessage(ws, { type: "home_clay_session_target", requestId: msg.requestId || null, sessionRef: msg.sessionRef || null, status: "error", error: "Sign in to open this session." });
|
|
410
|
+
else if (msg.type === "home_clay_log_resolve") sendMessage(ws, { type: "home_clay_log_target", requestId: msg.requestId || null, ref: msg.ref || null, status: "error", error: "Sign in to open this log." });
|
|
405
411
|
else if (msg.type === "home_mate_models_get" || msg.type === "home_mate_model_set") homeModels.sendAccessError(ws, msg, "Not authenticated.");
|
|
406
412
|
else sendError(ws, msg.mateId || null, "Not authenticated.", msg.requestId || null, msg.sessionId || null);
|
|
407
413
|
return true;
|
|
@@ -410,6 +416,7 @@ function attachHomeChat(deps) {
|
|
|
410
416
|
if (homeDebates.handle(ws, userId, msg)) return true;
|
|
411
417
|
if (msg.type === "home_clay_ask") { homeClayEntry.start(ws, userId, msg); return true; }
|
|
412
418
|
if (msg.type === "home_clay_session_resolve") { homeClaySessionLinks.resolve(ws, msg); return true; }
|
|
419
|
+
if (msg.type === "home_clay_log_resolve") { homeClaySessionLinks.resolveLog(ws, msg); return true; }
|
|
413
420
|
|
|
414
421
|
if (msg.type === "home_mate_debate_plan") {
|
|
415
422
|
homeDebate.start(ws, userId, msg);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
function attachHomeClayEntry(ctx) {
|
|
4
4
|
function providerPrompt(text) {
|
|
5
|
-
return "This conversation began in Clay Studio's global search. Respond as Clay. When the user is trying to locate
|
|
5
|
+
return "This conversation began in Clay Studio's global search. Respond as Clay. When the user is trying to locate a conversation, use search_workspace_history before any other investigation. When the user asks about a project's prior work, decisions, defects, status, rationale, or unfinished work, identify the project and search_project_logs before answering; use conversation history as additional evidence when useful. Search by meaning, related terms, and context, using at most three focused search passes. Do not use Bash, filesystem scans, or generic agents to search Clay history. Give the user useful findings promptly, and do not mention tools or internal search mechanics. Do not force a workspace or Logs search when the request is a general question or task.\n\nUser request:\n" + text;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
function findExisting(manager, userId, requestId) {
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
// Resolve rendered Mate session references through an exact
|
|
1
|
+
// Resolve rendered Mate session and Project Log references through an exact
|
|
2
|
+
// owner-bound source session.
|
|
2
3
|
|
|
3
4
|
function attachHomeClaySessionLinks(ctx) {
|
|
4
|
-
function
|
|
5
|
+
function source(ws, msg) {
|
|
5
6
|
var tap = msg.surface === "search" ? ws._searchClayTap : ws._homeChatTap;
|
|
6
7
|
var sourceProject = tap && tap.mateSlug ? ctx.projects.get(tap.mateSlug) : null;
|
|
7
8
|
var sourceManager = sourceProject && sourceProject.getSessionManager ? sourceProject.getSessionManager() : null;
|
|
8
9
|
var sourceSession = sourceManager && tap ? sourceManager.sessions.get(tap.sessionId) : null;
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
return { tap: tap, project: sourceProject, session: sourceSession };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function resolve(ws, msg) {
|
|
14
|
+
var selected = source(ws, msg);
|
|
15
|
+
var bound = ctx.workspaceQueryService && selected.session
|
|
16
|
+
? ctx.workspaceQueryService.bindProjectSession({ projectSlug: selected.tap.mateSlug, session: selected.session })
|
|
11
17
|
: null;
|
|
12
18
|
var payload = { type: "home_clay_session_target", requestId: msg.requestId || null, sessionRef: msg.sessionRef || null, status: "error" };
|
|
13
19
|
try {
|
|
@@ -20,7 +26,30 @@ function attachHomeClaySessionLinks(ctx) {
|
|
|
20
26
|
ctx.sendMessage(ws, payload);
|
|
21
27
|
}
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
function resolveLog(ws, msg) {
|
|
30
|
+
var selected = source(ws, msg);
|
|
31
|
+
var status = selected.project && selected.project.getStatus ? selected.project.getStatus() : null;
|
|
32
|
+
var bound = ctx.projectLogsService && selected.session && status
|
|
33
|
+
? ctx.projectLogsService.bindMate({
|
|
34
|
+
projectSlug: selected.tap.mateSlug,
|
|
35
|
+
projectOwnerId: status.projectOwnerId || null,
|
|
36
|
+
isMate: true,
|
|
37
|
+
mateId: selected.tap.mateId,
|
|
38
|
+
session: selected.session,
|
|
39
|
+
})
|
|
40
|
+
: null;
|
|
41
|
+
var payload = { type: "home_clay_log_target", requestId: msg.requestId || null, ref: msg.ref || null, status: "error" };
|
|
42
|
+
try {
|
|
43
|
+
if (!bound) throw new Error("The source conversation is no longer available.");
|
|
44
|
+
payload.target = bound.resolveLogNavigation({ ref: msg.ref });
|
|
45
|
+
payload.status = "ready";
|
|
46
|
+
} catch (error) {
|
|
47
|
+
payload.error = error.message || "Log not available.";
|
|
48
|
+
}
|
|
49
|
+
ctx.sendMessage(ws, payload);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { resolve: resolve, resolveLog: resolveLog };
|
|
24
53
|
}
|
|
25
54
|
|
|
26
55
|
module.exports = { attachHomeClaySessionLinks: attachHomeClaySessionLinks };
|
package/lib/server.js
CHANGED
|
@@ -996,17 +996,21 @@ function createServer(opts) {
|
|
|
996
996
|
|
|
997
997
|
// --- WebSocket ---
|
|
998
998
|
var wss = new WebSocketServer({ noServer: true });
|
|
999
|
+
var homePreferencesHandler = null;
|
|
999
1000
|
|
|
1000
1001
|
// Slug-less /ws handler: lets a user with no projects yet load the regular
|
|
1001
1002
|
// app shell and create/add/clone their first one. Only knows about the
|
|
1002
|
-
// small set of bootstrap messages
|
|
1003
|
-
//
|
|
1003
|
+
// small set of bootstrap messages, including the Home preferences required
|
|
1004
|
+
// to dismiss the workspace restoration screen.
|
|
1004
1005
|
var globalWs = serverGlobalWs.attachGlobalWs({
|
|
1005
1006
|
osUsers: osUsers,
|
|
1006
1007
|
usersModule: users,
|
|
1007
1008
|
onAddProject: onAddProject,
|
|
1008
1009
|
onCreateProject: onCreateProject,
|
|
1009
1010
|
onCloneProject: onCloneProject,
|
|
1011
|
+
onHomePreferenceMessage: function (ws, msg) {
|
|
1012
|
+
return homePreferencesHandler ? homePreferencesHandler.handleMessage(ws, msg) : false;
|
|
1013
|
+
},
|
|
1010
1014
|
});
|
|
1011
1015
|
|
|
1012
1016
|
server.on("upgrade", function (req, socket, head) {
|
|
@@ -1447,9 +1451,10 @@ function createServer(opts) {
|
|
|
1447
1451
|
projects: projects,
|
|
1448
1452
|
addProject: addProject,
|
|
1449
1453
|
workspaceQueryService: workspaceQueryService,
|
|
1454
|
+
projectLogsService: projectLogsService,
|
|
1450
1455
|
});
|
|
1451
1456
|
|
|
1452
|
-
|
|
1457
|
+
homePreferencesHandler = serverHomePreferences.attachHomePreferences({
|
|
1453
1458
|
users: users,
|
|
1454
1459
|
projects: projects,
|
|
1455
1460
|
});
|
package/lib/ws-schema.js
CHANGED
|
@@ -44,6 +44,8 @@ var schema = {
|
|
|
44
44
|
"project_assignment_response": { direction: "c2s", handler: "lib/workspace-assignment-service.js", description: "Approve or cancel an exact-session cross-project assignment proposal" },
|
|
45
45
|
"home_clay_session_resolve": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Resolve an opaque Mate session reference through the exact bound source session" },
|
|
46
46
|
"home_clay_session_target": { direction: "s2c", handler: "lib/public/modules/command-palette.js", description: "Return an owner-validated navigation target for a rendered Mate session reference" },
|
|
47
|
+
"home_clay_log_resolve": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Resolve an opaque Project Log reference through the exact bound Clay session" },
|
|
48
|
+
"home_clay_log_target": { direction: "s2c", handler: "lib/public/modules/command-palette.js", description: "Return an owner-validated project target for a rendered Log reference" },
|
|
47
49
|
|
|
48
50
|
"session_list": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full list of sessions for the sidebar" },
|
|
49
51
|
"session_switched": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Confirmation that active session changed" },
|
package/package.json
CHANGED