@oar-sh/oar 0.9.6 → 0.9.7

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 (81) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/README.md +176 -22
  3. package/package.json +1 -1
  4. package/server/README.md +174 -7
  5. package/server/claude-cloud-worker/claude-cloud-event-normalizer.mjs +340 -0
  6. package/server/claude-cloud-worker/claude-cloud-session-process.mjs +1325 -0
  7. package/server/claude-cloud-worker/claude-cloud-session-worker.mjs +155 -0
  8. package/server/claude-worker/claude-remote-relay-tool.mjs +2 -0
  9. package/server/claude-worker/claude-sdk-adapter.mjs +18 -2
  10. package/server/claude-worker/claude-session-process.mjs +33 -0
  11. package/server/claude-worker/claude-session-worker.mjs +6 -0
  12. package/server/db-schema.mjs +17 -0
  13. package/server/migrations/0006-workspace-root-settings.mjs +67 -0
  14. package/server/migrations/0007-claude-cloud.mjs +82 -0
  15. package/server/public/app/agent-sessions-settings-ui.js +197 -0
  16. package/server/public/app/agent-sessions-settings.mjs +134 -0
  17. package/server/public/app/api-client.js +143 -4
  18. package/server/public/app/attachments-view.js +20 -1
  19. package/server/public/app/attribution-control.mjs +46 -0
  20. package/server/public/app/bootstrap.js +344 -31
  21. package/server/public/app/claude-cloud-conversation-ui.js +110 -0
  22. package/server/public/app/claude-cloud-settings-ui.js +374 -0
  23. package/server/public/app/claude-cloud-ui.mjs +611 -0
  24. package/server/public/app/composer-control-state.mjs +11 -0
  25. package/server/public/app/context-menu.mjs +140 -0
  26. package/server/public/app/context-tier-options.mjs +4 -0
  27. package/server/public/app/conversation-origin-ui.js +27 -0
  28. package/server/public/app/conversation-provider-indicator.mjs +22 -4
  29. package/server/public/app/conversation-view.js +83 -4
  30. package/server/public/app/cwd-picker.js +12 -0
  31. package/server/public/app/journal-view.js +698 -28
  32. package/server/public/app/math-dollars.mjs +89 -0
  33. package/server/public/app/model-selector-options.mjs +1 -1
  34. package/server/public/app/plan-usage-view.mjs +36 -8
  35. package/server/public/app/relay-error-ctas.mjs +16 -2
  36. package/server/public/app/remote-relay-origin-view.mjs +120 -4
  37. package/server/public/app/router.js +49 -12
  38. package/server/public/app/settings-modal.js +33 -2
  39. package/server/public/app/settings-tabs.js +1 -1
  40. package/server/public/app/socket-handlers.js +51 -0
  41. package/server/public/index.html +381 -7
  42. package/server/relay-tools.md +13 -1
  43. package/server/repositories/remote-relay-repository.mjs +42 -1
  44. package/server/repositories/session-repository.mjs +47 -1
  45. package/server/routes/claude-cloud-routes.mjs +140 -0
  46. package/server/routes/git-routes.mjs +37 -0
  47. package/server/routes/messages-routes.mjs +120 -15
  48. package/server/routes/remote-relay-routes.mjs +15 -1
  49. package/server/routes/sessions-routes.mjs +502 -72
  50. package/server/server-runtime.mjs +258 -25
  51. package/server/services/claude-account-usage-service.mjs +176 -0
  52. package/server/services/claude-cloud-repo-service.mjs +408 -0
  53. package/server/services/claude-cloud-session-service.mjs +326 -0
  54. package/server/services/claude-cloud-settings-service.mjs +269 -0
  55. package/server/services/git-remote-service.mjs +115 -0
  56. package/server/services/plan-usage-claude-cloud.mjs +211 -0
  57. package/server/services/plan-usage-claude.mjs +202 -40
  58. package/server/services/plan-usage-contract.mjs +39 -3
  59. package/server/services/plan-usage-service.mjs +82 -2
  60. package/server/services/provider-reasoning-effort.mjs +3 -0
  61. package/server/services/remote-relay-caller-context.mjs +49 -10
  62. package/server/services/remote-relay-client.mjs +9 -0
  63. package/server/services/remote-relay-dispatcher.mjs +383 -35
  64. package/server/services/remote-relay-inbound.mjs +49 -2
  65. package/server/services/remote-relay-loopback.mjs +245 -0
  66. package/server/services/remote-relay-registry-service.mjs +52 -0
  67. package/server/services/session-worker-launch-service.mjs +100 -15
  68. package/server/services/session-worker-process-service.mjs +348 -129
  69. package/server/services/session-worker-stop-service.mjs +24 -13
  70. package/server/services/session-worker-supervisor-service.mjs +56 -1
  71. package/server/services/usage-limit-pause-service.mjs +3 -1
  72. package/server/services/worker-launch-note-service.mjs +151 -0
  73. package/shared/claude-attribution.mjs +117 -0
  74. package/shared/claude-cloud/api-client.mjs +809 -0
  75. package/shared/claude-cloud/base-url.mjs +47 -0
  76. package/shared/claude-cloud/credentials.mjs +221 -0
  77. package/shared/claude-cloud/repo-url.mjs +111 -0
  78. package/shared/provider-routing.mjs +4 -2
  79. package/shared/remote-relay-contract.mjs +153 -22
  80. package/shared/remote-relay-tool-core.mjs +11 -8
  81. package/shared/steer-settle-failure.mjs +1 -0
@@ -256,6 +256,7 @@
256
256
  --provider-claude: #e0855a;
257
257
  --provider-cursor: #a78bfa;
258
258
  --provider-grok: #e879f9;
