@oar-sh/oar 0.9.2 → 0.9.4
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/.github/extensions/web-relay/extension.mjs +6 -1
- package/.github/extensions/web-relay/skills/tool-activity.mjs +7 -0
- package/CHANGELOG.md +336 -0
- package/README.md +455 -423
- package/bin/oar.js +25 -10
- package/package.json +11 -5
- package/server/README.md +44 -4
- package/server/claude-worker/claude-remote-relay-tool.mjs +99 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +26 -4
- package/server/claude-worker/claude-session-process.mjs +1073 -69
- package/server/claude-worker/claude-session-worker.mjs +45 -4
- package/server/claude-worker/claude-turn-publisher.mjs +81 -20
- package/server/claude-worker/claude-worker-link-wiring.mjs +9 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +7 -0
- package/server/copilot-worker/copilot-ask-user-tool.mjs +89 -0
- package/server/copilot-worker/copilot-continuation-signals.mjs +55 -0
- package/server/copilot-worker/copilot-question-bridge.mjs +34 -12
- package/server/copilot-worker/copilot-remote-relay-tool.mjs +59 -0
- package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +113 -14
- package/server/copilot-worker/copilot-sdk-session-process.mjs +2349 -498
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +82 -20
- package/server/cursor-worker/cursor-remote-relay-tool.mjs +65 -0
- package/server/cursor-worker/cursor-turn-runner.mjs +45 -2
- package/server/cursor-worker/sdk-message-normalizer.mjs +25 -0
- package/server/db-schema.mjs +28 -0
- package/server/grok-worker/acp-client.mjs +92 -6
- package/server/grok-worker/grok-turn-runner.mjs +59 -8
- package/server/grok-worker/sdk-message-normalizer.mjs +13 -0
- package/server/mcp/oar-mcp-server.mjs +473 -0
- package/server/migrations/0004-steer-settle-markers.mjs +119 -0
- package/server/migrations/0005-remote-relays.mjs +90 -0
- package/server/public/app/action-confirmations.js +141 -13
- package/server/public/app/api-client.js +125 -2
- package/server/public/app/ask-user-view.js +102 -13
- package/server/public/app/attachments-view.js +37 -2
- package/server/public/app/background-task-timeout-options.mjs +9 -0
- package/server/public/app/background-tasks-view.mjs +33 -18
- package/server/public/app/bootstrap.js +55 -14
- package/server/public/app/composer-control-state.mjs +52 -34
- package/server/public/app/conversation-deep-link.mjs +42 -0
- package/server/public/app/conversation-draft-sync.mjs +187 -0
- package/server/public/app/conversation-list-filter.mjs +86 -0
- package/server/public/app/conversation-provider-indicator.mjs +8 -0
- package/server/public/app/conversation-view.js +1016 -200
- package/server/public/app/emoji-view.js +2 -0
- package/server/public/app/font-scaling.js +4 -37
- package/server/public/app/host-suspend-ui.js +272 -0
- package/server/public/app/journal-view.js +149 -6
- package/server/public/app/mention-autocomplete.mjs +298 -0
- package/server/public/app/model-selector-options.mjs +1 -1
- package/server/public/app/push-settings.js +1 -0
- package/server/public/app/pwa-install.js +8 -3
- package/server/public/app/question-multi-select.mjs +52 -0
- package/server/public/app/relay-prompt-sanitizer.mjs +61 -3
- package/server/public/app/remote-relay-origin-view.mjs +76 -0
- package/server/public/app/remote-relay-shared.mjs +85 -0
- package/server/public/app/remote-relays-settings.js +618 -0
- package/server/public/app/remote-relays-store.mjs +126 -0
- package/server/public/app/settings-modal.js +12 -8
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/socket-handlers.js +37 -1
- package/server/public/app/steered-turn-merge.mjs +100 -0
- package/server/public/app/store.js +126 -15
- package/server/public/app/sync-outbox.mjs +16 -8
- package/server/public/app/thinking-follow.js +260 -0
- package/server/public/app/tool-activity-emoji.mjs +40 -0
- package/server/public/app/transcript-viewport-anchor.mjs +210 -0
- package/server/public/index.html +463 -17
- package/server/public/sw.js +7 -0
- package/server/relay-tools.md +7 -1
- package/server/repositories/message-repository.mjs +68 -18
- package/server/repositories/question-repository.mjs +18 -0
- package/server/repositories/remote-relay-repository.mjs +75 -0
- package/server/repositories/session-repository.mjs +13 -32
- package/server/routes/ask-user-routes.mjs +42 -2
- package/server/routes/messages-routes.mjs +572 -56
- package/server/routes/remote-relay-routes.mjs +133 -0
- package/server/routes/sessions-routes.mjs +378 -84
- package/server/server-runtime.mjs +303 -25
- package/server/services/github-ci-activity-service.mjs +199 -0
- package/server/services/host-suspend-command.mjs +38 -0
- package/server/services/host-suspend-service.mjs +254 -0
- package/server/services/live-turn-picker.mjs +47 -0
- package/server/services/push-dispatch-service.mjs +37 -4
- package/server/services/relay-prompt-sanitizer.mjs +17 -2
- package/server/services/relay-question-context.mjs +46 -0
- package/server/services/remote-relay-caller-context.mjs +341 -0
- package/server/services/remote-relay-client.mjs +332 -0
- package/server/services/remote-relay-dispatcher.mjs +1500 -0
- package/server/services/remote-relay-inbound.mjs +187 -0
- package/server/services/remote-relay-pairing.mjs +189 -0
- package/server/services/remote-relay-registry-service.mjs +581 -0
- package/server/services/sdk-session-import-service.mjs +49 -7
- package/server/services/session-worker-launch-service.mjs +131 -12
- package/server/services/session-worker-registry-service.mjs +75 -0
- package/server/services/session-worker-supervisor-service.mjs +7 -1
- package/server/services/session-worker-websocket-service.mjs +23 -1
- package/server/services/steer-resend.mjs +45 -0
- package/shared/background-task-timeout.mjs +12 -4
- package/shared/control-poller.mjs +4 -1
- package/shared/question-multi-select.mjs +16 -0
- package/shared/relay-stop-failure.mjs +15 -0
- package/shared/remote-relay-contract.mjs +539 -0
- package/shared/remote-relay-mentions.mjs +129 -0
- package/shared/remote-relay-tool-core.mjs +314 -0
- package/shared/slash-command-guard.mjs +76 -0
- package/shared/steer-note.mjs +66 -0
- package/shared/steer-settle-failure.mjs +89 -0
- package/shared/steer-settle-markers.mjs +18 -0
- package/shared/worker-crash-guard.mjs +40 -25
- package/shared/worker-runtime/api-client.mjs +112 -22
- package/shared/worker-runtime/heartbeat.mjs +55 -1
- package/shared/worker-runtime/runner-link-wiring.mjs +44 -0
- package/shared/worker-runtime/worker-websocket-link.mjs +119 -8
- package/server/claude-worker/claude-session-test-harness.mjs +0 -250
- package/server/claude-worker/fixtures/workflow-run-record.json +0 -1
- package/server/copilot-worker/copilot-sdk-test-harness.mjs +0 -382
- package/server/copilot-worker/fixtures/abort-turn.json +0 -588
- package/server/copilot-worker/fixtures/ask-user-turn.json +0 -1154
- package/server/copilot-worker/fixtures/background-timer-continuation.json +0 -445
- package/server/copilot-worker/fixtures/background-timer-turn.json +0 -497
- package/server/copilot-worker/fixtures/happy-turn.json +0 -566
- package/server/copilot-worker/fixtures/quota-turn.json +0 -647
- package/server/copilot-worker/fixtures/reasoning-turn.json +0 -625
- package/server/copilot-worker/fixtures/resume-turn.json +0 -700
- package/server/copilot-worker/fixtures/subagent-turn.json +0 -1378
- package/server/copilot-worker/fixtures/tool-permission-turn.json +0 -1293
- package/server/routes/messages-routes-test-harness.mjs +0 -111
- package/server/services/fixtures/claude-auth-stub.cmd +0 -1
- package/server/services/fixtures/claude-auth-stub.mjs +0 -76
- package/server/services/fixtures/claude-auth-stub.sh +0 -4
- package/server/services/fixtures/cli-install-stub.cmd +0 -1
- package/server/services/fixtures/cli-install-stub.mjs +0 -98
- package/server/services/fixtures/cli-install-stub.sh +0 -4
- package/server/services/fixtures/grok-stub.cmd +0 -1
- package/server/services/fixtures/grok-stub.mjs +0 -89
- package/server/services/fixtures/grok-stub.sh +0 -4
- package/shared/fixtures/copilot-catalog-raw-2026-09-07.json +0 -2363
package/server/public/index.html
CHANGED
|
@@ -210,6 +210,13 @@
|
|
|
210
210
|
document.documentElement.style.setProperty('--font-scale', String(value));
|
|
211
211
|
document.documentElement.style.setProperty('--font-scale-percent', `${Math.round(value * 100)}%`);
|
|
212
212
|
})();
|
|
213
|
+
// Mirrored by syncScreenShortSide in app/store.js.
|
|
214
|
+
(function applyScreenShortSideEarly() {
|
|
215
|
+
const shortSide = Math.min(Number(screen.width) || 0, Number(screen.height) || 0);
|
|
216
|
+
if (!shortSide) return;
|
|
217
|
+
document.documentElement.style.setProperty('--screen-short-side', `${shortSide}px`);
|
|
218
|
+
document.documentElement.toggleAttribute('data-phone-screen', shortSide <= 500);
|
|
219
|
+
})();
|
|
213
220
|
</script>
|
|
214
221
|
<style>
|
|
215
222
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -318,6 +325,20 @@
|
|
|
318
325
|
|
|
319
326
|
html {
|
|
320
327
|
font-size: var(--font-scale-percent, 100%);
|
|
328
|
+
/* Mobile browsers otherwise inflate text on rotation. */
|
|
329
|
+
-webkit-text-size-adjust: 100%;
|
|
330
|
+
text-size-adjust: 100%;
|
|
331
|
+
}
|
|
332
|
+
/* A rotated phone must keep its portrait text size. Portrait scales with
|
|
333
|
+
the width (3.4vw, phone rule below), which is the screen's short side;
|
|
334
|
+
landscape uses that short side directly. No viewport unit or height
|
|
335
|
+
works here: with interactive-widget=resizes-content the on-screen
|
|
336
|
+
keyboard shrinks the viewport, and would shrink the text with it (and
|
|
337
|
+
make a portrait phone match this landscape query mid-typing). :root
|
|
338
|
+
outranks the later width-based html rule, which a narrow phone still
|
|
339
|
+
matches in landscape. */
|
|
340
|
+
@media (pointer: coarse) and (orientation: landscape) and (max-height: 500px) {
|
|
341
|
+
:root[data-phone-screen] { font-size: calc(clamp(12px, calc(var(--screen-short-side) * 0.034), 14px) * var(--font-scale, 1)); }
|
|
321
342
|
}
|
|
322
343
|
html, body {
|
|
323
344
|
height: 100%;
|
|
@@ -508,6 +529,65 @@
|
|
|
508
529
|
box-shadow: 0 0 0 1px rgba(210, 153, 34, 0.42), 0 0 8px rgba(210, 153, 34, 0.35);
|
|
509
530
|
}
|
|
510
531
|
|
|
532
|
+
#conv-filter-row {
|
|
533
|
+
position: relative;
|
|
534
|
+
padding: 10px 16px;
|
|
535
|
+
}
|
|
536
|
+
#conv-filter-input {
|
|
537
|
+
width: 100%;
|
|
538
|
+
box-sizing: border-box;
|
|
539
|
+
height: 36px;
|
|
540
|
+
border: 1px solid var(--border);
|
|
541
|
+
border-radius: 8px;
|
|
542
|
+
background: var(--bg2);
|
|
543
|
+
color: var(--text);
|
|
544
|
+
padding: 0 38px 0 10px;
|
|
545
|
+
font-size: var(--fs-meta-md);
|
|
546
|
+
}
|
|
547
|
+
/* iOS Safari zooms the page on focus for inputs under 16px, and the mobile
|
|
548
|
+
root font is 12-14px, so 1rem alone is not enough. */
|
|
549
|
+
@media (pointer: coarse), (max-width: 680px) {
|
|
550
|
+
#conv-filter-input { font-size: max(16px, 1rem); }
|
|
551
|
+
}
|
|
552
|
+
#conv-filter-input:focus {
|
|
553
|
+
outline: none;
|
|
554
|
+
border-color: var(--accent);
|
|
555
|
+
}
|
|
556
|
+
#conv-filter-clear {
|
|
557
|
+
position: absolute;
|
|
558
|
+
top: 50%;
|
|
559
|
+
right: 20px;
|
|
560
|
+
transform: translateY(-50%);
|
|
561
|
+
width: 28px;
|
|
562
|
+
height: 28px;
|
|
563
|
+
display: inline-flex;
|
|
564
|
+
align-items: center;
|
|
565
|
+
justify-content: center;
|
|
566
|
+
border: none;
|
|
567
|
+
border-radius: 6px;
|
|
568
|
+
background: transparent;
|
|
569
|
+
color: var(--muted);
|
|
570
|
+
cursor: pointer;
|
|
571
|
+
font-size: 1rem;
|
|
572
|
+
line-height: 1;
|
|
573
|
+
padding: 0;
|
|
574
|
+
}
|
|
575
|
+
#conv-filter-clear:hover { color: var(--text); background: var(--bg3); }
|
|
576
|
+
/* The display rule above outranks the UA [hidden] rule, same trap as the
|
|
577
|
+
other [hidden] overrides in this file. */
|
|
578
|
+
#conv-filter-clear[hidden] { display: none; }
|
|
579
|
+
#conv-filter-status {
|
|
580
|
+
position: absolute;
|
|
581
|
+
width: 1px;
|
|
582
|
+
height: 1px;
|
|
583
|
+
margin: -1px;
|
|
584
|
+
padding: 0;
|
|
585
|
+
overflow: hidden;
|
|
586
|
+
clip: rect(0 0 0 0);
|
|
587
|
+
white-space: nowrap;
|
|
588
|
+
border: 0;
|
|
589
|
+
}
|
|
590
|
+
|
|
511
591
|
#new-conv-btn {
|
|
512
592
|
margin: 0; padding: 10px 14px; background: var(--accent); color: #000;
|
|
513
593
|
border: none; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: var(--fs-meta-md);
|
|
@@ -1408,14 +1488,58 @@
|
|
|
1408
1488
|
width: 100%; max-width: 100%; box-sizing: border-box;
|
|
1409
1489
|
min-width: 0;
|
|
1410
1490
|
}
|
|
1491
|
+
/* Sticky inside the scroller: Stop on the live bubble is the only way to
|
|
1492
|
+
stop a turn, and a long turn must stay stoppable without scrolling back
|
|
1493
|
+
up. It sticks within its own bubble only, so it never rides over the
|
|
1494
|
+
messages steered in below it. The bubble's background keeps streamed
|
|
1495
|
+
text from showing through; `top` cancels #messages' padding-top (sticky
|
|
1496
|
+
insets start at the padding edge), so nothing scrolls visibly above it. */
|
|
1411
1497
|
.thinking-bubble-header {
|
|
1412
1498
|
display: flex;
|
|
1413
1499
|
align-items: center;
|
|
1414
1500
|
justify-content: space-between;
|
|
1415
1501
|
gap: 12px;
|
|
1416
1502
|
margin-bottom: 4px;
|
|
1503
|
+
position: sticky;
|
|
1504
|
+
top: -20px;
|
|
1505
|
+
z-index: 2;
|
|
1506
|
+
background: var(--bot-bg);
|
|
1507
|
+
padding: 4px 0;
|
|
1417
1508
|
}
|
|
1418
1509
|
.thinking-bubble-header:empty { display: none; margin-bottom: 0; }
|
|
1510
|
+
/* Follow buttons (thinking-follow.js): 💭 thoughts, 📃 answer, 🛠️ tools.
|
|
1511
|
+
Chips on the right of the header, Stop stays left; the armed one gets
|
|
1512
|
+
the accent ring. */
|
|
1513
|
+
.thinking-follow-group {
|
|
1514
|
+
display: inline-flex;
|
|
1515
|
+
align-items: center;
|
|
1516
|
+
gap: 4px;
|
|
1517
|
+
flex-shrink: 0;
|
|
1518
|
+
margin-left: auto; /* right edge even when there is no Stop button */
|
|
1519
|
+
}
|
|
1520
|
+
.thinking-follow-btn {
|
|
1521
|
+
display: inline-flex;
|
|
1522
|
+
align-items: center;
|
|
1523
|
+
justify-content: center;
|
|
1524
|
+
min-width: 30px;
|
|
1525
|
+
height: 26px;
|
|
1526
|
+
padding: 0 6px;
|
|
1527
|
+
font-size: var(--fs-meta-md);
|
|
1528
|
+
line-height: 1;
|
|
1529
|
+
color: var(--muted);
|
|
1530
|
+
background: transparent;
|
|
1531
|
+
border: 1px solid transparent;
|
|
1532
|
+
border-radius: 6px;
|
|
1533
|
+
cursor: pointer;
|
|
1534
|
+
transition: background 0.15s, border-color 0.15s, opacity 0.15s;
|
|
1535
|
+
}
|
|
1536
|
+
.thinking-follow-btn:hover { background: rgba(127, 127, 127, 0.12); }
|
|
1537
|
+
.thinking-follow-btn.active,
|
|
1538
|
+
.thinking-follow-btn[aria-pressed="true"] {
|
|
1539
|
+
color: var(--accent);
|
|
1540
|
+
border-color: var(--accent);
|
|
1541
|
+
background: color-mix(in srgb, var(--accent) 16%, transparent);
|
|
1542
|
+
}
|
|
1419
1543
|
.bubble-action-btn {
|
|
1420
1544
|
display: inline-flex;
|
|
1421
1545
|
align-items: center;
|
|
@@ -2239,6 +2363,61 @@
|
|
|
2239
2363
|
pointer-events: none;
|
|
2240
2364
|
}
|
|
2241
2365
|
#relay-toast.visible { display: block; }
|
|
2366
|
+
#relay-toast.has-action { pointer-events: auto; }
|
|
2367
|
+
.relay-toast-action {
|
|
2368
|
+
margin-left: 10px;
|
|
2369
|
+
border: 1px solid var(--warn);
|
|
2370
|
+
background: transparent;
|
|
2371
|
+
color: var(--warn);
|
|
2372
|
+
border-radius: 999px;
|
|
2373
|
+
padding: 4px 12px;
|
|
2374
|
+
font-size: var(--fs-meta-sm);
|
|
2375
|
+
font-weight: 600;
|
|
2376
|
+
cursor: pointer;
|
|
2377
|
+
}
|
|
2378
|
+
.relay-toast-action:hover { background: rgba(210, 153, 34, 0.2); }
|
|
2379
|
+
/* Persistent app-wide banner for a queued host suspend / relay restart
|
|
2380
|
+
(host-suspend-ui.js). Same look as the toast, one row per pending action,
|
|
2381
|
+
sits just below the toast so both stay readable; takes pointer input for
|
|
2382
|
+
its Cancel buttons. */
|
|
2383
|
+
#pending-action-banner {
|
|
2384
|
+
display: none;
|
|
2385
|
+
position: fixed;
|
|
2386
|
+
top: calc(var(--header-safe-top, 0px) + 10px);
|
|
2387
|
+
left: 50%;
|
|
2388
|
+
transform: translateX(-50%);
|
|
2389
|
+
z-index: 12590;
|
|
2390
|
+
max-width: min(640px, calc(100vw - 24px));
|
|
2391
|
+
background: var(--bg2);
|
|
2392
|
+
background: color-mix(in srgb, var(--warn) 14%, var(--bg2));
|
|
2393
|
+
border: 1px solid var(--warn);
|
|
2394
|
+
color: var(--warn);
|
|
2395
|
+
border-radius: 10px;
|
|
2396
|
+
padding: 6px 12px;
|
|
2397
|
+
font-size: var(--fs-meta-sm);
|
|
2398
|
+
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
|
|
2399
|
+
pointer-events: auto;
|
|
2400
|
+
}
|
|
2401
|
+
#pending-action-banner.visible { display: flex; flex-direction: column; gap: 4px; }
|
|
2402
|
+
#relay-toast.visible ~ #app #pending-action-banner,
|
|
2403
|
+
#relay-toast.visible + #pending-action-banner { top: calc(var(--header-safe-top, 0px) + 56px); }
|
|
2404
|
+
.pending-action-row {
|
|
2405
|
+
display: flex;
|
|
2406
|
+
align-items: center;
|
|
2407
|
+
justify-content: center;
|
|
2408
|
+
gap: 8px;
|
|
2409
|
+
flex-wrap: wrap;
|
|
2410
|
+
text-align: center;
|
|
2411
|
+
}
|
|
2412
|
+
.pending-action-text { overflow-wrap: anywhere; }
|
|
2413
|
+
.pending-action-cancel { margin-left: 0; }
|
|
2414
|
+
.suspend-host-blockers {
|
|
2415
|
+
margin: 6px 0 10px;
|
|
2416
|
+
padding-left: 20px;
|
|
2417
|
+
color: var(--text);
|
|
2418
|
+
font-size: var(--fs-meta-md);
|
|
2419
|
+
}
|
|
2420
|
+
.suspend-host-blockers li { margin: 2px 0; overflow-wrap: anywhere; }
|
|
2242
2421
|
#model-metadata-blocker {
|
|
2243
2422
|
display: none;
|
|
2244
2423
|
align-items: center;
|
|
@@ -2337,8 +2516,11 @@
|
|
|
2337
2516
|
gap: 6px;
|
|
2338
2517
|
}
|
|
2339
2518
|
/* Slash-command autocompletion: same composer-anchored overlay as the emoji
|
|
2340
|
-
popup, but list-shaped. Row highlight follows the arrow keys only.
|
|
2341
|
-
|
|
2519
|
+
popup, but list-shaped. Row highlight follows the arrow keys only. The
|
|
2520
|
+
@relay mention popup (mention-autocomplete.mjs) is its twin; the two are
|
|
2521
|
+
never open at once. */
|
|
2522
|
+
#slash-autocomplete-popup,
|
|
2523
|
+
#mention-autocomplete-popup {
|
|
2342
2524
|
display: none;
|
|
2343
2525
|
position: absolute;
|
|
2344
2526
|
left: 16px;
|
|
@@ -2353,7 +2535,9 @@
|
|
|
2353
2535
|
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
|
|
2354
2536
|
z-index: 30;
|
|
2355
2537
|
}
|
|
2356
|
-
#slash-autocomplete-popup.visible
|
|
2538
|
+
#slash-autocomplete-popup.visible,
|
|
2539
|
+
#mention-autocomplete-popup.visible { display: block; }
|
|
2540
|
+
.mention-item { align-items: center; }
|
|
2357
2541
|
.slash-item {
|
|
2358
2542
|
display: flex; align-items: baseline; gap: 10px;
|
|
2359
2543
|
padding: 6px 8px; border-radius: 8px; cursor: pointer;
|
|
@@ -2365,7 +2549,8 @@
|
|
|
2365
2549
|
.slash-item-hint { cursor: default; }
|
|
2366
2550
|
.slash-item-hint:hover { background: transparent; }
|
|
2367
2551
|
.slash-item-hint .slash-item-name { color: var(--muted); }
|
|
2368
|
-
body.shared-reader-mode #slash-autocomplete-popup
|
|
2552
|
+
body.shared-reader-mode #slash-autocomplete-popup,
|
|
2553
|
+
body.shared-reader-mode #mention-autocomplete-popup { display: none; }
|
|
2369
2554
|
#emoji-popup.visible {
|
|
2370
2555
|
display: grid;
|
|
2371
2556
|
}
|
|
@@ -2439,6 +2624,16 @@
|
|
|
2439
2624
|
(badge above tokens/elapsed/Stop) so the badge cannot float mid-row when
|
|
2440
2625
|
the wrapped description spans two lines. */
|
|
2441
2626
|
.bg-task-side { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex-shrink: 0; }
|
|
2627
|
+
.bg-task-side-head { display: flex; align-items: center; gap: 8px; }
|
|
2628
|
+
.bg-task-model { color: var(--text); font-size: var(--fs-meta-sm); white-space: nowrap; }
|
|
2629
|
+
/* Many tasks or previews must not push the composer off-screen: both lists
|
|
2630
|
+
scroll together inside the panel while the summary line stays put. The dvh term keeps the whole
|
|
2631
|
+
composer on screen in phone landscape, where 45vh alone left the input
|
|
2632
|
+
row clipped below the fold (header + the rest of the composer take ~296px). */
|
|
2633
|
+
#background-panel-body {
|
|
2634
|
+
max-height: clamp(64px, calc(100dvh - 296px), min(45vh, 420px));
|
|
2635
|
+
overflow-y: auto; overscroll-behavior: contain;
|
|
2636
|
+
}
|
|
2442
2637
|
.bg-task-side-status { display: flex; align-items: center; gap: 10px; }
|
|
2443
2638
|
/* Mobile readability: bounded wrapping instead of single-line ellipsis.
|
|
2444
2639
|
Long text clamps to two lines so the tail (the part that distinguishes
|
|
@@ -2454,6 +2649,19 @@
|
|
|
2454
2649
|
background: var(--bg3); white-space: nowrap;
|
|
2455
2650
|
}
|
|
2456
2651
|
.bg-task-detail { color: var(--muted); font-size: var(--fs-meta-sm); }
|
|
2652
|
+
/* A live tool call is one line cropped to the row: the command's head is
|
|
2653
|
+
what identifies it, and wrapping would make rows jump on every call. */
|
|
2654
|
+
.bg-task-detail.bg-task-toolcall {
|
|
2655
|
+
display: block; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;
|
|
2656
|
+
}
|
|
2657
|
+
/* A phone row is too narrow for one line to say anything; two clamped lines
|
|
2658
|
+
fit beside the side column's height anyway. */
|
|
2659
|
+
@media (max-width: 680px) {
|
|
2660
|
+
.bg-task-detail.bg-task-toolcall {
|
|
2661
|
+
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
|
2662
|
+
white-space: normal; overflow-wrap: anywhere;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2457
2665
|
.bg-task-tokens { color: var(--muted); flex-shrink: 0; font-variant-numeric: tabular-nums; }
|
|
2458
2666
|
.bg-task-elapsed { color: var(--muted); flex-shrink: 0; font-variant-numeric: tabular-nums; }
|
|
2459
2667
|
.bg-task-stop {
|
|
@@ -2492,6 +2700,68 @@
|
|
|
2492
2700
|
.preview-close:hover:not(:disabled) { border-color: #f85149; color: #f85149; }
|
|
2493
2701
|
.preview-close:disabled { opacity: 0.5; cursor: default; }
|
|
2494
2702
|
#settings-previews-empty { color: var(--muted); font-size: var(--fs-meta-sm); }
|
|
2703
|
+
/* Remote relays (Settings → Relays, the @mention popup): a health dot —
|
|
2704
|
+
online green, unreachable/refused red, never checked grey. */
|
|
2705
|
+
.remote-relay-dot {
|
|
2706
|
+
width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex-shrink: 0;
|
|
2707
|
+
background: var(--relay-tone-offline);
|
|
2708
|
+
}
|
|
2709
|
+
.remote-relay-dot[data-status="online"] { background: var(--relay-tone-online); }
|
|
2710
|
+
.remote-relay-dot[data-status="offline"],
|
|
2711
|
+
.remote-relay-dot[data-status="unauthorized"],
|
|
2712
|
+
.remote-relay-dot[data-status="error"] { background: #f85149; }
|
|
2713
|
+
#remote-relays-list { width: 100%; }
|
|
2714
|
+
.remote-relay-tag {
|
|
2715
|
+
border: 1px solid var(--border); border-radius: 999px; padding: 0 7px;
|
|
2716
|
+
font-size: var(--fs-meta-sm); color: var(--muted); background: var(--bg3); white-space: nowrap;
|
|
2717
|
+
}
|
|
2718
|
+
.remote-relay-error { color: #f85149; overflow-wrap: anywhere; }
|
|
2719
|
+
.remote-relay-http-warning { color: var(--warn); opacity: 1; }
|
|
2720
|
+
label.remote-relay-permission { display: flex; align-items: center; gap: 6px; font-size: var(--fs-meta-sm); color: var(--muted); font-weight: normal; }
|
|
2721
|
+
.remote-relay-permission select {
|
|
2722
|
+
background: var(--bg); color: var(--text); border: 1px solid var(--border);
|
|
2723
|
+
border-radius: 6px; padding: 2px 6px; font-size: var(--fs-meta-sm);
|
|
2724
|
+
}
|
|
2725
|
+
.remote-relays-self-name-row { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
|
|
2726
|
+
.remote-relays-self-name { font-family: var(--mono, monospace); color: var(--text); }
|
|
2727
|
+
#remote-relays-public-url-input,
|
|
2728
|
+
#remote-relays-add-url-input,
|
|
2729
|
+
#remote-relays-add-token-input {
|
|
2730
|
+
background: var(--bg); color: var(--text); border: 1px solid var(--border);
|
|
2731
|
+
border-radius: 6px; padding: 6px 12px; outline: none; font-size: var(--fs-meta-md);
|
|
2732
|
+
width: min(420px, 100%); box-sizing: border-box;
|
|
2733
|
+
}
|
|
2734
|
+
.remote-relays-add-token-row { display: flex; flex-direction: column; gap: 6px; width: 100%; }
|
|
2735
|
+
.remote-relays-add-token-row[hidden] { display: none; }
|
|
2736
|
+
.remote-relays-add-token-row label { font-weight: normal; color: var(--muted); font-size: var(--fs-meta-sm); }
|
|
2737
|
+
.remote-relays-link-btn {
|
|
2738
|
+
background: none; border: none; padding: 0; cursor: pointer;
|
|
2739
|
+
color: var(--accent); font-size: var(--fs-meta-sm); text-decoration: underline;
|
|
2740
|
+
}
|
|
2741
|
+
.remote-relays-link-btn[hidden] { display: none; }
|
|
2742
|
+
.settings-row label.remote-relays-pair-back { display: flex; align-items: center; gap: 8px; font-weight: normal; }
|
|
2743
|
+
#remote-relays-add-status[data-state="working"] { color: var(--muted); }
|
|
2744
|
+
#remote-relays-add-status[data-state="added"],
|
|
2745
|
+
#remote-relays-add-status[data-state="paired"] { color: #3fb950; }
|
|
2746
|
+
#remote-relays-add-status[data-state="pair-failed"],
|
|
2747
|
+
#remote-relays-add-status[data-state="needs-token"] { color: var(--warn); }
|
|
2748
|
+
#remote-relays-add-status[data-state="self"],
|
|
2749
|
+
#remote-relays-add-status[data-state="error"] { color: #f85149; }
|
|
2750
|
+
/* Provenance of a prompt another relay's agent sent (remote-relay-origin-view.mjs). */
|
|
2751
|
+
.msg-origin { margin-bottom: 6px; max-width: 100%; }
|
|
2752
|
+
.msg-origin-badge {
|
|
2753
|
+
display: inline-block; max-width: 100%; box-sizing: border-box;
|
|
2754
|
+
padding: 1px 8px; border-radius: 999px; font-size: var(--fs-meta-sm); line-height: 1.5;
|
|
2755
|
+
border: 1px solid color-mix(in srgb, currentColor 40%, transparent);
|
|
2756
|
+
background: color-mix(in srgb, currentColor 12%, transparent);
|
|
2757
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
2758
|
+
}
|
|
2759
|
+
.msg.user .msg-bubble a.msg-origin-badge { text-decoration: none; }
|
|
2760
|
+
.msg.user .msg-bubble a.msg-origin-badge:hover { text-decoration: underline; }
|
|
2761
|
+
.conv-origin-marker {
|
|
2762
|
+
flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
2763
|
+
color: var(--muted); font-size: var(--fs-ui-chip);
|
|
2764
|
+
}
|
|
2495
2765
|
/* Transcript card: a preview published during this turn, pinned into the
|
|
2496
2766
|
response bubble. Compact single row; closed previews keep the label but
|
|
2497
2767
|
lose the dead link. */
|
|
@@ -2535,6 +2805,30 @@
|
|
|
2535
2805
|
background: rgba(63, 185, 80, 0.12); color: var(--relay-tone-online);
|
|
2536
2806
|
border-radius: 6px; padding: 0 6px; font-size: var(--fs-meta-sm);
|
|
2537
2807
|
}
|
|
2808
|
+
/* Mid-turn steering. The CLI usually FOLDS a steered message into the
|
|
2809
|
+
running turn and answers everything with one reply; the steered row then
|
|
2810
|
+
settles as a compact marker under its own message (kind='folded'). More
|
|
2811
|
+
rarely the turn is handed off: the interrupted reply settles as
|
|
2812
|
+
kind='absorbed' and the steered message carries the continuing reply. A
|
|
2813
|
+
steer cut off by Stop settles as kind='stopped' with a Resend button.
|
|
2814
|
+
Markers are notes, not answers, so they render muted and compact.
|
|
2815
|
+
steered-turn-merge.mjs adds the adjacency classes. */
|
|
2816
|
+
.msg.assistant.msg-absorbed .msg-bubble,
|
|
2817
|
+
.msg.assistant.msg-folded .msg-bubble,
|
|
2818
|
+
.msg.assistant.msg-steer-stopped .msg-bubble {
|
|
2819
|
+
font-size: var(--fs-meta-md); color: var(--muted); font-style: italic;
|
|
2820
|
+
padding: 6px 12px; opacity: 0.85;
|
|
2821
|
+
}
|
|
2822
|
+
.msg.assistant.msg-absorbed { margin-top: -4px; }
|
|
2823
|
+
/* Attached to the steered message above it (the transcript gap is 12px). */
|
|
2824
|
+
.msg.assistant.msg-folded,
|
|
2825
|
+
.msg.assistant.msg-steer-stopped { margin-top: -8px; }
|
|
2826
|
+
.msg.assistant.msg-folded .msg-label,
|
|
2827
|
+
.msg.assistant.msg-steer-stopped .msg-label { display: none; }
|
|
2828
|
+
.msg.assistant.msg-steer-stopped .msg-bubble-actions { font-style: normal; }
|
|
2829
|
+
.msg.user.msg-steered .msg-bubble { border-style: dashed; }
|
|
2830
|
+
.msg.user.msg-steered .msg-label::after { content: ' · steered mid-turn'; }
|
|
2831
|
+
.msg.assistant.msg-steered-continuation { margin-top: -2px; }
|
|
2538
2832
|
body.shared-reader-mode #background-tasks-panel { display: none; }
|
|
2539
2833
|
#model-pricing-details {
|
|
2540
2834
|
display: none; margin-top: 6px; color: var(--muted); font-size: var(--fs-meta-sm);
|
|
@@ -4197,6 +4491,43 @@
|
|
|
4197
4491
|
color: #000;
|
|
4198
4492
|
font-weight: 700;
|
|
4199
4493
|
}
|
|
4494
|
+
/* Multi-select cards: the same pills, with a checkmark instead of one-shot
|
|
4495
|
+
buttons, so several answers can be ticked before one Reply. */
|
|
4496
|
+
.relay-question-choice-check {
|
|
4497
|
+
display: inline-flex;
|
|
4498
|
+
align-items: center;
|
|
4499
|
+
gap: 8px;
|
|
4500
|
+
user-select: none;
|
|
4501
|
+
}
|
|
4502
|
+
.relay-question-choice-check input[type="checkbox"] {
|
|
4503
|
+
margin: 0;
|
|
4504
|
+
width: 16px;
|
|
4505
|
+
height: 16px;
|
|
4506
|
+
accent-color: var(--accent);
|
|
4507
|
+
cursor: pointer;
|
|
4508
|
+
}
|
|
4509
|
+
.relay-question-choice-check:has(input:checked) {
|
|
4510
|
+
border-color: var(--accent);
|
|
4511
|
+
background: rgba(88, 166, 255, 0.12);
|
|
4512
|
+
}
|
|
4513
|
+
/* The "Select several" switch on Copilot choice cards. */
|
|
4514
|
+
.relay-question-multi-toggle {
|
|
4515
|
+
display: inline-flex;
|
|
4516
|
+
align-items: center;
|
|
4517
|
+
gap: 6px;
|
|
4518
|
+
margin-top: 8px;
|
|
4519
|
+
font-size: var(--fs-meta-sm);
|
|
4520
|
+
color: var(--muted, var(--text));
|
|
4521
|
+
cursor: pointer;
|
|
4522
|
+
user-select: none;
|
|
4523
|
+
}
|
|
4524
|
+
.relay-question-multi-toggle input[type="checkbox"] {
|
|
4525
|
+
margin: 0;
|
|
4526
|
+
width: 16px;
|
|
4527
|
+
height: 16px;
|
|
4528
|
+
accent-color: var(--accent);
|
|
4529
|
+
cursor: pointer;
|
|
4530
|
+
}
|
|
4200
4531
|
.relay-question-reply {
|
|
4201
4532
|
display: flex;
|
|
4202
4533
|
gap: 8px;
|
|
@@ -4397,10 +4728,12 @@
|
|
|
4397
4728
|
font-size: var(--fs-meta-md);
|
|
4398
4729
|
min-height: 36px;
|
|
4399
4730
|
}
|
|
4400
|
-
.thinking-bubble-header { margin-bottom: 6px; }
|
|
4731
|
+
.thinking-bubble-header { margin-bottom: 6px; top: -12px; }
|
|
4401
4732
|
.sidebar-header { padding: var(--sidebar-header-top-pad-mobile) 12px 12px; gap: 8px; }
|
|
4402
4733
|
#new-conv-btn { padding: 9px 12px; border-radius: 8px; font-size: var(--fs-meta-md); }
|
|
4403
4734
|
#status-btn { min-width: 72px; padding: 9px 10px; font-size: var(--fs-meta-md); }
|
|
4735
|
+
#conv-filter-row { padding: 8px 12px; }
|
|
4736
|
+
#conv-filter-clear { right: 16px; }
|
|
4404
4737
|
#conv-list { padding: 0 5px 8px; }
|
|
4405
4738
|
.conv-item { padding: 6px 11px; border-radius: 8px; }
|
|
4406
4739
|
.conv-title {
|
|
@@ -4916,6 +5249,7 @@
|
|
|
4916
5249
|
<div id="chat-actions-menu-backdrop" onclick="window.closeChatActionsMenu && window.closeChatActionsMenu()"></div>
|
|
4917
5250
|
<div id="font-scale-indicator" aria-live="polite" aria-atomic="true" hidden>100%</div>
|
|
4918
5251
|
<div id="relay-toast" role="status" aria-live="polite"></div>
|
|
5252
|
+
<div id="pending-action-banner" role="status" aria-live="polite" hidden></div>
|
|
4919
5253
|
|
|
4920
5254
|
<!-- Main App -->
|
|
4921
5255
|
<div id="app">
|
|
@@ -4928,6 +5262,11 @@
|
|
|
4928
5262
|
<button id="install-btn" class="header-icon-btn" onclick="promptInstallApp()" title="Install app to home screen" aria-label="Install app">⬇</button>
|
|
4929
5263
|
</div>
|
|
4930
5264
|
</div>
|
|
5265
|
+
<div id="conv-filter-row">
|
|
5266
|
+
<input id="conv-filter-input" type="text" placeholder="Filter conversations…" autocomplete="off" spellcheck="false" aria-label="Filter conversations by title">
|
|
5267
|
+
<button id="conv-filter-clear" type="button" title="Clear filter" aria-label="Clear filter" hidden>×</button>
|
|
5268
|
+
<div id="conv-filter-status" role="status" aria-live="polite"></div>
|
|
5269
|
+
</div>
|
|
4931
5270
|
<div id="conv-list"></div>
|
|
4932
5271
|
</div>
|
|
4933
5272
|
<div id="sidebar-resizer" role="separator" aria-orientation="vertical" aria-label="Resize conversations sidebar"></div>
|
|
@@ -4991,15 +5330,18 @@
|
|
|
4991
5330
|
<div id="image-edit-target" aria-live="polite"></div>
|
|
4992
5331
|
<div id="emoji-popup" aria-hidden="true"></div>
|
|
4993
5332
|
<div id="slash-autocomplete-popup" aria-hidden="true" role="listbox"></div>
|
|
5333
|
+
<div id="mention-autocomplete-popup" aria-hidden="true" role="listbox" aria-label="Remote relays"></div>
|
|
4994
5334
|
<details id="background-tasks-panel" hidden>
|
|
4995
5335
|
<summary id="background-tasks-summary"></summary>
|
|
4996
|
-
<div id="background-
|
|
4997
|
-
|
|
5336
|
+
<div id="background-panel-body">
|
|
5337
|
+
<div id="background-previews-list"></div>
|
|
5338
|
+
<div id="background-tasks-list"></div>
|
|
5339
|
+
</div>
|
|
4998
5340
|
</details>
|
|
4999
5341
|
<div class="input-row">
|
|
5000
5342
|
<input id="image-input" type="file" multiple hidden onchange="handleAttachmentInput(this.files); this.value='';">
|
|
5001
5343
|
<button id="emoji-btn" onclick="toggleEmojiPicker()" title="Emoji picker (Tip: Win + .)">🙂</button>
|
|
5002
|
-
<textarea id="msg-input" placeholder="Message Copilot…" rows="1" onkeydown="handleKey(event)" oninput="autoResize(this); updateComposerSlashMenu(this)"></textarea>
|
|
5344
|
+
<textarea id="msg-input" placeholder="Message Copilot…" rows="1" aria-autocomplete="list" onkeydown="handleKey(event)" oninput="autoResize(this); updateComposerSlashMenu(this)"></textarea>
|
|
5003
5345
|
<select id="mode-select" title="Mode">
|
|
5004
5346
|
<option value="agent" selected>Agent</option>
|
|
5005
5347
|
<option value="ask">Ask</option>
|
|
@@ -5136,6 +5478,16 @@
|
|
|
5136
5478
|
tabindex="-1"
|
|
5137
5479
|
data-settings-tab="providers"
|
|
5138
5480
|
>Providers</button>
|
|
5481
|
+
<button
|
|
5482
|
+
id="settings-tab-relays"
|
|
5483
|
+
class="settings-tab"
|
|
5484
|
+
type="button"
|
|
5485
|
+
role="tab"
|
|
5486
|
+
aria-selected="false"
|
|
5487
|
+
aria-controls="settings-panel-relays"
|
|
5488
|
+
tabindex="-1"
|
|
5489
|
+
data-settings-tab="relays"
|
|
5490
|
+
>Relays</button>
|
|
5139
5491
|
<button
|
|
5140
5492
|
id="settings-tab-previews"
|
|
5141
5493
|
class="settings-tab"
|
|
@@ -5214,20 +5566,20 @@
|
|
|
5214
5566
|
</div>
|
|
5215
5567
|
<div class="settings-help" style="margin-top: 6px;">A hard cap on how long one turn may run before the relay requeues it. Turns are already kept alive by worker heartbeats, and a turn waiting on your answer to a question is never cut off — so this only catches a worker that has hung. Set to 0 for no limit.</div>
|
|
5216
5568
|
<div class="settings-row settings-row-column" style="margin-top: 12px; margin-bottom: 0;">
|
|
5217
|
-
<label for="background-task-timeout-slider">Background task timeout <span id="background-task-timeout-value">
|
|
5569
|
+
<label for="background-task-timeout-slider">Background task timeout <span id="background-task-timeout-value">4 h</span></label>
|
|
5218
5570
|
<input
|
|
5219
5571
|
id="background-task-timeout-slider"
|
|
5220
5572
|
type="range"
|
|
5221
5573
|
min="0"
|
|
5222
5574
|
max="600"
|
|
5223
5575
|
step="5"
|
|
5224
|
-
value="
|
|
5576
|
+
value="240"
|
|
5225
5577
|
aria-label="Background task timeout in minutes"
|
|
5226
5578
|
oninput="previewBackgroundTaskTimeoutSetting(this.value)"
|
|
5227
5579
|
onchange="updateBackgroundTaskTimeoutSetting(this.value)"
|
|
5228
5580
|
>
|
|
5229
5581
|
</div>
|
|
5230
|
-
<div class="settings-help" style="margin-top: 6px;">How long background tasks
|
|
5582
|
+
<div class="settings-help" style="margin-top: 6px;">How long background tasks may keep a session alive after its turn ends: backgrounded commands, subagents and monitors on Claude; background agents and detached shells on Copilot's SDK engine. When the limit is reached the tasks are stopped. Set to 0 for no limit — tasks then run until they finish or you stop them from the composer panel.</div>
|
|
5231
5583
|
<div id="windows-autostart-setting" hidden>
|
|
5232
5584
|
<div class="settings-row settings-row-column" style="margin-top: 12px; margin-bottom: 0;">
|
|
5233
5585
|
<label>Autostart (Windows)</label>
|
|
@@ -5251,16 +5603,16 @@
|
|
|
5251
5603
|
</div>
|
|
5252
5604
|
</div>
|
|
5253
5605
|
<div class="settings-row settings-row-column" style="margin-top: 12px; margin-bottom: 0;">
|
|
5254
|
-
<label for="pwa-app-name-input">
|
|
5606
|
+
<label for="pwa-app-name-input">Relay name</label>
|
|
5255
5607
|
<input
|
|
5256
5608
|
id="pwa-app-name-input"
|
|
5257
5609
|
type="text"
|
|
5258
5610
|
maxlength="60"
|
|
5259
5611
|
placeholder="OAR"
|
|
5260
|
-
aria-label="
|
|
5612
|
+
aria-label="Relay name"
|
|
5261
5613
|
onchange="updatePwaAppName(this.value)"
|
|
5262
5614
|
>
|
|
5263
|
-
<div class="settings-help">
|
|
5615
|
+
<div class="settings-help">Shown to other relays and used as the installed app's name. On Android, a rename reaches an already-installed app after a short delay, with one confirmation prompt showing the new name.</div>
|
|
5264
5616
|
</div>
|
|
5265
5617
|
<div class="settings-row" style="margin-top: 12px; margin-bottom: 0;">
|
|
5266
5618
|
<label for="update-auto-check-toggle">Check for updates automatically</label>
|
|
@@ -5402,14 +5754,14 @@
|
|
|
5402
5754
|
<div class="settings-row settings-row-column" style="margin-top: 12px; margin-bottom: 0;">
|
|
5403
5755
|
<label for="copilot-engine-select">Copilot engine</label>
|
|
5404
5756
|
<select id="copilot-engine-select" aria-label="Copilot engine">
|
|
5405
|
-
<option value="extension">Extension
|
|
5406
|
-
<option value="sdk">SDK —
|
|
5757
|
+
<option value="extension">Extension</option>
|
|
5758
|
+
<option value="sdk">SDK — default</option>
|
|
5407
5759
|
</select>
|
|
5408
5760
|
<div id="copilot-settings-status" class="settings-help settings-provider-status">Loading Copilot settings…</div>
|
|
5409
5761
|
<div style="display: flex; gap: 8px; margin-top: 8px;">
|
|
5410
5762
|
<button id="copilot-save-btn" type="button" class="summary-btn" onclick="saveCopilotSettings()">Save Copilot settings</button>
|
|
5411
5763
|
</div>
|
|
5412
|
-
<div class="settings-help">Chooses how GitHub Copilot conversations run. <strong>
|
|
5764
|
+
<div class="settings-help">Chooses how GitHub Copilot conversations run. <strong>SDK</strong> is the default: a headless worker per conversation with mid-turn steering and background task cards — no CLI extension to install, but no tmux inspector for those sessions. <strong>Extension</strong> runs the Copilot CLI in a terminal session, which you can attach to with the tmux inspector. A relay that cannot run the SDK engine uses Extension. Applies to newly started conversations; sessions already running keep their engine until their worker restarts.</div>
|
|
5413
5765
|
</div>
|
|
5414
5766
|
</div>
|
|
5415
5767
|
<div
|
|
@@ -5752,6 +6104,96 @@
|
|
|
5752
6104
|
</div>
|
|
5753
6105
|
</div>
|
|
5754
6106
|
</div>
|
|
6107
|
+
<div
|
|
6108
|
+
id="settings-panel-relays"
|
|
6109
|
+
class="settings-panel"
|
|
6110
|
+
role="tabpanel"
|
|
6111
|
+
tabindex="0"
|
|
6112
|
+
aria-labelledby="settings-tab-relays"
|
|
6113
|
+
data-settings-panel="relays"
|
|
6114
|
+
hidden
|
|
6115
|
+
>
|
|
6116
|
+
<div id="remote-relays-self-section" class="settings-row settings-row-column" style="margin-top: 12px; margin-bottom: 0;">
|
|
6117
|
+
<label>This relay</label>
|
|
6118
|
+
<div class="remote-relays-self-name-row">
|
|
6119
|
+
<span id="remote-relays-self-name" class="remote-relays-self-name">—</span>
|
|
6120
|
+
<span class="settings-help">Change it under General → Relay name.</span>
|
|
6121
|
+
</div>
|
|
6122
|
+
</div>
|
|
6123
|
+
<div class="settings-row settings-row-column" style="margin-top: 12px; margin-bottom: 0;">
|
|
6124
|
+
<label for="remote-relays-public-url-input">Public address</label>
|
|
6125
|
+
<input
|
|
6126
|
+
id="remote-relays-public-url-input"
|
|
6127
|
+
type="url"
|
|
6128
|
+
inputmode="url"
|
|
6129
|
+
autocomplete="off"
|
|
6130
|
+
autocapitalize="off"
|
|
6131
|
+
spellcheck="false"
|
|
6132
|
+
placeholder="https://…"
|
|
6133
|
+
aria-label="Public address of this relay"
|
|
6134
|
+
onchange="saveRemoteRelayPublicUrl(this.value)"
|
|
6135
|
+
>
|
|
6136
|
+
<div class="settings-help">How other relays reach this one — the address you open this relay at from another device. Needed for "Also add this relay there".</div>
|
|
6137
|
+
<div id="remote-relays-self-status" class="settings-help settings-provider-status" role="status" aria-live="polite" hidden></div>
|
|
6138
|
+
</div>
|
|
6139
|
+
<div class="settings-row" style="margin-top: 12px; margin-bottom: 0;">
|
|
6140
|
+
<label for="remote-relays-inbound-toggle">Accept prompts from other relays' agents</label>
|
|
6141
|
+
<input
|
|
6142
|
+
id="remote-relays-inbound-toggle"
|
|
6143
|
+
type="checkbox"
|
|
6144
|
+
onchange="toggleRemoteRelayInbound(this.checked)"
|
|
6145
|
+
aria-label="Accept prompts from other relays' agents"
|
|
6146
|
+
checked
|
|
6147
|
+
>
|
|
6148
|
+
</div>
|
|
6149
|
+
<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>
|
|
6150
|
+
<div id="remote-relays-list-section" class="settings-row settings-row-column" style="margin-top: 16px; margin-bottom: 0;">
|
|
6151
|
+
<label>Remote relays</label>
|
|
6152
|
+
<div id="remote-relays-list"></div>
|
|
6153
|
+
<div id="remote-relays-empty" class="settings-help">Loading remote relays…</div>
|
|
6154
|
+
<div id="remote-relays-status" class="settings-help settings-provider-status" role="status" aria-live="polite" hidden></div>
|
|
6155
|
+
<div class="settings-help">Agents in any session here can work on these relays once you mention one in the conversation (for example <code>@name</code>). "Agents may" limits what they can do there.</div>
|
|
6156
|
+
</div>
|
|
6157
|
+
<div id="remote-relays-add-section" class="settings-row settings-row-column" style="margin-top: 16px; margin-bottom: 0;">
|
|
6158
|
+
<label for="remote-relays-add-url-input">Add a relay</label>
|
|
6159
|
+
<input
|
|
6160
|
+
id="remote-relays-add-url-input"
|
|
6161
|
+
type="url"
|
|
6162
|
+
inputmode="url"
|
|
6163
|
+
autocomplete="off"
|
|
6164
|
+
autocapitalize="off"
|
|
6165
|
+
spellcheck="false"
|
|
6166
|
+
placeholder="Paste the other relay's web address"
|
|
6167
|
+
aria-label="Paste the other relay's web address"
|
|
6168
|
+
onkeydown="if (event.key === 'Enter') { event.preventDefault(); addRemoteRelayFromForm(); }"
|
|
6169
|
+
>
|
|
6170
|
+
<div id="remote-relays-add-token-row" class="remote-relays-add-token-row" hidden>
|
|
6171
|
+
<label for="remote-relays-add-token-input">Token of the other relay</label>
|
|
6172
|
+
<input
|
|
6173
|
+
id="remote-relays-add-token-input"
|
|
6174
|
+
type="password"
|
|
6175
|
+
autocomplete="off"
|
|
6176
|
+
autocapitalize="off"
|
|
6177
|
+
spellcheck="false"
|
|
6178
|
+
placeholder="Only needed when it differs from this relay's token"
|
|
6179
|
+
aria-label="Token of the other relay"
|
|
6180
|
+
onkeydown="if (event.key === 'Enter') { event.preventDefault(); addRemoteRelayFromForm(); }"
|
|
6181
|
+
>
|
|
6182
|
+
</div>
|
|
6183
|
+
<button id="remote-relays-use-token-btn" class="remote-relays-link-btn" type="button" onclick="showRemoteRelayTokenField()">Use a different token</button>
|
|
6184
|
+
<label class="remote-relays-pair-back">
|
|
6185
|
+
<input id="remote-relays-pair-back-toggle" type="checkbox" checked>
|
|
6186
|
+
Also add this relay there
|
|
6187
|
+
</label>
|
|
6188
|
+
<div id="remote-relays-pair-back-hint" class="settings-help">
|
|
6189
|
+
The other relay gets this relay's public address and, if it uses a different token, this
|
|
6190
|
+
relay's token too: its agents can then work here. Removing a relay here does not revoke
|
|
6191
|
+
that; rotate this relay's token to lock it out.
|
|
6192
|
+
</div>
|
|
6193
|
+
<button id="remote-relays-add-btn" class="chat-title-action-btn" type="button" onclick="addRemoteRelayFromForm()">Add</button>
|
|
6194
|
+
<div id="remote-relays-add-status" class="settings-help settings-provider-status" role="status" aria-live="polite" hidden></div>
|
|
6195
|
+
</div>
|
|
6196
|
+
</div>
|
|
5755
6197
|
<div
|
|
5756
6198
|
id="settings-panel-previews"
|
|
5757
6199
|
class="settings-panel"
|
|
@@ -5821,6 +6263,10 @@
|
|
|
5821
6263
|
<label for="push-event-cli-offline-toggle">CLI went offline</label>
|
|
5822
6264
|
<input id="push-event-cli-offline-toggle" type="checkbox" onchange="updatePushPreferencesFromControls()" disabled>
|
|
5823
6265
|
</div>
|
|
6266
|
+
<div class="settings-row" style="width: 100%; margin-top: 6px; margin-bottom: 0;">
|
|
6267
|
+
<label for="push-event-host-suspend-toggle">Host suspend fired or was dropped</label>
|
|
6268
|
+
<input id="push-event-host-suspend-toggle" type="checkbox" onchange="updatePushPreferencesFromControls()" checked disabled>
|
|
6269
|
+
</div>
|
|
5824
6270
|
<label style="margin-top: 10px;">Devices</label>
|
|
5825
6271
|
<div id="push-device-list" style="width: 100%;"></div>
|
|
5826
6272
|
<div class="settings-help">Notifications are only sent while no device has the app in the foreground. Content defaults to generic text; message previews are opt-in per device.</div>
|