clay-server 4.1.0 → 4.2.0-beta.2

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.
Files changed (47) hide show
  1. package/lib/daemon-project-access.js +14 -0
  2. package/lib/daemon.js +7 -0
  3. package/lib/project-connection.js +5 -2
  4. package/lib/project-full-access.js +69 -56
  5. package/lib/project-issue-launch.js +3 -1
  6. package/lib/project-sessions.js +158 -32
  7. package/lib/project-user-message.js +5 -2
  8. package/lib/project.js +5 -0
  9. package/lib/public/app.js +7 -0
  10. package/lib/public/css/command-palette.css +51 -33
  11. package/lib/public/css/filebrowser.css +20 -0
  12. package/lib/public/css/issues.css +4 -4
  13. package/lib/public/css/messages.css +27 -0
  14. package/lib/public/css/overlays.css +60 -0
  15. package/lib/public/css/pane.css +21 -4
  16. package/lib/public/css/pending-message-queue.css +5 -0
  17. package/lib/public/index.html +18 -3
  18. package/lib/public/modules/app-connection.js +4 -0
  19. package/lib/public/modules/app-header.js +18 -25
  20. package/lib/public/modules/app-messages.js +24 -7
  21. package/lib/public/modules/app-panels.js +22 -11
  22. package/lib/public/modules/clay-file-links.js +7 -0
  23. package/lib/public/modules/clay-issue-links.js +1 -0
  24. package/lib/public/modules/clay-log-links.js +5 -0
  25. package/lib/public/modules/github-links.js +115 -0
  26. package/lib/public/modules/markdown.js +9 -2
  27. package/lib/public/modules/mcp-ui.js +154 -17
  28. package/lib/public/modules/pane-bridge.js +2 -0
  29. package/lib/public/modules/pane-file-bridge.js +69 -0
  30. package/lib/public/modules/pending-message-queue-model.js +6 -0
  31. package/lib/public/modules/pending-message-queue-recovery.js +73 -0
  32. package/lib/public/modules/pending-message-queue.js +67 -64
  33. package/lib/public/modules/permission-control.js +187 -0
  34. package/lib/public/modules/project-session-visibility-settings.js +138 -0
  35. package/lib/public/modules/project-settings.js +5 -0
  36. package/lib/public/modules/sidebar-projects.js +14 -3
  37. package/lib/public/modules/split-view.js +29 -29
  38. package/lib/sdk-bridge.js +95 -40
  39. package/lib/server.js +8 -1
  40. package/lib/session-permission-mode.js +42 -0
  41. package/lib/session-visibility.js +62 -1
  42. package/lib/sessions.js +16 -1
  43. package/lib/ws-schema.js +4 -0
  44. package/lib/yoke/adapters/claude-worker-policy.js +67 -0
  45. package/lib/yoke/adapters/claude-worker.js +47 -7
  46. package/lib/yoke/adapters/claude.js +114 -6
  47. package/package.json +1 -1
@@ -512,9 +512,45 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
512
512
  .search-clay-activity-item small { color: var(--text-dimmer); font-size: 10px; line-height: 1.35; }
513
513
  @keyframes searchClaySpin { to { transform: rotate(360deg); } }
514
514
 