259
+ --provider-claude-cloud: #7dd3fc;
259
260
  --viewport-top-offset: 0px;
260
261
  --header-safe-top: max(env(safe-area-inset-top, 0px), var(--viewport-top-offset, 0px));
261
262
  --chat-header-top-base-desktop: 14px;
@@ -317,6 +318,7 @@
317
318
  --provider-claude: #b4531f;
318
319
  --provider-cursor: #6b3fd4;
319
320
  --provider-grok: #a21caf;
321
+ --provider-claude-cloud: #0369a1;
320
322
  --inline-code-bg: #eff1f3;
321
323
  }
322
324
  :root {
@@ -541,7 +543,7 @@
541
543
  border-radius: 8px;
542
544
  background: var(--bg2);
543
545
  color: var(--text);
544
- padding: 0 38px 0 10px;
546
+ padding: 0 76px 0 10px;
545
547
  font-size: var(--fs-meta-md);
546
548
  }
547
549
  /* iOS Safari zooms the page on focus for inputs under 16px, and the mobile
@@ -724,6 +726,7 @@
724
726
  .conv-provider-indicator[data-provider="claude"] { color: var(--provider-claude); }
725
727
  .conv-provider-indicator[data-provider="cursor"] { color: var(--provider-cursor); }
726
728
  .conv-provider-indicator[data-provider="grok"] { color: var(--provider-grok); }
729
+ .conv-provider-indicator[data-provider="claude-cloud"] { color: var(--provider-claude-cloud); }
727
730
  .conv-item.worker-ui-offline .conv-title,
728
731
  #chat-title[data-ui-state="offline"] { color: var(--conversation-title-offline); }
729
732
  .conv-item.worker-ui-ready .conv-title,
@@ -744,6 +747,70 @@
744
747
  }
745
748
  .conv-item:hover .conv-delete { opacity: 1; }
746
749
  .conv-delete:hover { color: var(--danger); }
750
+ /* Archive / unarchive sits left of the bin, shown on hover like it. */
751
+ .conv-archive {
752
+ position: absolute; right: 34px; top: 50%; transform: translateY(-50%);
753
+ background: none; border: none; color: var(--muted); cursor: pointer; font-size: var(--fs-ui-icon);
754
+ opacity: 0; transition: opacity 0.15s; padding: 4px;
755
+ }
756
+ .conv-item:hover .conv-archive { opacity: 1; }
757
+ .conv-archive:hover { color: var(--text); }
758
+ .conv-item.archived .conv-title { color: var(--muted); }
759
+ /* The Archived view: a toggle in the filter row, a banner above the list. */
760
+ #conv-archived-toggle {
761
+ position: absolute;
762
+ top: 50%;
763
+ right: 46px;
764
+ transform: translateY(-50%);
765
+ height: 28px;
766
+ min-width: 28px;
767
+ padding: 0 6px;
768
+ border: 1px solid transparent;
769
+ border-radius: 6px;
770
+ background: transparent;
771
+ color: var(--muted);
772
+ cursor: pointer;
773
+ font-size: var(--fs-meta-md);
774
+ }
775
+ #conv-archived-toggle:hover { color: var(--text); border-color: var(--border); }
776
+ #conv-archived-toggle[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); }
777
+ #conv-archived-banner {
778
+ display: flex; align-items: center; justify-content: space-between; gap: 8px;
779
+ margin: 0 16px 6px; padding: 6px 10px;
780
+ border: 1px solid var(--border); border-radius: 8px; background: var(--bg2);
781
+ font-size: var(--fs-meta-sm); color: var(--muted);
782
+ }
783
+ #conv-archived-banner[hidden] { display: none; }
784
+ .conv-archived-back {
785
+ border: 1px solid var(--border); border-radius: 6px; background: var(--bg3); color: var(--text);
786
+ font-size: var(--fs-meta-xs); padding: 2px 8px; cursor: pointer;
787
+ }
788
+ /* The conversation context menu (context-menu.mjs): right-click / long press. */
789
+ .context-menu {
790
+ position: fixed;
791
+ z-index: 1200;
792
+ min-width: 200px;
793
+ max-width: min(320px, calc(100vw - 8px));
794
+ padding: 4px;
795
+ display: flex;
796
+ flex-direction: column;
797
+ gap: 2px;
798
+ background: var(--bg2);
799
+ border: 1px solid var(--border);
800
+ border-radius: 10px;
801
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
802
+ }
803
+ .context-menu-item {
804
+ display: flex; align-items: center; gap: 8px; width: 100%; min-height: 34px;
805
+ border: 1px solid transparent; border-radius: 8px; background: transparent; color: var(--text);
806
+ padding: 0 10px; font: inherit; font-size: var(--fs-meta-md); text-align: left; cursor: pointer;
807
+ touch-action: manipulation; -webkit-tap-highlight-color: transparent;
808
+ }
809
+ .context-menu-item:hover, .context-menu-item:focus-visible { background: var(--bg3); border-color: var(--border); outline: none; }
810
+ .context-menu-item:disabled { opacity: 0.5; cursor: not-allowed; }
811
+ .context-menu-item-danger { color: var(--danger); }
812
+ .context-menu-sep { height: 1px; margin: 3px 6px; background: var(--border); }
813
+ #conv-list .conv-item { -webkit-touch-callout: none; user-select: none; -webkit-user-select: none; }
747
814
  .conv-open-questions {
748
815
  display: inline-flex;
749
816
  margin-left: 6px;
@@ -1434,6 +1501,17 @@
1434
1501
  overflow-wrap: normal;
1435
1502
  word-break: normal;
1436
1503
  }