515
- .clayos-session-link,
516
- .clayos-issue-link,
517
- .clayos-log-link {
515
+ .clayos-record-link {
516
+ display: inline-flex;
517
+ align-items: center;
518
+ gap: 6px;
519
+ min-height: 28px;
520
+ margin: 2px 1px;
521
+ padding: 3px 8px 3px 6px;
522
+ border: 1px solid color-mix(in srgb, var(--record-color, var(--accent)) 32%, var(--border));
523
+ border-radius: 8px;
524
+ background: color-mix(in srgb, var(--record-color, var(--accent)) 7%, var(--bg-alt));
525
+ color: var(--text);
526
+ font: inherit;
527
+ font-size: 11px;
528
+ line-height: 1;
529
+ vertical-align: middle;
530
+ cursor: pointer;
531
+ }
532
+ .clayos-record-link::before {
533
+ content: "\2197";
534
+ display: grid;
535
+ place-items: center;
536
+ width: 17px;
537
+ height: 17px;
538
+ border-radius: 5px;
539
+ background: color-mix(in srgb, var(--record-color, var(--accent)) 14%, transparent);
540
+ color: var(--record-color, var(--accent));
541
+ font-size: 12px;
542
+ }
543
+ .clayos-record-link > span { font-weight: 620; }
544
+ .clayos-record-link > small { min-width: 0; max-width: 28em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-inline-start: 1px solid color-mix(in srgb, currentColor 22%, transparent); padding-inline-start: 6px; color: var(--text-muted); font-size: 11px; }
545
+ .clayos-record-link:hover { border-color: color-mix(in srgb, var(--record-color, var(--accent)) 58%, var(--border)); background: color-mix(in srgb, var(--record-color, var(--accent)) 11%, var(--bg-alt)); }
546
+ .clayos-record-link:focus-visible { outline: 2px solid var(--record-color, var(--accent)); outline-offset: 2px; }
547
+ .clayos-record-link.is-loading { opacity: 0.62; cursor: progress; }
548
+ .clayos-record-link.is-loading::before { animation: searchClaySpin 1.2s linear infinite; }
549
+ .clayos-record-link.is-error { border-color: color-mix(in srgb, var(--warning) 48%, var(--border)); }
550
+ .clayos-issue-link { --record-color: var(--link, var(--accent)); }
551
+ .clayos-log-link { --record-color: var(--accent); }
552
+
553
+ .clayos-session-link {
518
554
  display: inline-flex;
519
555
  align-items: center;
520
556
  gap: 6px;
@@ -531,9 +567,7 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
531
567
  vertical-align: middle;
532
568
  cursor: pointer;
533
569
  }
534
- .clayos-session-link::before,
535
- .clayos-issue-link::before,
536
- .clayos-log-link::before {
570
+ .clayos-session-link::before {
537
571
  content: "\2197";
538
572
  display: grid;
539
573
  place-items: center;
@@ -544,29 +578,13 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
544
578
  color: var(--accent);
545
579
  font-size: 12px;
546
580
  }
547
- .clayos-issue-link::before,
548
- .clayos-log-link::before { content: "\2261"; }
549
- .clayos-session-link > span,
550
- .clayos-issue-link > span,
551
- .clayos-log-link > span { font-weight: 620; }
552
- .clayos-session-link > small,
553
- .clayos-issue-link > small,
554
- .clayos-log-link > small { color: var(--text-muted); font-size: 10px; }
555
- .clayos-session-link:hover,
556
- .clayos-issue-link:hover,
557
- .clayos-log-link:hover { border-color: color-mix(in srgb, var(--accent) 52%, var(--border)); background: color-mix(in srgb, var(--accent) 11%, var(--bg-alt)); }
558
- .clayos-session-link:focus-visible,
559
- .clayos-issue-link:focus-visible,
560
- .clayos-log-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
561
- .clayos-session-link.is-loading,
562
- .clayos-issue-link.is-loading,
563
- .clayos-log-link.is-loading { opacity: 0.62; cursor: progress; }
564
- .clayos-session-link.is-loading::before,
565
- .clayos-issue-link.is-loading::before,
566
- .clayos-log-link.is-loading::before { animation: searchClaySpin 1.2s linear infinite; }
567
- .clayos-session-link.is-error,
568
- .clayos-issue-link.is-error,
569
- .clayos-log-link.is-error { border-color: color-mix(in srgb, var(--warning) 48%, var(--border)); }
581
+ .clayos-session-link > span { font-weight: 620; }
582
+ .clayos-session-link > small { color: var(--text-muted); font-size: 10px; }
583
+ .clayos-session-link:hover { border-color: color-mix(in srgb, var(--accent) 52%, var(--border)); background: color-mix(in srgb, var(--accent) 11%, var(--bg-alt)); }
584
+ .clayos-session-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
585
+ .clayos-session-link.is-loading { opacity: 0.62; cursor: progress; }
586
+ .clayos-session-link.is-loading::before { animation: searchClaySpin 1.2s linear infinite; }
587
+ .clayos-session-link.is-error { border-color: color-mix(in srgb, var(--warning) 48%, var(--border)); }
570
588
 
571
589
  .search-clay-pending {
572
590
  align-self: flex-start;
@@ -925,11 +943,11 @@ body.mate-dm-active .search-clay-transcript .search-clay-message-user .bubble,
925
943
  }
926
944
 
927
945
  .clayos-issue-link { max-width: min(100%, 30em); box-sizing: border-box; }
946
+ .clayos-issue-link::before,
947
+ .clayos-log-link::before { content: "\2261"; }
928
948
  .clayos-issue-link::before { content: "\25CE"; flex: 0 0 17px; }
929
- .clayos-issue-link[data-status="in_progress"]::before { color: #bd923f; }
930
- .clayos-issue-link[data-status="resolved"]::before { content: "\2713"; color: var(--brand-green); }
949
+ .clayos-issue-link[data-status="in_progress"]::before { color: var(--warning, var(--accent)); }
950
+ .clayos-issue-link[data-status="resolved"]::before { content: "\2713"; color: var(--success, var(--accent)); }
931
951
  .clayos-issue-link[data-status="closed"]::before { content: "\00D7"; color: var(--text-dimmer); }
932
952
  .clayos-issue-link > span { flex-shrink: 0; }
933
- .clayos-issue-link > small { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
934
953
  .clayos-log-link { max-width: 100%; box-sizing: border-box; }
935
- .clayos-log-link > small { min-width: 0; overflow-wrap: anywhere; line-height: 1.35; }
@@ -686,6 +686,26 @@
686
686
  font-size: 12px;
687
687
  color: var(--text-muted);
688
688
  }
689
+ .mcp-server-perm-mode {
690
+ max-width: 118px;
691
+ padding: 5px 24px 5px 8px;
692
+ border: 1px solid var(--border);
693
+ border-radius: 6px;
694
+ background: var(--bg);
695
+ color: var(--text);
696
+ font-size: 12px;
697
+ }
698
+ .mcp-server-perm-mode:disabled {
699
+ cursor: wait;
700
+ opacity: 0.55;
701
+ }
702
+ .mcp-server-perm-reason {
703
+ max-width: 180px;
704
+ color: var(--text-muted);
705
+ font-size: 11px;
706
+ line-height: 1.3;
707
+ text-align: right;
708
+ }
689
709
 
690
710
  /* Beta toggle row inside settings card */
691
711
  .ps-beta-toggle-row,
@@ -46,11 +46,11 @@
46
46
  .issue-row small { font-size: 10.5px; font-family: var(--font-mono); }
47
47
  .issue-row-meta { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; margin-top: 8px; color: var(--text-dimmer); font: 10.5px/1.5 var(--font-mono); }
48
48
  .issue-row-meta .issue-status { font-family: var(--font-sans); }
49
- .issue-status { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; letter-spacing: .03em; }
49
+ .issue-status { display: inline-flex; align-items: center; gap: 6px; color: var(--text-secondary); font-size: 11px; letter-spacing: .03em; }
50
50
  .issue-status::before { content: ''; width: 7px; height: 7px; border: 1px solid currentColor; border-radius: 50%; }
51
- .issue-status[data-status="in_progress"] { color: #bd923f; }
52
- .issue-status[data-status="resolved"] { color: #479b77; }
53
- .issue-status[data-status="closed"] { color: var(--text-muted); }
51
+ .issue-status[data-status="in_progress"]::before { color: var(--warning, var(--accent)); }
52
+ .issue-status[data-status="resolved"]::before { color: var(--success, var(--accent)); }
53
+ .issue-status[data-status="closed"]::before { color: var(--text-muted); }
54
54
  .issue-detail { max-width: 760px; margin: 0 auto; padding: 32px 28px 52px; }
55
55
  .issue-doc-chips { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; margin-bottom: 10px; }
56
56
  .issue-detail .issue-doc-title { margin: 0 0 10px; font-family: var(--font-display); font-size: clamp(24px, 2.8vw, 34px); letter-spacing: -.025em; line-height: 1.12; overflow-wrap: anywhere; }
@@ -508,6 +508,33 @@ pre:hover .code-copy-btn { opacity: 1; }
508
508
  .clay-file-link-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
509
509
  .clay-file-link-notice { margin-top: 5px; color: var(--text-secondary); font-size: 12px; }
510
510
 
511
+ .hosted-link-chip, .github-link-chip {
512
+ display: inline-flex;
513
+ align-items: center;
514
+ gap: 5px;
515
+ max-width: 100%;
516
+ padding: 3px 8px;
517
+ border: 1px solid color-mix(in srgb, var(--text) 16%, var(--border));
518
+ border-radius: 999px;
519
+ background: color-mix(in srgb, var(--text) 8%, var(--bg));
520
+ color: var(--text);
521
+ font-size: 0.92em;
522
+ line-height: 1.35;
523
+ vertical-align: baseline;
524
+ text-decoration: none !important;
525
+ transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
526
+ }
527
+ .hosted-link-chip { color: var(--text); }
528
+ .md-content .github-link-chip { color: var(--text); }
529
+ .md-content .gitlab-link-chip { color: var(--text); }
530
+ .hosted-link-chip:hover, .github-link-chip:hover { background: color-mix(in srgb, var(--text) 14%, var(--bg-alt)); border-color: color-mix(in srgb, var(--text) 30%, var(--border)); }
531
+ .hosted-link-chip:active, .github-link-chip:active { transform: translateY(1px); }
532
+ .hosted-link-chip:focus-visible, .github-link-chip:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }
533
+ .github-link-icon { display: inline-flex; width: 15px; height: 15px; flex: 0 0 15px; }
534
+ .github-link-icon .lucide { width: 15px; height: 15px; }
535
+ .github-link-main { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
536
+ .github-link-kind { min-width: 0; max-width: 65%; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-muted); font-size: 0.88em; }
537
+
511
538
  .md-content ul, .md-content ol {
512
539
  padding-left: 24px;
513
540
  margin: 10px 0;
@@ -519,6 +519,66 @@ button.top-bar-pill.pill-accent:hover { background: color-mix(in srgb, var(--acc
519
519
  .popover-copy.copied { color: var(--success); border-color: var(--success); }
520
520
 
521
521
  /* Skip permissions pill */
522
+ .session-permission-control {
523
+ display: inline-flex;
524
+ align-items: center;
525
+ flex: 0 1 auto;
526
+ flex-wrap: wrap;
527
+ gap: 7px;
528
+ max-width: 100%;
529
+ min-width: 0;
530
+ }
531
+ .session-permission-control.hidden { display: none; }
532
+ .session-permission-segmented {
533
+ display: inline-flex;
534
+ align-items: center;
535
+ flex: 0 0 auto;
536
+ gap: 2px;
537
+ padding: 2px;
538
+ border: 1px solid var(--border, rgba(255,255,255,.18));
539
+ border-radius: 999px;
540
+ background: var(--surface-raised, rgba(0,0,0,.18));
541
+ }
542
+ .session-permission-segmented button {
543
+ flex: 0 0 auto;
544
+ border: 0;
545
+ border-radius: 999px;
546
+ padding: 3px 8px;
547
+ color: var(--text-muted, inherit);
548
+ background: transparent;
549
+ font: inherit;
550
+ font-size: 11px;
551
+ white-space: nowrap;
552
+ cursor: pointer;
553
+ }
554
+ .session-permission-segmented button.active { background: var(--accent, #6f8cff); color: #fff; }
555
+ .session-permission-segmented button:disabled { cursor: not-allowed; opacity: .5; }
556
+ .session-permission-control.permission-pending .session-permission-segmented button:disabled { cursor: wait; }
557
+ .session-permission-status {
558
+ flex: 1 1 118px;
559
+ min-width: 0;
560
+ max-width: 190px;
561
+ overflow-wrap: anywhere;
562
+ white-space: normal;
563
+ color: var(--text-dimmer, var(--text-muted));
564
+ font-size: 10px;
565
+ line-height: 1.2;
566
+ }
567
+ .session-permission-status.hidden { display: none; }
568
+ .permission-runtime-fallback .session-permission-status { color: var(--warning, #d49a55); }
569
+ .permission-globally-forced .session-permission-status { color: var(--warning, #d49a55); }
570
+ .permission-label-short { display: none; }
571
+ @media (max-width: 760px) {
572
+ .permission-label-long { display: none; }
573
+ .permission-label-short { display: inline; }
574
+ .session-permission-control { column-gap: 5px; row-gap: 2px; }
575
+ .session-permission-segmented button { padding-inline: 7px; }
576
+ .session-permission-status {
577
+ flex-basis: 96px;
578
+ max-width: 170px;
579
+ font-size: 9px;
580
+ }
581
+ }
522
582
  #skip-perms-pill .lucide { width: 12px; height: 12px; flex-shrink: 0; }
523
583
 
524
584
  /* --- Confirm modal --- */
@@ -370,13 +370,31 @@ body.pane-mode #input-area {
370
370
  tie and keep the pill at its 22px title-bar size inside the 24px pane
371
371
  header. Scope under .split-pane-header to outrank them. */
372
372
  .split-pane-header .split-pane-full-access {
373
- height: 15px;
373
+ height: auto;
374
374
  align-self: center;
375
375
  margin-left: 0;
376
- padding: 0 3px 0 7px;
377
- gap: 5px;
376
+ padding: 0;
377
+ gap: 4px;
378
378
  font-size: 8px;
379
379
  letter-spacing: 0.02em;
380
+ flex-wrap: nowrap;
381
+ flex: 0 0 auto;
382
+ overflow: hidden;
383
+ }
384
+
385
+ .split-pane-header .session-permission-segmented button {
386
+ padding: 1px 5px;
387
+ font-size: 8px;
388
+ }
389
+
390
+ .split-pane-header .session-permission-status {
391
+ flex: 0 0 auto;
392
+ min-width: 40px;
393
+ max-width: 72px;
394
+ overflow: hidden;
395
+ text-overflow: ellipsis;
396
+ white-space: nowrap;
397
+ font-size: 8px;
380
398
  }
381
399
 
382
400
  .split-pane-header .session-full-access-track {
@@ -395,7 +413,6 @@ body.pane-mode #input-area {
395
413
 
396
414
  @media (max-width: 900px) {
397
415
  .split-pane-header .session-full-access-label { display: none; }
398
- .split-pane-header .split-pane-full-access { padding-left: 3px; }
399
416
  }
400
417
 
401
418
  .split-pane-close {
@@ -167,6 +167,11 @@ button.pending-message-drag:active { cursor: grabbing; }
167
167
  .pending-message-banner-error { color: var(--error); font-size: 10px; line-height: 1.35; }
168
168
  .pending-message-error { margin-top: 4px; }
169
169
  .pending-message-banner-error { padding: 0 8px 6px; }
170
+ .pending-message-restoring { padding: 0 8px 6px; color: var(--text-dimmer); font-size: 10px; line-height: 1.35; }
171
+
172
+ .pending-message-drag:disabled,
173
+ .pending-message-actions button:disabled,
174
+ .pending-message-resume:disabled { cursor: not-allowed; opacity: .48; }
170
175
 
171
176
  .pending-message-drag:focus-visible,
172
177
  .pending-message-actions button:focus-visible,
@@ -408,9 +408,14 @@
408
408
  <span class="header-title" id="header-title">Connecting...</span>
409
409
  <button id="header-rename-btn" type="button" title="Rename session"><i data-lucide="pencil"></i></button>
410
410
  <button id="header-info-btn" type="button" title="Session info"><i data-lucide="info"></i></button>
411
- <button id="header-full-access-btn" type="button" class="session-full-access hidden" role="switch" aria-checked="false" title="Skip Permissions is off — permission prompts are on">
412
- <span class="session-full-access-label">Skip Permissions</span><span class="session-full-access-track" aria-hidden="true"><span></span></span>
413
- </button>
411
+ <div id="header-full-access-btn" class="session-permission-control hidden" role="group" aria-label="Session permission mode">
412
+ <div class="session-permission-segmented">
413
+ <button type="button" data-permission-mode="default">Ask</button>
414
+ <button type="button" data-permission-mode="auto">Auto</button>
415
+ <button type="button" data-permission-mode="bypassPermissions" aria-label="Skip permissions"><span class="permission-label-long">Skip permissions</span><span class="permission-label-short" aria-hidden="true">Skip</span></button>
416
+ </div>
417
+ <span class="session-permission-status hidden" aria-live="polite"></span>
418
+ </div>
414
419
  <button type="button" id="header-tui-close-btn" class="header-tui-close-btn hidden" title="Close terminal (keeps history, resume anytime)" aria-label="Close terminal"><i data-lucide="power"></i></button>
415
420
  <div id="mate-mobile-title" class="mate-mobile-title hidden">
416
421
  <img id="mate-mobile-avatar" class="mate-mobile-avatar" alt="">
@@ -830,6 +835,16 @@
830
835
  </div>
831
836
  </div>
832
837
  </div>
838
+ <div class="settings-field" id="ps-session-visibility-default-field" style="display:none">
839
+ <label class="settings-label" for="ps-session-visibility-default">New session visibility</label>
840
+ <div class="settings-hint">Applies to new project conversations and Issue Start work sessions. Existing sessions, hidden work, and delegated workflows keep their current visibility.</div>
841
+ <div class="settings-hint hidden" id="ps-session-visibility-default-inherited-hint">This worktree inherits the setting from its parent project. Change it there.</div>
842
+ <div class="settings-hint hidden" id="ps-session-visibility-default-non-current-hint">Open this project to change this setting.</div>
843
+ <select class="ps-select" id="ps-session-visibility-default">
844
+ <option value="private">Private</option>
845
+ <option value="shared">Shared with project members</option>
846
+ </select>
847
+ </div>
833
848
  </div>
834
849
  </div>
835
850
  <!-- Defaults -->
@@ -20,6 +20,8 @@ import { beginDefaultAiConnection, requestDefaultAi } from './default-ai.js';
20
20
  import { beginContextViewConnection, requestContextView } from './context-view-preference.js';
21
21
  import { beginDefaultVendorConnection, requestDefaultVendor } from './default-vendor.js';
22
22
  import { clearProjectSplitState } from './split-session-boundary.js';
23
+ import { clearPermissionModePending } from './permission-control.js';
24
+ import { clearMcpPermissionModePending } from './mcp-ui.js';
23
25
 
24
26
  var reconnectTimer = null;
25
27
  var reconnectDelay = 1000;
@@ -165,6 +167,8 @@ export function setStatus(status) {
165
167
  } else if (status === "processing") {
166
168
  store.set({ processing: true });
167
169
  } else {
170
+ clearPermissionModePending();
171
+ clearMcpPermissionModePending();
168
172
  store.set({ connected: false, processing: false });
169
173
  }
170
174
  }
@@ -12,7 +12,7 @@ import { saveToolState, resetToolState, restoreToolState } from './tools.js';
12
12
  import { getSessionUsage, setSessionUsage, getContextData, setContextData, updateContextPanel, updateUsagePanel } from './app-panels.js';
13
13
  import { onHistoryPrepended as onSessionSearchHistoryPrepended } from './session-search.js';
14
14
  import { getCachedSessions } from './sidebar-sessions.js';
15
- import { showConfirm } from './app-misc.js';
15
+ import { bindPermissionControl, renderPermissionControl } from './permission-control.js';
16
16
 
17
17
  // --- Module-owned state ---
18
18
  var sessionInfoPopover = null;
@@ -24,37 +24,30 @@ export function initHeader() {
24
24
  var headerInfoBtn = document.getElementById("header-info-btn");
25
25
  var headerFullAccessBtn = document.getElementById("header-full-access-btn");
26
26
 
27
- function updateFullAccessButton() {
28
- if (!headerFullAccessBtn) return;
27
+ function permissionControlState() {
29
28
  var s = store.snap();
30
- var visible = !s.dmMode && !s.splitPanes && !s.skipPermsEnabled && s.activeSessionMode === "gui" && !!s.activeSessionId;
31
- var enabled = !!s.sessionFullAccess;
32
- headerFullAccessBtn.classList.toggle("hidden", !visible);
33
- headerFullAccessBtn.classList.toggle("active", enabled);
34
- headerFullAccessBtn.setAttribute("aria-checked", enabled ? "true" : "false");
35
- headerFullAccessBtn.title = enabled ? "Skip Permissions is on — click to restore permission prompts" : "Skip Permissions is off — permission prompts are on";
29
+ return {
30
+ projectSlug: s.currentSlug,
31
+ sessionId: s.activeSessionId,
32
+ vendor: s.currentVendor || "claude",
33
+ permissionMode: s.currentMode,
34
+ effectivePermissionMode: s.effectivePermissionMode,
35
+ permissionCapabilities: s.permissionCapabilities,
36
+ connected: s.connected,
37
+ globalPermissionModeForced: s.skipPermsEnabled === true,
38
+ visible: !s.dmMode && !s.splitPanes && s.activeSessionMode === "gui" && !!s.activeSessionId,
39
+ };
36
40
  }
37
41
 
38
- function sendFullAccess(enabled) {
39
- var ws = getWs();
40
- var id = store.get('activeSessionId');
41
- if (ws && ws.readyState === 1 && id) {
42
- ws.send(JSON.stringify({ type: "set_session_full_access", id: id, enabled: enabled }));
43
- }
42
+ function updateFullAccessButton() {
43
+ if (!headerFullAccessBtn) return;
44
+ renderPermissionControl(headerFullAccessBtn, permissionControlState());
44
45
  }
45
46
 
46
47
  if (headerFullAccessBtn) {
47
- headerFullAccessBtn.addEventListener("click", function () {
48
- if (store.get('sessionFullAccess')) {
49
- sendFullAccess(false);
50
- return;
51
- }
52
- showConfirm("Skip permission prompts for this session? Tool requests will be approved automatically.", function () {
53
- sendFullAccess(true);
54
- }, "Skip Permissions", true);
55
- });
48
+ bindPermissionControl(headerFullAccessBtn, permissionControlState);
56
49
  store.subscribe(function (state, prev) {
57
- if (state.sessionFullAccess !== prev.sessionFullAccess || state.activeSessionId !== prev.activeSessionId ||
50
+ if (state.currentMode !== prev.currentMode || state.effectivePermissionMode !== prev.effectivePermissionMode || state.permissionCapabilities !== prev.permissionCapabilities || state.permissionModePendingByTarget !== prev.permissionModePendingByTarget || state.connected !== prev.connected || state.activeSessionId !== prev.activeSessionId || state.currentSlug !== prev.currentSlug || state.currentVendor !== prev.currentVendor ||
58
51
  state.activeSessionMode !== prev.activeSessionMode ||
59
52
  state.dmMode !== prev.dmMode || state.splitPanes !== prev.splitPanes ||
60
53
  state.skipPermsEnabled !== prev.skipPermsEnabled) updateFullAccessButton();
@@ -38,6 +38,7 @@ import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handle
38
38
  import { beginMarkdownTurn, finishMarkdownTurn, markdownPathFromToolInput } from './markdown-live-edit.js';
39
39
  import { forwardPaneMarkdownPresentation } from './pane-bridge.js';
40
40
  import { isProjectSettingsOpen, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './project-settings.js';
41
+ import { handleProjectSessionVisibilityDefault, refreshProjectSessionVisibilitySettings } from './project-session-visibility-settings.js';
41
42
  import { updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleInheritGroupsChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './server-settings.js';
42
43
  import { handleTermList, handleTermCreated, sendTerminalCommand, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed } from './terminal.js';
43
44
  import { attachTuiView, detachTuiView, setTuiSuspendedView, tuiHandleTermOutput, tuiHandleTermResized, tuiHandleTermExited, tuiHandleTermClosed } from './session-tui-view.js';
@@ -53,7 +54,8 @@ import { showRewindModal, onRewindComplete, setRewindMode, onRewindError, clearP
53
54
  import { checkAdminAccess } from './admin.js';
54
55
  import { mateAvatarUrl } from './avatar.js';
55
56
  import { showImageModal, sendExtensionCommand, handleMcpToolCallMessage } from './app-misc.js';
56
- import { handleMcpServersState } from './mcp-ui.js';
57
+ import { handleMcpServersState, handleMcpPermissionModeResult } from './mcp-ui.js';
58
+ import { handlePermissionModeResult } from './permission-control.js';
57
59
  import { handleShellCommandResult } from './shell-command.js';
58
60
  import { handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, isSchedulerOpen, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles } from './scheduler.js';
59
61
  import { handleScheduledTaskMessage, handleScheduledTaskSessionSwitched } from './scheduled-tasks.js';
@@ -103,6 +105,10 @@ export function processMessage(msg) {
103
105
  if (handleScheduledTaskMessage(msg)) return;
104
106
  if (handleDefaultVendorMessage(msg)) return;
105
107
  if (handleContextViewMessage(msg)) return;
108
+ if (msg && msg.type === "set_project_session_visibility_default_result") {
109
+ handleProjectSessionVisibilityDefault(msg);
110
+ return;
111
+ }
106
112
  if (msg && msg.type === "schedule_message_result") {
107
113
  handleScheduleMessageResult(msg);
108
114
  return;
@@ -369,12 +375,11 @@ export function processMessage(msg) {
369
375
  if (msg.ownerLocked !== undefined) store.set({ ownerLocked: !!msg.ownerLocked });
370
376
  if (msg.osUsers !== undefined) store.set({ isOsUsers: !!msg.osUsers });
371
377
  if (msg.lanHost) window.__lanHost = msg.lanHost;
372
- if (msg.dangerouslySkipPermissions) {
373
- store.set({ skipPermsEnabled: true });
374
- var spBanner = document.getElementById("skip-perms-pill");
375
- if (spBanner) spBanner.classList.remove("hidden");
376
- }
378
+ store.set({ skipPermsEnabled: msg.dangerouslySkipPermissions === true });
379
+ var spBanner = document.getElementById("skip-perms-pill");
380
+ if (spBanner) spBanner.classList.toggle("hidden", msg.dangerouslySkipPermissions !== true);
377
381
  updateProjectList(msg);
382
+ refreshProjectSessionVisibilitySettings(msg.projects);
378
383
  break;
379
384
 
380
385
  case "update_available":
@@ -549,6 +554,9 @@ export function processMessage(msg) {
549
554
  }
550
555
  }
551
556
  if (msg.mode) _cs.currentMode = msg.mode;
557
+ if (Object.prototype.hasOwnProperty.call(msg, 'effectivePermissionMode')) _cs.effectivePermissionMode = msg.effectivePermissionMode;
558
+ if (msg.permissionCapabilities) _cs.permissionCapabilities = msg.permissionCapabilities;
559
+ if (msg.mcpPermissionModeOverrides) _cs.mcpPermissionModeOverrides = msg.mcpPermissionModeOverrides;
552
560
  if (msg.effort) _cs.currentEffort = msg.effort;
553
561
  if (msg.betas) _cs.currentBetas = msg.betas;
554
562
  if (msg.thinking) _cs.currentThinking = msg.thinking;
@@ -817,7 +825,7 @@ export function processMessage(msg) {
817
825
  var _effectiveTerminalId = (typeof msg.runtimeTerminalId === "number")
818
826
  ? msg.runtimeTerminalId
819
827
  : (typeof msg.terminalId === "number" ? msg.terminalId : null);
820
- store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, currentModel: msg.model || "", currentEffort: msg.effort || store.get('currentEffort'), vendorCapabilities: msg.capabilities || {}, sessionIsProcessing: !!msg.isProcessing, activeSessionMode: _effectiveMode, activeTerminalId: _effectiveTerminalId, sessionHasHistory: !!msg.hasHistory, sessionVendorBound: !!msg.vendor || !!msg.hasHistory, sessionFullAccess: msg.permissionMode === "bypassPermissions", currentMode: msg.permissionMode || store.get('currentMode') });
828
+ store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, currentModel: msg.model || "", currentEffort: msg.effort || store.get('currentEffort'), vendorCapabilities: msg.capabilities || {}, sessionIsProcessing: !!msg.isProcessing, activeSessionMode: _effectiveMode, activeTerminalId: _effectiveTerminalId, sessionHasHistory: !!msg.hasHistory, sessionVendorBound: !!msg.vendor || !!msg.hasHistory, sessionFullAccess: msg.permissionMode === "bypassPermissions", currentMode: msg.permissionMode || store.get('currentMode'), effectivePermissionMode: msg.effectivePermissionMode || null, permissionCapabilities: msg.permissionCapabilities || { auto: false, mcpOverride: false }, mcpPermissionModeOverrides: msg.mcpPermissionModeOverrides || {} });
821
829
  handleScheduledTaskSessionSwitched(msg);
822
830
  requestLoopInterviewState();
823
831
  replayPendingMessages(msg.id);
@@ -1592,6 +1600,14 @@ export function processMessage(msg) {
1592
1600
  handleMcpServersState(msg);
1593
1601
  break;
1594
1602
 
1603
+ case "mcp_permission_mode_result":
1604
+ handleMcpPermissionModeResult(msg);
1605
+ break;
1606
+
1607
+ case "permission_mode_result":
1608
+ handlePermissionModeResult(msg);
1609
+ break;
1610
+
1595
1611
  case "term_created":
1596
1612
  // Login terminals never come through here: they are created by the
1597
1613
  // server-owned flow and announced with vendor_login_ready instead.
@@ -1735,6 +1751,7 @@ export function processMessage(msg) {
1735
1751
 
1736
1752
  case "projects_updated":
1737
1753
  updateProjectList(msg);
1754
+ refreshProjectSessionVisibilitySettings(msg.projects);
1738
1755
  renderUserStrip();
1739
1756
  break;
1740
1757
 
@@ -8,6 +8,7 @@ import { getWs } from './ws-ref.js';
8
8
  import { getContextView as getStoredContextView, getEffectiveContextView, setContextView as setStoredContextView } from './context-view-preference.js';
9
9
  import { reportPaneContext } from './pane-bridge.js';
10
10
  import { setupModelPicker, renderModelPicker, requestVendorModels, prepareModelPickerOpen, modelDisplayName } from './model-picker.js';
11
+ import { sendPermissionMode } from './permission-control.js';
11
12
 
12
13
  // --- Module-owned state (not in store) ---
13
14
  var sessionUsage = { cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
@@ -85,11 +86,12 @@ var contextMiniLabel = null;
85
86
 
86
87
  // --- Constants ---
87
88
  var MODE_OPTIONS = [
88
- { value: "default", label: "Default" },
89
+ { value: "default", label: "Ask" },
89
90
  { value: "plan", label: "Plan" },
90
91
  { value: "acceptEdits", label: "Auto-accept edits" },
91
92
  ];
92
- var MODE_FULL_AUTO = { value: "bypassPermissions", label: "Full auto" };
93
+ var MODE_FULL_AUTO = { value: "auto", label: "Auto" };
94
+ var MODE_SKIP = { value: "bypassPermissions", label: "Skip permissions" };
93
95
  var EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
94
96
  var EFFORT_LEVELS_BY_VENDOR = {
95
97
  claude: ["low", "medium", "high", "xhigh", "max"],
@@ -142,7 +144,8 @@ function modeDisplayName(value) {
142
144
  for (var i = 0; i < MODE_OPTIONS.length; i++) {
143
145
  if (MODE_OPTIONS[i].value === value) return MODE_OPTIONS[i].label;
144
146
  }
145
- if (value === "bypassPermissions") return "Full auto";
147
+ if (value === "auto") return "Auto";
148
+ if (value === "bypassPermissions") return "Skip permissions";
146
149
  if (value === "dontAsk") return "Don\u2019t ask";
147
150
  return value;
148
151
  }
@@ -196,22 +199,29 @@ function rebuildModeList() {
196
199
  if (!configModeList) return;
197
200
  configModeList.innerHTML = "";
198
201
  var options = MODE_OPTIONS.slice();
199
- if (store.get('skipPermsEnabled')) {
200
- options.push(MODE_FULL_AUTO);
201
- }
202
+ options.push(MODE_FULL_AUTO);
203
+ options.push(MODE_SKIP);
204
+ var autoSupported = store.get('permissionCapabilities').auto === true;
205
+ var globallyForced = store.get('skipPermsEnabled') === true;
202
206
  for (var i = 0; i < options.length; i++) {
203
207
  var opt = options[i];
204
208
  var btn = document.createElement("button");
205
209
  btn.className = "config-radio-item";
206
- if (opt.value === store.get('currentMode')) btn.classList.add("active");
210
+ if (opt.value === (globallyForced ? "bypassPermissions" : store.get('currentMode'))) btn.classList.add("active");
207
211
  btn.dataset.mode = opt.value;
208
212
  btn.textContent = opt.label;
213
+ if (opt.value === "auto" && !autoSupported) {
214
+ btn.disabled = true;
215
+ btn.title = "Auto permissions require a Claude session.";
216
+ btn.setAttribute("aria-description", "Unavailable because this is not a Claude session.");
217
+ }
218
+ if (globallyForced) {
219
+ btn.disabled = true;
220
+ btn.title = "Skip permissions is forced by the server setting.";
221
+ }
209
222
  btn.addEventListener("click", function () {
210
223
  var mode = this.dataset.mode;
211
- var ws = getWs();
212
- if (ws && ws.readyState === 1) {
213
- ws.send(JSON.stringify({ type: "set_permission_mode", mode: mode }));
214
- }
224
+ sendPermissionMode(store.get('currentSlug'), store.get('activeSessionId'), mode);
215
225
  configPopover.classList.add("hidden");
216
226
  configChip.classList.remove("active");
217
227
  });
@@ -420,6 +430,7 @@ export function initPanels() {
420
430
  state.codexApproval !== prev.codexApproval ||
421
431
  state.codexSandbox !== prev.codexSandbox ||
422
432
  state.codexWebSearch !== prev.codexWebSearch ||
433
+ state.skipPermsEnabled !== prev.skipPermsEnabled ||
423
434
  state.sessionHasHistory !== prev.sessionHasHistory ||
424
435
  state.activeSessionMode !== prev.activeSessionMode) {
425
436
  updateConfigChip();
@@ -3,6 +3,7 @@
3
3
  import { escapeHtml } from './utils.js';
4
4
  import { refreshIcons } from './icons.js';
5
5
  import { store } from './store.js';
6
+ import { forwardPaneFileReference } from './pane-bridge.js';
6
7
 
7
8
  var openFileHandler = null;
8
9
  var delegatedBinding = false;
@@ -99,6 +100,12 @@ function bindDelegatedHandler() {
99
100
  document.addEventListener("click", function (event) {
100
101
  var button = event.target && event.target.closest ? event.target.closest(".clay-file-link") : null;
101
102
  if (!button) return;
103
+ if (store.get('paneMode') && forwardPaneFileReference(button.dataset.filePath, {
104
+ line: button.dataset.fileLine ? Number(button.dataset.fileLine) : null,
105
+ column: button.dataset.fileColumn ? Number(button.dataset.fileColumn) : null,
106
+ projectSlug: button.dataset.fileProjectSlug || null,
107
+ sessionId: button.dataset.fileSessionId || null
108
+ })) return;
102
109
  if (!openFileHandler) { showUnavailable(button); return; }
103
110
  var result = openFileHandler(button.dataset.filePath, {
104
111
  line: button.dataset.fileLine ? Number(button.dataset.fileLine) : null,
@@ -31,6 +31,7 @@ function createIssueLink(ref, labelText) {
31
31
  var button = document.createElement("button");
32
32
  button.type = "button";
33
33
  button.className = "clayos-issue-link";
34
+ button.classList.add("clayos-record-link");
34
35
  button.contentEditable = "false";
35
36
  button.dataset.issueRef = ref;
36
37
  button.setAttribute("aria-label", "Open Project Issue" + (labelText ? " " + labelText : ""));