1504
+ /* An inline formula breaks only between its terms: one long term is a box
1505
+ wider than the bubble and made the whole bubble scroll sideways. It
1506
+ scrolls inside itself instead. */
1507
+ .msg-bubble :not(.katex-display) > .katex {
1508
+ display: inline-block;
1509
+ max-width: 100%;
1510
+ overflow-x: auto;
1511
+ overflow-y: hidden;
1512
+ vertical-align: bottom;
1513
+ -webkit-overflow-scrolling: touch;
1514
+ }
1437
1515
  .katex-error {
1438
1516
  color: var(--danger);
1439
1517
  white-space: pre-wrap;
@@ -2021,6 +2099,14 @@
2021
2099
  }
2022
2100
  /* Claude-only thinking control (on/off + text visibility), under the
2023
2101
  auto-compact slider. */
2102
+ /* The folder's commit attribution (attribution-control.mjs), styled like
2103
+ the thinking rows above it. */
2104
+ .ctx-attribution { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border); }
2105
+ .ctx-attribution-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
2106
+ .ctx-attribution-label { font-size: var(--fs-meta-sm); color: var(--muted); }
2107
+ .ctx-attribution-select { font-size: var(--fs-meta-sm); max-width: 100%; }
2108
+ .ctx-attribution-note { margin-top: 6px; font-size: var(--fs-meta-sm); color: var(--muted); line-height: 1.4; overflow-wrap: anywhere; }
2109
+ .ctx-attribution-note code { font-size: inherit; }
2024
2110
  .ctx-thinking {
2025
2111
  margin-top: 14px;
2026
2112
  padding-top: 12px;
@@ -2119,6 +2205,13 @@
2119
2205
  }
2120
2206
  .plan-usage-card[data-status="unavailable"],
2121
2207
  .plan-usage-card[data-status="not-configured"] { opacity: 0.72; }
2208
+ /* A card whose provider is being read live while the kept reading shows. */
2209
+ .plan-usage-card[data-updating] .plan-usage-card-title::after {
2210
+ content: " · updating…";
2211
+ font-weight: 400;
2212
+ font-size: 0.78rem;
2213
+ color: var(--muted);
2214
+ }
2122
2215
  .plan-usage-card-head {
2123
2216
  display: flex;
2124
2217
  align-items: center;
@@ -2516,6 +2609,40 @@
2516
2609
  #model-lock-note[data-provider="claude"] { color: var(--provider-claude); }
2517
2610
  #model-lock-note[data-provider="cursor"] { color: var(--provider-cursor); }
2518
2611
  #model-lock-note[data-provider="grok"] { color: var(--provider-grok); }
2612
+ #model-lock-note[data-provider="claude-cloud"] { color: var(--provider-claude-cloud); }
2613
+ /* Claude Cloud conversations (claude-cloud-conversation-ui.js): where the
2614
+ session runs and what it pushed, above the composer like the lock note.
2615
+ The items wrap instead of scrolling, so a long branch name can never push
2616
+ the composer wider than a phone. */
2617
+ #cloud-session-line {
2618
+ display: flex;
2619
+ flex-wrap: wrap;
2620
+ align-items: center;
2621
+ gap: 2px 10px;
2622
+ margin-bottom: 6px;
2623
+ min-width: 0;
2624
+ color: var(--muted);
2625
+ font-size: var(--fs-meta-sm);
2626
+ line-height: 1.4;
2627
+ }
2628
+ #cloud-session-line[hidden] { display: none; }
2629
+ .cloud-line-item {
2630
+ min-width: 0;
2631
+ max-width: 100%;
2632
+ overflow: hidden;
2633
+ text-overflow: ellipsis;
2634
+ white-space: nowrap;
2635
+ }
2636
+ a.cloud-line-item { color: var(--accent); text-decoration: none; }
2637
+ a.cloud-line-item:hover { text-decoration: underline; }
2638
+ .cloud-line-repo { color: var(--provider-claude-cloud); font-weight: 600; }
2639
+ a.cloud-line-repo { color: var(--provider-claude-cloud); }
2640
+ /* No relay modes, no effort choice and no context tier in a cloud chat. */
2641
+ body.claude-cloud-conversation #mode-select,
2642
+ body.claude-cloud-conversation #context-tier-select,
2643
+ body.claude-cloud-conversation #reasoning-effort-select {
2644
+ display: none;
2645
+ }
2519
2646
  #model-select:disabled,
2520
2647
  #reasoning-effort-select:disabled {
2521
2648
  opacity: 0.55;
@@ -2822,6 +2949,46 @@
2822
2949
  flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
2823
2950
  color: var(--muted); font-size: var(--fs-ui-chip);
2824
2951
  }
2952
+ /* "via agent · “title”": an agent on this relay started the conversation.
2953
+ A button while the conversation it runs in can be opened, a span when it
2954
+ is gone. The title is long, so in the sidebar the date keeps its share. */
2955
+ .conv-origin-marker.conv-origin-agent { max-width: 60%; }
2956
+ button.conv-origin-agent,
2957
+ button.msg-origin-badge {
2958
+ font-family: inherit; cursor: pointer; text-align: left;
2959
+ touch-action: manipulation; -webkit-tap-highlight-color: transparent;
2960
+ }
2961
+ button.conv-origin-agent {
2962
+ background: none; border: none; padding: 0; margin: 0; line-height: inherit;
2963
+ color: var(--accent);
2964
+ }
2965
+ /* A finger needs more than one line of chip text: the padding grows the
2966
+ button, the negative margin keeps the row's height. */
2967
+ button.conv-origin-marker.conv-origin-agent { padding: 6px 0; margin: -6px 0; }
2968
+ button.conv-origin-agent:hover,
2969
+ button.msg-origin-badge:hover { text-decoration: underline; }
2970
+ button.msg-origin-badge { color: inherit; }
2971
+ #chat-title-origin {
2972
+ display: block; margin-top: 2px; min-width: 0;
2973
+ font-size: var(--fs-meta-xs); line-height: 1.2; color: var(--muted);
2974
+ overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
2975
+ }
2976
+ #chat-title-origin[hidden] { display: none; }
2977
+ #chat-title-origin .conv-origin-agent {
2978
+ display: block; max-width: 100%; font-size: inherit;
2979
+ overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
2980
+ }
2981
+ #chat-title-origin button.conv-origin-agent { padding: 3px 0; }
2982
+ /* Agent sessions (Settings → Relays). The slider spans the row so a thumb
2983
+ has room to travel at phone width, with a taller touch area than the
2984
+ track itself. */
2985
+ #agent-sessions-section[hidden] { display: none; }
2986
+ #agent-sessions-max-wait-slider {
2987
+ width: min(420px, 100%); min-height: 32px; margin: 0; box-sizing: border-box;
2988
+ accent-color: var(--accent); touch-action: pan-y;
2989
+ }
2990
+ #agent-sessions-max-wait-slider:disabled { opacity: 0.5; }
2991
+ #agent-sessions-status { overflow-wrap: anywhere; }
2825
2992
  /* Transcript card: a preview published during this turn, pinned into the
2826
2993
  response bubble. Compact single row; closed previews keep the label but
2827
2994
  lose the dead link. */
@@ -2865,6 +3032,11 @@
2865
3032
  background: rgba(63, 185, 80, 0.12); color: var(--relay-tone-online);
2866
3033
  border-radius: 6px; padding: 0 6px; font-size: var(--fs-meta-sm);
2867
3034
  }
3035
+ /* The relay's note while a session's worker cannot be started
3036
+ (worker-launch-note-service.mjs): a notice of the relay's, not a reply.
3037
+ Amber while the relay keeps trying or has stopped, green once started. */
3038
+ .msg.assistant.msg-worker-launch .msg-bubble { border-left: 3px solid #d29922; }
3039
+ .msg.assistant.msg-worker-launch-started .msg-bubble { border-left-color: var(--relay-tone-online); }
2868
3040
  /* Mid-turn steering. The CLI usually FOLDS a steered message into the
2869
3041
  running turn and answers everything with one reply; the steered row then
2870
3042
  settles as a compact marker under its own message (kind='folded'). More
@@ -2926,6 +3098,7 @@
2926
3098
  body.shared-reader-mode #reasoning-effort-select,
2927
3099
  body.shared-reader-mode #send-btn,
2928
3100
  body.shared-reader-mode #model-lock-note,
3101
+ body.shared-reader-mode #cloud-session-line,
2929
3102
  body.shared-reader-mode #model-pricing-details {
2930
3103
  display: none !important;
2931
3104
  }
@@ -3298,12 +3471,16 @@
3298
3471
  #new-conversation-context-select,
3299
3472
  #new-conversation-size-select,
3300
3473
  #new-conversation-cwd-select,
3301
- #new-conversation-cwd-manual {
3474
+ #new-conversation-cwd-manual,
3475
+ #new-conversation-cloud-repo,
3476
+ #new-conversation-cloud-branch {
3302
3477
  width: 100%;
3303
3478
  min-height: 42px;
3304
3479
  margin-top: 8px;
3305
3480
  }
3306
- #new-conversation-cwd-manual {
3481
+ #new-conversation-cwd-manual,
3482
+ #new-conversation-cloud-repo,
3483
+ #new-conversation-cloud-branch {
3307
3484
  box-sizing: border-box;
3308
3485
  padding: 0 10px;
3309
3486
  border-radius: 8px;
@@ -3312,6 +3489,84 @@
3312
3489
  color: var(--text);
3313
3490
  }
3314
3491
  #new-conversation-cwd-manual[hidden] { display: none; }
3492
+ #new-conversation-cloud-repo[aria-invalid="true"],
3493
+ #new-conversation-cloud-branch[aria-invalid="true"] { border-color: var(--danger); }
3494
+ /* Warning lines under Repository/Branch: what the cloud clone will not see. */
3495
+ #new-conversation-cloud-warnings {
3496
+ margin-top: 8px;
3497
+ font-size: 0.78rem;
3498
+ line-height: 1.4;
3499
+ color: var(--warn);
3500
+ overflow-wrap: anywhere;
3501
+ }
3502
+ #new-conversation-cloud-warnings:empty { display: none; }
3503
+ .new-conversation-cloud-warning::before { content: "⚠ "; }
3504
+ #new-conversation-cloud-error {
3505
+ margin-top: 8px;
3506
+ font-size: 0.78rem;
3507
+ line-height: 1.4;
3508
+ color: var(--danger);
3509
+ overflow-wrap: anywhere;
3510
+ }
3511
+ #new-conversation-cloud-error[hidden] { display: none; }
3512
+ /* Suggestions under Repository/Branch: a list in the flow of the modal
3513
+ (not a floating one: the modal scrolls on a phone and a popover would
3514
+ be clipped), at most a few rows high, scrolling inside itself. */
3515
+ .new-conversation-cloud-suggest {
3516
+ margin-top: 4px;
3517
+ max-height: min(40vh, 220px);
3518
+ overflow-y: auto;
3519
+ border: 1px solid var(--border);
3520
+ border-radius: 8px;
3521
+ background: var(--bg3);
3522
+ }
3523
+ .new-conversation-cloud-suggest[hidden] { display: none; }
3524
+ .new-conversation-cloud-suggest-item {
3525
+ display: flex;
3526
+ align-items: baseline;
3527
+ justify-content: space-between;
3528
+ gap: 8px;
3529
+ width: 100%;
3530
+ box-sizing: border-box;
3531
+ min-height: 38px;
3532
+ padding: 8px 10px;
3533
+ border: 0;
3534
+ border-bottom: 1px solid var(--border);
3535
+ background: transparent;
3536
+ color: var(--text);
3537
+ font: inherit;
3538
+ text-align: left;
3539
+ cursor: pointer;
3540
+ overflow-wrap: anywhere;
3541
+ }
3542
+ .new-conversation-cloud-suggest-item:last-child { border-bottom: 0; }
3543
+ .new-conversation-cloud-suggest-item:hover,
3544
+ .new-conversation-cloud-suggest-item[aria-selected="true"] { background: var(--bg2); }
3545
+ .new-conversation-cloud-suggest-meta {
3546
+ flex: 0 0 auto;
3547
+ font-size: 0.72rem;
3548
+ color: var(--muted);
3549
+ white-space: nowrap;
3550
+ }
3551
+ .new-conversation-cloud-refresh {
3552
+ margin-left: 6px;
3553
+ padding: 0 6px;
3554
+ min-height: 22px;
3555
+ border: 1px solid transparent;
3556
+ border-radius: 6px;
3557
+ background: transparent;
3558
+ color: var(--muted);
3559
+ font: inherit;
3560
+ cursor: pointer;
3561
+ vertical-align: middle;
3562
+ }
3563
+ .new-conversation-cloud-refresh:hover { color: var(--text); border-color: var(--border); }
3564
+ .new-conversation-cloud-refresh[aria-busy="true"] { opacity: 0.5; cursor: progress; }
3565
+ .new-conversation-cloud-suggest-note {
3566
+ padding: 8px 10px;
3567
+ font-size: 0.78rem;
3568
+ color: var(--muted);
3569
+ }
3315
3570
  /* Shared row/button recipe for the file-explorer CWD browse buttons in the
3316
3571
  New chat and Change CWD modals — one place to restyle both. */
3317
3572
  .cwd-browse-row {
@@ -4826,6 +5081,7 @@
4826
5081
  #status-btn { min-width: 72px; padding: 9px 10px; font-size: var(--fs-meta-md); }
4827
5082
  #conv-filter-row { padding: 8px 12px; }
4828
5083
  #conv-filter-clear { right: 16px; }
5084
+ #conv-archived-toggle { right: 42px; }
4829
5085
  #conv-list { padding: 0 5px 8px; }
4830
5086
  .conv-item { padding: 6px 11px; border-radius: 8px; }
4831
5087
  .conv-title {
@@ -4890,6 +5146,10 @@
4890
5146
  overflow: hidden;
4891
5147
  text-overflow: ellipsis;
4892
5148
  }
5149
+ #chat-title-origin {
5150
+ margin-top: 1px;
5151
+ font-size: clamp(0.62rem, 2vw, 0.7rem);
5152
+ }
4893
5153
  #chat-header-actions {
4894
5154
  margin-left: auto;
4895
5155
  }
@@ -5040,6 +5300,12 @@
5040
5300
  font-size: var(--fs-meta-md);
5041
5301
  }
5042
5302
  #attach-btn { order: 4; flex: 0 0 40px; }
5303
+ /* A cloud chat hides mode, context tier and effort: the model select
5304
+ takes the room they leave. */
5305
+ body.claude-cloud-conversation #model-select {
5306
+ flex: 1 1 0;
5307
+ width: auto;
5308
+ }
5043
5309
  #context-tier-select {
5044
5310
  order: 5;
5045
5311
  flex: 0 0 70px;
@@ -5357,8 +5623,10 @@
5357
5623
  <div id="conv-filter-row">
5358
5624
  <input id="conv-filter-input" type="text" placeholder="Filter conversations…" autocomplete="off" spellcheck="false" aria-label="Filter conversations by title">
5359
5625
  <button id="conv-filter-clear" type="button" title="Clear filter" aria-label="Clear filter" hidden>×</button>
5626
+ <button id="conv-archived-toggle" type="button" title="Show archived conversations" aria-label="Show archived conversations" aria-pressed="false" onclick="toggleArchivedConversations()">🗄</button>
5360
5627
  <div id="conv-filter-status" role="status" aria-live="polite"></div>
5361
5628
  </div>
5629
+ <div id="conv-archived-banner" hidden>Archived conversations <button type="button" class="conv-archived-back" onclick="toggleArchivedConversations()">Back to the list</button></div>
5362
5630
  <div id="conv-list"></div>
5363
5631
  </div>
5364
5632
  <div id="sidebar-resizer" role="separator" aria-orientation="vertical" aria-label="Resize conversations sidebar"></div>
@@ -5367,7 +5635,7 @@
5367
5635
  <div id="chat-header">
5368
5636
  <button id="sidebar-toggle" class="header-icon-btn" type="button" title="Toggle conversations sidebar" aria-label="Toggle conversations sidebar">☰</button>
5369
5637
  <div id="chat-title-wrap">
5370
- <span id="chat-title-slot"><span id="chat-title-primary"><span id="chat-title-watch-indicator" hidden>👁️ </span><span id="chat-title">Select or start a conversation</span></span><span id="chat-title-cwd" hidden></span><span id="chat-title-session-usage" hidden></span></span>
5638
+ <span id="chat-title-slot"><span id="chat-title-primary"><span id="chat-title-watch-indicator" hidden>👁️ </span><span id="chat-title">Select or start a conversation</span></span><span id="chat-title-origin" hidden></span><span id="chat-title-cwd" hidden></span><span id="chat-title-session-usage" hidden></span></span>
5371
5639
  <form id="chat-title-editor" hidden>
5372
5640
  <input id="chat-title-input" type="text" maxlength="120" autocomplete="off" spellcheck="false">
5373
5641
  <button id="chat-title-save-btn" class="chat-title-action-btn" type="submit">Save</button>
@@ -5387,11 +5655,13 @@
5387
5655
  <button id="chat-menu-usage" class="chat-actions-menu-item" type="button" role="menuitem">📊 Check Usage</button>
5388
5656
  <button id="chat-menu-compact" class="chat-actions-menu-item" type="button" role="menuitem" hidden disabled aria-hidden="true" tabindex="-1" style="display:none">🗜️ Compact</button>
5389
5657
  <button id="chat-menu-share-conversation" class="chat-actions-menu-item" type="button" role="menuitem">➡️ Share conversation</button>
5658
+ <button id="chat-menu-archive" class="chat-actions-menu-item" type="button" role="menuitem">🗄 Archive conversation</button>
5390
5659
  <button id="chat-menu-refresh-history" class="chat-actions-menu-item" type="button" role="menuitem" hidden disabled aria-hidden="true" tabindex="-1" style="display:none">🔄️ Refresh history</button>
5391
5660
  <button id="chat-menu-inspect-tmux" class="chat-actions-menu-item" type="button" role="menuitem">🖥️ Inspect tmux console</button>
5392
5661
  <button id="chat-menu-select-models" class="chat-actions-menu-item" type="button" role="menuitem">🤗 Select Models</button>
5393
5662
  <button id="chat-menu-change-cwd" class="chat-actions-menu-item" type="button" role="menuitem">🗂️ Change CWD</button>
5394
5663
  <button id="chat-menu-git-changes" class="chat-actions-menu-item" type="button" role="menuitem">🌿 Git changes</button>
5664
+ <button id="chat-menu-open-cloud-session" class="chat-actions-menu-item" type="button" role="menuitem" hidden>☁️ Open on claude.ai</button>
5395
5665
  <button id="chat-menu-settings" class="chat-actions-menu-item" type="button" role="menuitem">⚙️ Settings</button>
5396
5666
  <button id="chat-menu-restart-relay" class="chat-actions-menu-item" type="button" role="menuitem">🌄 Restart web relay</button>
5397
5667
  <button id="chat-menu-suspend-host" class="chat-actions-menu-item" type="button" role="menuitem" onclick="openSuspendHostConfirmation()">💤 Suspend host</button>
@@ -5414,6 +5684,7 @@
5414
5684
  <div id="model-banner"></div>
5415
5685
  <div id="usage-limit-banner" role="status" aria-live="polite" hidden></div>
5416
5686
  <div id="model-lock-note" hidden></div>
5687
+ <div id="cloud-session-line" aria-label="Cloud session" hidden></div>
5417
5688
  <div id="model-metadata-blocker" aria-live="polite">
5418
5689
  <span id="model-metadata-blocker-text">Model metadata is unavailable. Refresh to choose a model and reasoning effort.</span>
5419
5690
  <button type="button" id="model-metadata-retry-btn">Refresh models</button>
@@ -5794,6 +6065,16 @@
5794
6065
  tabindex="-1"
5795
6066
  data-settings-provider-tab="claude"
5796
6067
  >Claude</button>
6068
+ <button
6069
+ id="settings-provider-tab-claude-cloud"
6070
+ class="settings-tab settings-subtab"
6071
+ type="button"
6072
+ role="tab"
6073
+ aria-selected="false"
6074
+ aria-controls="settings-provider-panel-claude-cloud"
6075
+ tabindex="-1"
6076
+ data-settings-provider-tab="claude-cloud"
6077
+ >Claude Cloud</button>
5797
6078
  <button
5798
6079
  id="settings-provider-tab-grok"
5799
6080
  class="settings-tab settings-subtab"
@@ -5982,6 +6263,13 @@
5982
6263
  placeholder="claude-sonnet-5"
5983
6264
  aria-label="Claude model ID"
5984
6265
  >
6266
+ <label for="claude-attribution-select" style="margin-top: 8px;">Commit attribution</label>
6267
+ <select id="claude-attribution-select" aria-label="Commit attribution" onchange="window.previewClaudeAttribution?.(this.value)">
6268
+ <option value="oar">OAR — Co-authored-by: Open Agent Relay (model)</option>
6269
+ <option value="vanilla">Vanilla — Claude Code's own attribution</option>
6270
+ <option value="off">Off — no attribution</option>
6271
+ </select>
6272
+ <div id="claude-attribution-example" class="settings-help"></div>
5985
6273
  <div id="claude-settings-status" class="settings-help settings-provider-status">Loading Claude settings…</div>
5986
6274
  <div style="display: flex; gap: 8px; margin-top: 8px;">
5987
6275
  <button id="claude-save-btn" type="button" class="summary-btn" onclick="saveClaudeSettings()">Save Claude settings</button>
@@ -5989,6 +6277,42 @@
5989
6277
  <div class="settings-help">Runs Claude conversations through the Claude Agent SDK using the relay host's logged-in Claude credentials (no API key stored). Disabling also rebinds conversations that have not sent a message yet.</div>
5990
6278
  </div>
5991
6279
  </div>
6280
+ <!-- Claude Cloud (claude-cloud-settings-ui.js). The login itself is managed on
6281
+ the Claude tab; this panel only says which account pays. -->
6282
+ <div
6283
+ id="settings-provider-panel-claude-cloud"
6284
+ class="settings-panel"
6285
+ role="tabpanel"
6286
+ tabindex="0"
6287
+ aria-labelledby="settings-provider-tab-claude-cloud"
6288
+ data-settings-provider-panel="claude-cloud"
6289
+ hidden
6290
+ >
6291
+ <div class="settings-row settings-row-column" style="margin-top: 12px; margin-bottom: 0;">
6292
+ <label for="claude-cloud-enabled-toggle">Claude Cloud</label>
6293
+ <div class="settings-help">Cloud chats run Claude Code in a sandbox at Anthropic, on its own clone of a GitHub repository, instead of on the relay host. They are billed to the Claude account below. The relay reads that account's login from the Claude CLI on the relay host; it does not store it or refresh it.</div>
6294
+ <div class="settings-row" style="width: 100%; margin-top: 8px; margin-bottom: 8px;">
6295
+ <label for="claude-cloud-enabled-toggle">Enable Claude Cloud for New Chat</label>
6296
+ <input id="claude-cloud-enabled-toggle" type="checkbox" onchange="toggleClaudeCloudProvider(this.checked)" disabled>
6297
+ </div>
6298
+ <div id="claude-cloud-account-row" class="settings-row" style="width: 100%; margin-bottom: 0;" hidden>
6299
+ <div id="claude-cloud-account" class="settings-help settings-provider-status" style="min-width: 0; overflow-wrap: anywhere;"></div>
6300
+ <button id="claude-cloud-login-btn" type="button" class="summary-btn" style="flex: 0 0 auto;" onclick="openClaudeCloudLoginTab()">Claude login</button>
6301
+ </div>
6302
+ <div id="claude-cloud-token" class="settings-help settings-provider-status" style="margin-top: 6px;" hidden></div>
6303
+ <label for="claude-cloud-environment-select" style="margin-top: 12px;">Cloud environment</label>
6304
+ <select id="claude-cloud-environment-select" aria-label="Cloud environment" style="max-width: 100%;" disabled></select>
6305
+ <div id="claude-cloud-environment-note" class="settings-help" hidden></div>
6306
+ <label for="claude-cloud-model-select" style="margin-top: 8px;">Default model</label>
6307
+ <select id="claude-cloud-model-select" aria-label="Default model for cloud chats" style="max-width: 100%;" disabled></select>
6308
+ <div id="claude-cloud-settings-status" class="settings-help settings-provider-status">Loading Claude Cloud settings…</div>
6309
+ <div style="display: flex; gap: 8px 12px; margin-top: 8px; flex-wrap: wrap; align-items: center;">
6310
+ <button id="claude-cloud-save-btn" type="button" class="summary-btn" onclick="saveClaudeCloudSettings()" disabled>Save Claude Cloud settings</button>
6311
+ <a id="claude-cloud-open-link" class="plan-usage-link" href="https://claude.ai/code" target="_blank" rel="noopener noreferrer" style="font-size: var(--fs-meta-sm);">Open claude.ai/code</a>
6312
+ </div>
6313
+ <div class="settings-help">A cloud chat works on what is pushed to GitHub, and its results come back as pushed branches. It has no relay modes, no reasoning effort choice and no mid-turn steering, and it takes images as its only attachments.</div>
6314
+ </div>
6315
+ </div>
5992
6316
  <div
5993
6317
  id="settings-provider-panel-grok"
5994
6318
  class="settings-panel"
@@ -6240,6 +6564,42 @@
6240
6564
  >
6241
6565
  </div>
6242
6566
  <div class="settings-help" style="margin-top: 6px;">When off, agents on paired relays can still read sessions here, but cannot send prompts, start sessions, answer questions or stop turns. The relay token stays the real boundary: anyone holding it has full access.</div>
6567
+ <!-- Agent sessions (agent-sessions-settings-ui.js). Hidden until the relay
6568
+ answers with settings; a relay without the route never shows it. -->
6569
+ <div id="agent-sessions-section" hidden>
6570
+ <div class="settings-row settings-row-column" style="margin-top: 16px; margin-bottom: 0;">
6571
+ <label>Agent sessions</label>
6572
+ </div>
6573
+ <div class="settings-row" style="margin-top: 6px; margin-bottom: 0;">
6574
+ <label for="agent-sessions-enabled-toggle" style="font-weight: normal;">Agents may start and use sessions on this relay</label>
6575
+ <input
6576
+ id="agent-sessions-enabled-toggle"
6577
+ type="checkbox"
6578
+ onchange="toggleAgentSessions(this.checked)"
6579
+ aria-label="Agents may start and use sessions on this relay"
6580
+ disabled
6581
+ >
6582
+ </div>
6583
+ <div class="settings-help" style="margin-top: 6px;">Sessions an agent starts use this relay's accounts. The first one in a conversation asks for your approval. <span id="agent-sessions-cap" hidden></span></div>
6584
+ <div class="settings-row settings-row-column" style="margin-top: 12px; margin-bottom: 0;">
6585
+ <label for="agent-sessions-max-wait-slider">Longest wait per tool call <span id="agent-sessions-max-wait-value">10 min</span></label>
6586
+ <input
6587
+ id="agent-sessions-max-wait-slider"
6588
+ type="range"
6589
+ min="120"
6590
+ max="3600"
6591
+ step="60"
6592
+ value="600"
6593
+ aria-label="Longest wait per tool call"
6594
+ aria-valuetext="10 min"
6595
+ oninput="previewAgentSessionsMaxWait(this.value)"
6596
+ onchange="saveAgentSessionsMaxWait(this.value)"
6597
+ disabled
6598
+ >
6599
+ </div>
6600
+ <div class="settings-help" style="margin-top: 6px;">How long an agent may wait for a session in one call, on this relay or a paired one. It can call again to keep waiting.</div>
6601
+ <div id="agent-sessions-status" class="settings-help settings-provider-status" style="margin-top: 6px;" role="status" aria-live="polite" hidden></div>
6602
+ </div>
6243
6603
  <div id="remote-relays-list-section" class="settings-row settings-row-column" style="margin-top: 16px; margin-bottom: 0;">
6244
6604
  <label>Remote relays</label>
6245
6605
  <div id="remote-relays-list"></div>
@@ -6414,11 +6774,25 @@
6414
6774
  </select>
6415
6775
  <div id="new-conversation-provider-help" style="margin-top:8px;font-size:0.78rem;color:var(--muted);">Copilot models use your GitHub Copilot runtime.</div>
6416
6776
  </div>
6777
+ <!-- Claude Cloud only (journal-view.js): the GitHub repository and branch
6778
+ the cloud session clones, filled from the folder picked above. -->
6779
+ <div id="new-conversation-cloud-row" hidden>
6780
+ <label for="new-conversation-cloud-repo" style="display:block;margin-top:12px;">Repository <button id="new-conversation-cloud-repo-refresh" type="button" class="new-conversation-cloud-refresh" title="Read the repository list again" aria-label="Read the repository list again" onclick="refreshNewConversationCloudRepoList()">↻</button></label>
6781
+ <input id="new-conversation-cloud-repo" type="text" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="new-conversation-cloud-repo-list" inputmode="url" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" placeholder="owner/repo or GitHub URL" aria-describedby="new-conversation-cloud-warnings new-conversation-cloud-error">
6782
+ <div id="new-conversation-cloud-repo-list" class="new-conversation-cloud-suggest" role="listbox" aria-label="Repositories" hidden></div>
6783
+ <label for="new-conversation-cloud-branch" style="display:block;margin-top:12px;">Branch</label>
6784
+ <input id="new-conversation-cloud-branch" type="text" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="new-conversation-cloud-branch-list" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" placeholder="Default branch" aria-describedby="new-conversation-cloud-warnings new-conversation-cloud-error">
6785
+ <div id="new-conversation-cloud-branch-list" class="new-conversation-cloud-suggest" role="listbox" aria-label="Branches" hidden></div>
6786
+ <div id="new-conversation-cloud-warnings" role="status" aria-live="polite"></div>
6787
+ <div id="new-conversation-cloud-error" role="alert" hidden></div>
6788
+ </div>
6417
6789
  <label for="new-conversation-model-select" style="display:block;margin-top:12px;">Model</label>
6418
6790
  <select id="new-conversation-model-select"></select>
6419
- <label for="new-conversation-reasoning-select" style="display:block;margin-top:12px;">Reasoning effort</label>
6420
- <select id="new-conversation-reasoning-select"></select>
6421
- <div id="new-conversation-reasoning-status" style="margin-top:8px;font-size:0.78rem;color:var(--muted);">Choose the effort used when this conversation starts.</div>
6791
+ <div id="new-conversation-reasoning-row">
6792
+ <label for="new-conversation-reasoning-select" style="display:block;margin-top:12px;">Reasoning effort</label>
6793
+ <select id="new-conversation-reasoning-select"></select>
6794
+ <div id="new-conversation-reasoning-status" style="margin-top:8px;font-size:0.78rem;color:var(--muted);">Choose the effort used when this conversation starts.</div>
6795
+ </div>
6422
6796
  <div id="new-conversation-context-row" style="display:none;">
6423
6797
  <label for="new-conversation-context-select" style="display:block;margin-top:12px;">Context window</label>
6424
6798
  <select id="new-conversation-context-select" disabled aria-readonly="true"></select>
@@ -30,4 +30,16 @@ If you have a `preview` tool, use it. Otherwise use the authenticated localhost
30
30
 
31
31
  ## Remote relays
32
32
 
33
- If you have a `remote_relay` tool, this relay is paired with other OAR relays and the tool works on them: list and read their sessions, prompt a session, start a new one and wait for its reply. Use it only when the user asks for work on another relay or mentions one, and start with `{"action":"list_relays"}`. A relay stays locked until the user mentions it in this conversation (`@name` or its plain name); a locked relay refuses everything except `list_relays`, so ask the user to mention it rather than retrying. Write actions may first wait for the user's approval. If a result lists `pendingQuestions`, the remote agent is waiting for an answer: ask the user (unless they told you to decide), then pass it on with `answer_question`. Always tell the user which relay and session you worked on.
33
+ If you have a `remote_relay` tool, it works with sessions on OAR relays: the other relays this relay is paired with and, when its owner allows it, this relay itself. It lists and reads sessions, prompts a session, starts a new one and waits for its reply. Start with `{"action":"list_relays"}`.
34
+
35
+ A paired relay stays locked until the user mentions it in this conversation (`@name` or its plain name); a locked relay refuses everything except `list_relays`, so ask the user to mention it rather than retrying. Use a paired relay only when the user asks for work there or mentions it.
36
+
37
+ This relay itself is listed with "self": true when its owner switched agent sessions on, and is addressed by its name or as "this"; it needs no mention. Use it to hand parts of your task to other sessions:
38
+
39
+ - `create_session` starts a session with any provider that `relay_info` lists: a local one in a folder ("cwd"), or provider "claude-cloud" with "repo" (a GitHub URL or owner/repo) and an optional "branch". At most 4 sessions you started may work at the same time; a fifth is refused until one finishes.
40
+ - The first `create_session` on this relay shows the user an approval card, once per conversation. A session that an agent created cannot create sessions itself (one level only).
41
+ - A new session knows nothing of this conversation: write a self-contained prompt. A Claude Cloud session works on a fresh clone in a sandbox at Anthropic, so name the branch it should work on and push.
42
+ - Follow each session with `wait` (the message id comes back from `create_session` and `send`) and `read_session`; "wait_seconds" is limited to the maximum set on the relay (default 600).
43
+ - Sessions run on the relay owner's accounts and cost their usage: start only what the task needs.
44
+
45
+ Write actions may first wait for the user's approval. If a result lists `pendingQuestions`, the other agent is waiting for an answer: ask the user (unless they told you to decide), then pass it on with `answer_question`. Always tell the user which relay and session you worked on.