@oar-sh/oar 0.9.9 → 0.9.10
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/CHANGELOG.md +247 -0
- package/README.md +78 -9
- package/bin/bat/oar.bat +46 -46
- package/package.json +2 -2
- package/server/README.md +286 -3
- package/server/claude-worker/claude-remote-relay-tool.mjs +10 -0
- package/server/claude-worker/claude-session-process.mjs +35 -1
- package/server/claude-worker/claude-turn-publisher.mjs +3 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +31 -4
- package/server/claude-worker/subagent-timeline-recorder.mjs +572 -0
- package/server/copilot-worker/copilot-sdk-session-process.mjs +3 -1
- package/server/cursor-worker/cursor-sdk-adapter.mjs +56 -2
- package/server/db-schema.mjs +36 -0
- package/server/migrations/0009-subagent-timeline.mjs +135 -0
- package/server/migrations/0010-subagent-timeline-pruning.mjs +89 -0
- package/server/migrations/0011-relay-event-subscriptions.mjs +125 -0
- package/server/migrations/0012-cross-relay-event-subscriptions.mjs +116 -0
- package/server/public/app/agent-exchange-fold-view.mjs +248 -0
- package/server/public/app/api-client.js +61 -0
- package/server/public/app/archive-confirm.mjs +28 -0
- package/server/public/app/ask-user-view.js +3 -0
- package/server/public/app/attachments-view.js +516 -6
- package/server/public/app/background-tasks-view.mjs +18 -26
- package/server/public/app/bootstrap.js +186 -35
- package/server/public/app/chat-actions-menu-state.mjs +116 -0
- package/server/public/app/claude-cloud-conversation-ui.js +10 -22
- package/server/public/app/composer-control-state.mjs +16 -0
- package/server/public/app/composer-keyboard.mjs +54 -0
- package/server/public/app/conversation-view.js +361 -85
- package/server/public/app/explorer-edit-state.mjs +68 -0
- package/server/public/app/host-suspend-ui.js +6 -3
- package/server/public/app/journal-view.js +146 -26
- package/server/public/app/no-conversation-state.mjs +58 -0
- package/server/public/app/provider-readiness.mjs +120 -0
- package/server/public/app/relay-board-view.js +3 -0
- package/server/public/app/relay-event-subscriptions-ui.js +184 -0
- package/server/public/app/relay-event-view.mjs +234 -0
- package/server/public/app/settings-modal.js +53 -1
- package/server/public/app/socket-handlers.js +59 -28
- package/server/public/app/store.js +19 -2
- package/server/public/app/subagent-row.mjs +138 -0
- package/server/public/app/subagent-timeline-view.mjs +186 -0
- package/server/public/app/subagent-viewer-watch.mjs +50 -0
- package/server/public/app/subagent-viewer.mjs +534 -0
- package/server/public/app/task-row-format.mjs +29 -0
- package/server/public/app/thinking-follow.js +4 -0
- package/server/public/app/transcript-jump-bottom-view.mjs +290 -0
- package/server/public/app/transcript-jump-bottom.mjs +99 -0
- package/server/public/app/transcript-reload-freshness.mjs +25 -0
- package/server/public/app/update-ui.js +1 -1
- package/server/public/app/viewer-history.mjs +5 -3
- package/server/public/index.html +396 -5
- package/server/repositories/message-repository.mjs +29 -3
- package/server/repositories/question-repository.mjs +9 -3
- package/server/repositories/remote-relay-repository.mjs +6 -2
- package/server/repositories/subagent-timeline-statements.mjs +170 -0
- package/server/routes/ask-user-routes.mjs +3 -0
- package/server/routes/messages-routes.mjs +515 -50
- package/server/routes/relay-event-routes.mjs +157 -0
- package/server/routes/remote-relay-routes.mjs +15 -0
- package/server/routes/sessions-routes.mjs +72 -0
- package/server/server-runtime.mjs +245 -62
- package/server/services/explorer-entry-name.mjs +26 -0
- package/server/services/explorer-mutations.mjs +122 -0
- package/server/services/preview-proxy-server.mjs +41 -23
- package/server/services/push-dispatch-service.mjs +18 -0
- package/server/services/relay-event-link-service.mjs +289 -0
- package/server/services/relay-event-service.mjs +1935 -0
- package/server/services/relay-shutdown-gate.mjs +31 -0
- package/server/services/remote-relay-caller-context.mjs +52 -7
- package/server/services/remote-relay-dispatcher.mjs +494 -2
- package/server/services/remote-relay-registry-service.mjs +11 -1
- package/server/services/session-activity-tracker.mjs +487 -0
- package/server/services/session-history-refresh-service.mjs +7 -0
- package/server/services/stale-attempt-log-limiter.mjs +96 -0
- package/server/services/stale-recovery-clock-guard.mjs +57 -0
- package/server/services/stale-subagent-run-sweep.mjs +109 -0
- package/server/services/subagent-record-retention.mjs +164 -0
- package/server/services/subagent-timeline-service.mjs +328 -0
- package/server/services/subagent-viewer-rooms.mjs +119 -0
- package/server/services/upgrade-socket-guard.mjs +16 -0
- package/shared/relay-events.mjs +371 -0
- package/shared/remote-relay-contract.mjs +157 -5
- package/shared/subagent-record-retention.mjs +31 -0
- package/shared/subagent-timeline.mjs +79 -0
package/server/public/index.html
CHANGED
|
@@ -587,6 +587,8 @@
|
|
|
587
587
|
/* The display rule above outranks the UA [hidden] rule, same trap as the
|
|
588
588
|
other [hidden] overrides in this file. */
|
|
589
589
|
#pinned-messages-btn[hidden] { display: none; }
|
|
590
|
+
/* Same trap: without this the ⋯ trigger stays drawn as a dead button. */
|
|
591
|
+
#chat-actions-menu-btn[hidden] { display: none; }
|
|
590
592
|
#cli-dot {
|
|
591
593
|
width: 12px;
|
|
592
594
|
height: 12px;
|
|
@@ -868,7 +870,8 @@
|
|
|
868
870
|
/* The conversation context menu (context-menu.mjs): right-click / long press. */
|
|
869
871
|
.context-menu {
|
|
870
872
|
position: fixed;
|
|
871
|
-
|
|
873
|
+
/* Above every modal: the explorer (10001, 12400 as a CWD picker) opens one too. */
|
|
874
|
+
z-index: 13000;
|
|
872
875
|
min-width: 200px;
|
|
873
876
|
max-width: min(320px, calc(100vw - 8px));
|
|
874
877
|
padding: 4px;
|
|
@@ -1442,7 +1445,27 @@
|
|
|
1442
1445
|
}
|
|
1443
1446
|
.empty-state .icon { font-size: 3rem; margin-bottom: 12px; }
|
|
1444
1447
|
.empty-state h3 { font-size: var(--fs-core-chat-title); margin-bottom: 8px; color: var(--text); }
|
|
1445
|
-
.empty-state p { font-size: var(--fs-meta-md); line-height: 1.5; }
|
|
1448
|
+
.empty-state p { font-size: var(--fs-meta-md); line-height: 1.5; max-width: 36em; }
|
|
1449
|
+
/* The actions of the "no conversation open" screen: Settings, New chat, the
|
|
1450
|
+
list on the phone. Same shapes as .summary-btn and #new-conv-btn. */
|
|
1451
|
+
.empty-state-actions {
|
|
1452
|
+
display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; align-items: center;
|
|
1453
|
+
margin-top: 18px;
|
|
1454
|
+
}
|
|
1455
|
+
.empty-state-btn {
|
|
1456
|
+
border: 1px solid var(--border); background: var(--bg2); color: var(--text);
|
|
1457
|
+
border-radius: 8px; height: 36px; padding: 0 14px; font-size: var(--fs-meta-md);
|
|
1458
|
+
cursor: pointer;
|
|
1459
|
+
}
|
|
1460
|
+
.empty-state-btn:hover { border-color: var(--accent); color: var(--accent); }
|
|
1461
|
+
.empty-state-btn.empty-state-btn-accent {
|
|
1462
|
+
background: var(--accent); color: #000; border-color: var(--accent); font-weight: 600;
|
|
1463
|
+
}
|
|
1464
|
+
.empty-state-btn.empty-state-btn-accent:hover { color: #000; opacity: 0.85; }
|
|
1465
|
+
.empty-state-btn.empty-state-link {
|
|
1466
|
+
border-color: transparent; background: transparent; color: var(--muted); text-decoration: underline;
|
|
1467
|
+
}
|
|
1468
|
+
.empty-state-btn.empty-state-link:hover { color: var(--accent); }
|
|
1446
1469
|
|
|
1447
1470
|
.msg {
|
|
1448
1471
|
display: flex;
|
|
@@ -2147,6 +2170,69 @@
|
|
|
2147
2170
|
opacity: 1;
|
|
2148
2171
|
transition: width 0.2s ease, background-size 0.2s ease, box-shadow 0.2s ease;
|
|
2149
2172
|
}
|
|
2173
|
+
/* Jump to the end of the transcript (transcript-jump-bottom-view.mjs).
|
|
2174
|
+
Anchored to the composer's top edge, so it rides above the composer and
|
|
2175
|
+
the background-task panel at whatever height they have, and above the
|
|
2176
|
+
on-screen keyboard with them. Below the composer popups (z-index 30). */
|
|
2177
|
+
#jump-to-bottom-btn {
|
|
2178
|
+
position: absolute;
|
|
2179
|
+
right: calc(20px + env(safe-area-inset-right, 0px));
|
|
2180
|
+
bottom: calc(100% + 12px);
|
|
2181
|
+
z-index: 25;
|
|
2182
|
+
width: 40px;
|
|
2183
|
+
height: 40px;
|
|
2184
|
+
padding: 0;
|
|
2185
|
+
border-radius: 50%;
|
|
2186
|
+
border: 1px solid var(--border);
|
|
2187
|
+
background: var(--bg2);
|
|
2188
|
+
color: var(--text);
|
|
2189
|
+
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
|
|
2190
|
+
display: flex;
|
|
2191
|
+
align-items: center;
|
|
2192
|
+
justify-content: center;
|
|
2193
|
+
cursor: pointer;
|
|
2194
|
+
touch-action: manipulation;
|
|
2195
|
+
-webkit-tap-highlight-color: transparent;
|
|
2196
|
+
opacity: 0;
|
|
2197
|
+
visibility: hidden;
|
|
2198
|
+
transform: translateY(6px);
|
|
2199
|
+
transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s linear 0.18s, border-color 0.15s ease, color 0.15s ease;
|
|
2200
|
+
}
|
|
2201
|
+
#jump-to-bottom-btn.visible {
|
|
2202
|
+
opacity: 1;
|
|
2203
|
+
visibility: visible;
|
|
2204
|
+
transform: none;
|
|
2205
|
+
transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s, border-color 0.15s ease, color 0.15s ease;
|
|
2206
|
+
}
|
|
2207
|
+
#jump-to-bottom-btn:hover,
|
|
2208
|
+
#jump-to-bottom-btn:focus-visible { border-color: var(--accent); color: var(--accent); }
|
|
2209
|
+
.jump-to-bottom-icon {
|
|
2210
|
+
font-size: 26px;
|
|
2211
|
+
line-height: 1;
|
|
2212
|
+
/* The arrowhead sits low in its em box. */
|
|
2213
|
+
transform: translateY(-4px);
|
|
2214
|
+
}
|
|
2215
|
+
.jump-to-bottom-badge {
|
|
2216
|
+
position: absolute;
|
|
2217
|
+
top: -6px;
|
|
2218
|
+
right: -6px;
|
|
2219
|
+
min-width: 18px;
|
|
2220
|
+
height: 18px;
|
|
2221
|
+
padding: 0 5px;
|
|
2222
|
+
box-sizing: border-box;
|
|
2223
|
+
border-radius: 9px;
|
|
2224
|
+
background: var(--accent);
|
|
2225
|
+
color: #000;
|
|
2226
|
+
font-size: 11px;
|
|
2227
|
+
font-weight: 700;
|
|
2228
|
+
line-height: 18px;
|
|
2229
|
+
text-align: center;
|
|
2230
|
+
}
|
|
2231
|
+
.jump-to-bottom-badge[hidden] { display: none; }
|
|
2232
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2233
|
+
#jump-to-bottom-btn,
|
|
2234
|
+
#jump-to-bottom-btn.visible { transition: none; }
|
|
2235
|
+
}
|
|
2150
2236
|
/* ── Context usage modal ── */
|
|
2151
2237
|
.ctx-usage-model {
|
|
2152
2238
|
font-size: 0.95rem;
|
|
@@ -2994,6 +3080,110 @@
|
|
|
2994
3080
|
}
|
|
2995
3081
|
.bg-task-stop:hover:not(:disabled) { border-color: #f85149; color: #f85149; }
|
|
2996
3082
|
.bg-task-stop:disabled { opacity: 0.5; cursor: default; }
|
|
3083
|
+
/* Sub-agent rows (reply bubble, viewer): the task panel's row grammar. The
|
|
3084
|
+
title is a button that opens the agent's record in the viewer. */
|
|
3085
|
+
.subagent-rows { display: flex; flex-direction: column; margin-top: 4px; }
|
|
3086
|
+
.subagent-row-bubble { background: var(--bg3); border: 1px solid var(--border); }
|
|
3087
|
+
.subagent-row-timeline { margin-top: 0; background: var(--bg3); border: 1px solid var(--border); }
|
|
3088
|
+
.bg-task-desc.subagent-open {
|
|
3089
|
+
display: block; -webkit-line-clamp: unset; background: none; border: 0; padding: 0; margin: 0;
|
|
3090
|
+
font: inherit; color: var(--text); text-align: left; cursor: pointer;
|
|
3091
|
+
text-decoration: underline; text-decoration-color: transparent; text-underline-offset: 3px;
|
|
3092
|
+
}
|
|
3093
|
+
.bg-task-desc.subagent-open:hover, .bg-task-desc.subagent-open:focus-visible {
|
|
3094
|
+
color: var(--accent); text-decoration-color: currentColor; outline: none;
|
|
3095
|
+
}
|
|
3096
|
+
.subagent-row-status.subagent-status-failed { color: #f85149; }
|
|
3097
|
+
.subagent-row-status.subagent-status-completed { color: #3fb950; }
|
|
3098
|
+
/* The sub-agent viewer: one agent's thoughts, tool calls and reply in order.
|
|
3099
|
+
Below the file viewer, so a file opened from the record comes on top. */
|
|
3100
|
+
#subagent-viewer-modal {
|
|
3101
|
+
position: fixed; inset: 0; z-index: 10900; display: none;
|
|
3102
|
+
align-items: center; justify-content: center;
|
|
3103
|
+
background: rgba(0, 0, 0, 0.68); padding: 18px;
|
|
3104
|
+
}
|
|
3105
|
+
#subagent-viewer-modal.visible { display: flex; }
|
|
3106
|
+
.subagent-viewer-dialog {
|
|
3107
|
+
position: relative; width: min(980px, 100%); height: min(90vh, 1000px);
|
|
3108
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
3109
|
+
background: var(--bg2); border: 1px solid var(--border); border-radius: 14px;
|
|
3110
|
+
}
|
|
3111
|
+
.subagent-viewer-header {
|
|
3112
|
+
display: flex; flex-direction: column; gap: 4px; padding: 10px 14px;
|
|
3113
|
+
border-bottom: 1px solid var(--border); background: var(--bg3);
|
|
3114
|
+
}
|
|
3115
|
+
.subagent-viewer-title-row { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
3116
|
+
#subagent-viewer-title { flex: 1; min-width: 0; margin: 0; font-size: 1.05rem; font-weight: 600; overflow-wrap: anywhere; }
|
|
3117
|
+
#subagent-viewer-meta { color: var(--muted); font-size: var(--fs-meta-sm); overflow-wrap: anywhere; }
|
|
3118
|
+
.subagent-viewer-btn {
|
|
3119
|
+
flex-shrink: 0; border: 1px solid var(--border); background: var(--bg2); color: var(--text);
|
|
3120
|
+
border-radius: 8px; height: 32px; padding: 0 10px; font-size: var(--fs-meta-sm); cursor: pointer;
|
|
3121
|
+
}
|
|
3122
|
+
.subagent-viewer-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
|
|
3123
|
+
#subagent-viewer-stop:hover:not(:disabled) { border-color: #f85149; color: #f85149; }
|
|
3124
|
+
.subagent-viewer-btn:disabled { opacity: 0.5; cursor: default; }
|
|
3125
|
+
.subagent-viewer-close { font-size: 1.2rem; line-height: 1; }
|
|
3126
|
+
#subagent-viewer-parent {
|
|
3127
|
+
align-self: flex-start; background: none; border: 0; padding: 0; color: var(--accent);
|
|
3128
|
+
cursor: pointer; font-size: var(--fs-meta-sm);
|
|
3129
|
+
}
|
|
3130
|
+
#subagent-viewer-body { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 8px 14px 28px; }
|
|
3131
|
+
#subagent-viewer-status { color: var(--muted); padding: 14px 0; font-size: var(--fs-meta-sm); }
|
|
3132
|
+
#subagent-viewer-jump {
|
|
3133
|
+
position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
|
|
3134
|
+
border: 1px solid var(--accent); background: var(--bg3); color: var(--accent);
|
|
3135
|
+
border-radius: 999px; padding: 5px 14px; font-size: var(--fs-meta-sm); cursor: pointer;
|
|
3136
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
|
|
3137
|
+
}
|
|
3138
|
+
.sav-entry { margin: 6px 0; min-width: 0; }
|
|
3139
|
+
.sav-entry details > summary {
|
|
3140
|
+
cursor: pointer; display: flex; align-items: baseline; gap: 6px; min-width: 0; list-style: none;
|
|
3141
|
+
}
|
|
3142
|
+
.sav-entry details > summary::-webkit-details-marker { display: none; }
|
|
3143
|
+
.sav-entry details > summary::before { content: '▸'; color: var(--muted); flex-shrink: 0; }
|
|
3144
|
+
.sav-entry details[open] > summary::before { content: '▾'; }
|
|
3145
|
+
.sav-call-line { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
|
|
3146
|
+
.sav-kind { flex-shrink: 0; }
|
|
3147
|
+
.sav-tool-name { font-weight: 600; flex-shrink: 0; }
|
|
3148
|
+
.sav-summary-text, .sav-tool-summary {
|
|
3149
|
+
min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
3150
|
+
}
|
|
3151
|
+
.sav-tool-summary { color: var(--muted); font-family: var(--mono, monospace); font-size: var(--fs-meta-sm); }
|
|
3152
|
+
.sav-pre {
|
|
3153
|
+
margin: 6px 0 0; padding: 8px 10px; max-height: 50vh; overflow: auto;
|
|
3154
|
+
white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--mono, monospace);
|
|
3155
|
+
font-size: var(--fs-meta-sm); background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
|
|
3156
|
+
}
|
|
3157
|
+
.sav-entry-tool_result { margin-left: 22px; color: var(--muted); font-size: var(--fs-meta-sm); }
|
|
3158
|
+
.sav-entry-tool_result .sav-pre { color: var(--text); }
|
|
3159
|
+
.sav-entry-tool_result.is-error .sav-result-label { color: #f85149; }
|
|
3160
|
+
.sav-entry-thought { color: var(--muted); }
|
|
3161
|
+
.sav-thought-body { padding: 4px 0 0 18px; color: var(--text); opacity: 0.85; }
|
|
3162
|
+
.sav-text {
|
|
3163
|
+
padding: 8px 12px; border-radius: 10px; background: var(--bg3); border: 1px solid var(--border);
|
|
3164
|
+
overflow-wrap: anywhere;
|
|
3165
|
+
}
|
|
3166
|
+
.sav-entry.is-open .sav-text::after,
|
|
3167
|
+
.sav-entry-thought.is-open .sav-thought-body::after {
|
|
3168
|
+
content: '▍'; margin-left: 2px; animation: bg-task-pulse 1s ease-in-out infinite;
|
|
3169
|
+
}
|
|
3170
|
+
/* Markdown in the record reads like a reply bubble. */
|
|
3171
|
+
.sav-text p, .sav-thought-body p { margin: 0 0 0.5em; }
|
|
3172
|
+
.sav-text p:last-child, .sav-thought-body p:last-child { margin-bottom: 0; }
|
|
3173
|
+
.sav-text ul, .sav-text ol, .sav-thought-body ul, .sav-thought-body ol { padding-left: 1.4em; margin: 0 0 0.5em; }
|
|
3174
|
+
.sav-text h1, .sav-text h2, .sav-text h3 { margin: 0.5em 0 0.25em; font-size: 1em; }
|
|
3175
|
+
.sav-text pre, .sav-thought-body pre { margin: 0.5em 0; border-radius: 8px; max-width: 100%; overflow-x: auto; }
|
|
3176
|
+
.sav-text code:not(pre code), .sav-thought-body code:not(pre code) {
|
|
3177
|
+
background: var(--inline-code-bg); padding: 2px 6px; border-radius: 4px; font-size: 0.875em; overflow-wrap: anywhere;
|
|
3178
|
+
}
|
|
3179
|
+
.sav-note { color: var(--muted); font-size: var(--fs-meta-sm); font-style: italic; }
|
|
3180
|
+
.sav-note-failed { color: #f85149; }
|
|
3181
|
+
.sav-muted { color: var(--muted); }
|
|
3182
|
+
@media (max-width: 680px) {
|
|
3183
|
+
#subagent-viewer-modal { padding: 0; }
|
|
3184
|
+
.subagent-viewer-dialog { width: 100%; height: 100%; border: 0; border-radius: 0; }
|
|
3185
|
+
#subagent-viewer-body { padding: 8px 10px 28px; }
|
|
3186
|
+
}
|
|
2997
3187
|
/* Preview cards: same row grammar as a background task, with a link-shaped
|
|
2998
3188
|
primary action. Shared by the composer panel and the settings list. */
|
|
2999
3189
|
.preview-row {
|
|
@@ -3115,6 +3305,102 @@
|
|
|
3115
3305
|
overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
|
|
3116
3306
|
}
|
|
3117
3307
|
#chat-title-origin button.conv-origin-agent { padding: 3px 0; }
|
|
3308
|
+
/* Relay events: the event rows the relay writes into a watcher's
|
|
3309
|
+
conversation (never a bubble of the user's), the "watching" chip with its
|
|
3310
|
+
list, and the "watched" badge. */
|
|
3311
|
+
.msg.relay-event { align-self: stretch; max-width: 100%; align-items: stretch; }
|
|
3312
|
+
.relay-event-row {
|
|
3313
|
+
border: 1px dashed var(--border); border-left: 3px solid var(--accent);
|
|
3314
|
+
border-radius: 6px; background: var(--bg2); color: var(--muted);
|
|
3315
|
+
font-size: var(--fs-meta-xs); padding: 2px 8px;
|
|
3316
|
+
}
|
|
3317
|
+
.relay-event-row.relay-event-urgent { border-left-color: var(--warn); }
|
|
3318
|
+
.relay-event-summary {
|
|
3319
|
+
cursor: pointer; display: flex; gap: 8px; align-items: baseline; min-width: 0;
|
|
3320
|
+
padding: 3px 0; list-style-position: inside;
|
|
3321
|
+
}
|
|
3322
|
+
.relay-event-tag {
|
|
3323
|
+
flex: 0 0 auto; text-transform: uppercase; letter-spacing: 0.04em;
|
|
3324
|
+
font-size: var(--fs-ui-chip); color: var(--accent);
|
|
3325
|
+
}
|
|
3326
|
+
.relay-event-urgent .relay-event-tag { color: var(--warn); }
|
|
3327
|
+
.relay-event-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3328
|
+
.relay-event-time { flex: 0 0 auto; }
|
|
3329
|
+
.relay-event-lines { margin: 2px 0 4px; padding-left: 18px; }
|
|
3330
|
+
.relay-event-line { margin: 2px 0; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
3331
|
+
.relay-event-line.relay-event-question-asked,
|
|
3332
|
+
.relay-event-line.relay-event-error,
|
|
3333
|
+
.relay-event-line.relay-event-task-failed,
|
|
3334
|
+
.relay-event-line.relay-event-stalled { color: var(--text, inherit); }
|
|
3335
|
+
/* Agent exchanges: an agent's prompt and this session's reply folded into
|
|
3336
|
+
one row in the event rows' look (agent-exchange-fold-view.mjs). The row
|
|
3337
|
+
is the first child of the prompt's own node; closed, the prompt's bubble
|
|
3338
|
+
and the exchange's other rows (the reply, the live bubble) are hidden.
|
|
3339
|
+
The prompt's node spans the pane for the row; its bubble keeps a user
|
|
3340
|
+
bubble's width. */
|
|
3341
|
+
.msg.agent-exchange-head { align-self: stretch; max-width: 100%; }
|
|
3342
|
+
.msg.agent-exchange-head > .msg-bubble { max-width: 82%; }
|
|
3343
|
+
.msg.agent-exchange-head.agent-exchange-closed > .msg-bubble,
|
|
3344
|
+
.msg.agent-exchange-head.agent-exchange-closed > .msg-label,
|
|
3345
|
+
#messages > .msg.agent-exchange-member.agent-exchange-closed { display: none; }
|
|
3346
|
+
.agent-exchange-row {
|
|
3347
|
+
align-self: stretch; min-width: 0;
|
|
3348
|
+
border: 1px dashed var(--border); border-left: 3px solid var(--accent);
|
|
3349
|
+
border-radius: 6px; background: var(--bg2); color: var(--muted);
|
|
3350
|
+
font-size: var(--fs-meta-xs); padding: 2px 8px;
|
|
3351
|
+
}
|
|
3352
|
+
.msg.agent-exchange-head:not(.agent-exchange-closed) > .agent-exchange-row { margin-bottom: 6px; }
|
|
3353
|
+
.agent-exchange-summary {
|
|
3354
|
+
cursor: pointer; display: flex; gap: 8px; align-items: baseline; min-width: 0;
|
|
3355
|
+
padding: 3px 0; list-style: none;
|
|
3356
|
+
}
|
|
3357
|
+
.agent-exchange-summary::-webkit-details-marker { display: none; }
|
|
3358
|
+
.agent-exchange-summary::before { content: '▸'; flex: 0 0 auto; color: var(--accent); }
|
|
3359
|
+
.agent-exchange-row[open] > .agent-exchange-summary::before { content: '▾'; }
|
|
3360
|
+
.agent-exchange-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3361
|
+
.agent-exchange-status,
|
|
3362
|
+
.agent-exchange-time { flex: 0 0 auto; }
|
|
3363
|
+
.agent-exchange-status { color: var(--text, inherit); }
|
|
3364
|
+
.agent-exchange-row.agent-exchange-failed,
|
|
3365
|
+
.agent-exchange-row.agent-exchange-stopped { border-left-color: var(--warn); }
|
|
3366
|
+
.agent-exchange-failed .agent-exchange-status,
|
|
3367
|
+
.agent-exchange-stopped .agent-exchange-status { color: var(--warn); }
|
|
3368
|
+
@media (max-width: 680px) {
|
|
3369
|
+
.msg.agent-exchange-head > .msg-bubble { max-width: 95%; }
|
|
3370
|
+
}
|
|
3371
|
+
#chat-title-relay-events {
|
|
3372
|
+
display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 2px;
|
|
3373
|
+
font-size: var(--fs-meta-xs); line-height: 1.2; color: var(--muted); position: relative;
|
|
3374
|
+
}
|
|
3375
|
+
#chat-title-relay-events[hidden] { display: none; }
|
|
3376
|
+
.relay-watch-panel[hidden] { display: none; }
|
|
3377
|
+
.relay-watch-chip {
|
|
3378
|
+
font: inherit; color: var(--accent); background: transparent; cursor: pointer;
|
|
3379
|
+
border: 1px solid var(--border); border-radius: 10px; padding: 2px 8px;
|
|
3380
|
+
}
|
|
3381
|
+
.relay-watch-panel {
|
|
3382
|
+
position: fixed; top: 0; left: 0; z-index: 60; overflow-y: auto; box-sizing: border-box;
|
|
3383
|
+
min-width: min(260px, calc(100vw - 16px)); max-width: min(calc(100vw - 16px), 480px);
|
|
3384
|
+
font-size: var(--fs-meta-xs); line-height: 1.2; color: var(--muted);
|
|
3385
|
+
background: var(--bg2); border: 1px solid var(--border); border-radius: 8px;
|
|
3386
|
+
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25); padding: 6px;
|
|
3387
|
+
}
|
|
3388
|
+
.relay-watch-list { list-style: none; margin: 0; padding: 0; }
|
|
3389
|
+
.relay-watch-item {
|
|
3390
|
+
display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 8px; align-items: center;
|
|
3391
|
+
padding: 6px 4px; border-bottom: 1px solid var(--border);
|
|
3392
|
+
}
|
|
3393
|
+
.relay-watch-item:last-child { border-bottom: none; }
|
|
3394
|
+
.relay-watch-what { color: var(--text, inherit); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3395
|
+
.relay-watch-events { grid-column: 1; font-size: var(--fs-ui-chip); }
|
|
3396
|
+
.relay-watch-unsubscribe {
|
|
3397
|
+
grid-column: 2; grid-row: 1 / span 2; font: inherit; cursor: pointer;
|
|
3398
|
+
color: var(--danger); background: transparent; border: 1px solid var(--border); border-radius: 6px; padding: 4px 8px;
|
|
3399
|
+
}
|
|
3400
|
+
.conv-watched-badge {
|
|
3401
|
+
flex: 0 0 auto; font-size: var(--fs-ui-chip); color: var(--accent);
|
|
3402
|
+
border: 1px solid var(--border); border-radius: 8px; padding: 0 5px;
|
|
3403
|
+
}
|
|
3118
3404
|
/* Agent sessions (Settings → Relays). The slider spans the row so a thumb
|
|
3119
3405
|
has room to travel at phone width, with a taller touch area than the
|
|
3120
3406
|
track itself. */
|
|
@@ -4069,6 +4355,28 @@
|
|
|
4069
4355
|
border-color: var(--accent);
|
|
4070
4356
|
color: var(--accent);
|
|
4071
4357
|
}
|
|
4358
|
+
/* The one main action of a dialog, in #new-conv-btn's colours. */
|
|
4359
|
+
.summary-btn.summary-btn-accent {
|
|
4360
|
+
background: var(--accent);
|
|
4361
|
+
color: #000;
|
|
4362
|
+
border-color: var(--accent);
|
|
4363
|
+
font-weight: 600;
|
|
4364
|
+
}
|
|
4365
|
+
.summary-btn.summary-btn-accent:hover { opacity: 0.85; }
|
|
4366
|
+
#new-conversation-open-settings[hidden] { display: none; }
|
|
4367
|
+
/* New Chat with no provider ready: the note above the buttons. */
|
|
4368
|
+
.new-conversation-setup-note {
|
|
4369
|
+
margin-top: 14px;
|
|
4370
|
+
padding: 10px 12px;
|
|
4371
|
+
border: 1px solid var(--border);
|
|
4372
|
+
border-left: 3px solid var(--accent);
|
|
4373
|
+
border-radius: 8px;
|
|
4374
|
+
background: var(--bg3);
|
|
4375
|
+
color: var(--text);
|
|
4376
|
+
font-size: var(--fs-meta-md);
|
|
4377
|
+
line-height: 1.5;
|
|
4378
|
+
}
|
|
4379
|
+
.new-conversation-setup-note[hidden] { display: none; }
|
|
4072
4380
|
.summary-close {
|
|
4073
4381
|
width: 32px;
|
|
4074
4382
|
padding: 0;
|
|
@@ -5050,6 +5358,43 @@
|
|
|
5050
5358
|
font-size: var(--fs-meta-md);
|
|
5051
5359
|
padding: 10px 6px;
|
|
5052
5360
|
}
|
|
5361
|
+
/* Explorer edits: the New-folder row at the top of the folder pane and the in-place rename. */
|
|
5362
|
+
.repo-tree-summary, .repo-entry-row, .repo-card { -webkit-touch-callout: none; user-select: none; -webkit-user-select: none; }
|
|
5363
|
+
.repo-inline-edit {
|
|
5364
|
+
display: grid;
|
|
5365
|
+
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
|
5366
|
+
align-items: center;
|
|
5367
|
+
gap: 8px;
|
|
5368
|
+
margin: 0 0 10px;
|
|
5369
|
+
padding: 8px 10px;
|
|
5370
|
+
border: 1px solid var(--accent);
|
|
5371
|
+
border-radius: 10px;
|
|
5372
|
+
background: rgba(88, 166, 255, 0.08);
|
|
5373
|
+
}
|
|
5374
|
+
.repo-inline-edit input,
|
|
5375
|
+
.repo-rename-input,
|
|
5376
|
+
.repo-delete-confirm-input {
|
|
5377
|
+
min-width: 0;
|
|
5378
|
+
width: 100%;
|
|
5379
|
+
border: 1px solid var(--border);
|
|
5380
|
+
border-radius: 8px;
|
|
5381
|
+
background: var(--bg);
|
|
5382
|
+
color: var(--text);
|
|
5383
|
+
padding: 6px 8px;
|
|
5384
|
+
font: inherit;
|
|
5385
|
+
font-size: var(--fs-meta-md);
|
|
5386
|
+
}
|
|
5387
|
+
.repo-inline-edit input:focus, .repo-rename-input:focus, .repo-delete-confirm-input:focus { outline: none; border-color: var(--accent); }
|
|
5388
|
+
.repo-inline-edit-error { grid-column: 1 / -1; color: var(--danger); font-size: var(--fs-meta-xs); min-height: 0; }
|
|
5389
|
+
.repo-inline-edit-error:empty { display: none; }
|
|
5390
|
+
.repo-rename-input { display: inline-block; width: auto; max-width: 100%; padding: 2px 6px; }
|
|
5391
|
+
.repo-tree-summary .repo-rename-input { width: calc(100% - 2.2em); }
|
|
5392
|
+
.repo-delete-confirm-label { display: block; margin: 10px 0 0; font-size: var(--fs-meta-md); color: var(--muted); }
|
|
5393
|
+
.repo-delete-confirm-input { margin-top: 6px; }
|
|
5394
|
+
@media (max-width: 720px) {
|
|
5395
|
+
.repo-inline-edit { grid-template-columns: auto minmax(0, 1fr); }
|
|
5396
|
+
.repo-inline-edit .repo-browser-btn { grid-column: span 1; }
|
|
5397
|
+
}
|
|
5053
5398
|
|
|
5054
5399
|
.msg-mode {
|
|
5055
5400
|
display: inline-block; font-size: var(--fs-ui-chip); background: var(--bg3);
|
|
@@ -5362,6 +5707,10 @@
|
|
|
5362
5707
|
pointer-events: auto;
|
|
5363
5708
|
}
|
|
5364
5709
|
#sidebar-resizer { display: none; }
|
|
5710
|
+
/* The "no conversation open" actions stack on the phone. */
|
|
5711
|
+
.empty-state { padding: 16px; }
|
|
5712
|
+
.empty-state-actions { flex-direction: column; align-items: stretch; width: min(100%, 320px); }
|
|
5713
|
+
.empty-state-btn { height: 40px; }
|
|
5365
5714
|
#fullscreen-btn { display: inline-flex; align-items: center; justify-content: center; }
|
|
5366
5715
|
#sidebar-overlay { display: block; opacity: 0; pointer-events: none; transition: opacity 0.25s; }
|
|
5367
5716
|
#sidebar-overlay.visible { opacity: 1; pointer-events: all; }
|
|
@@ -5542,6 +5891,7 @@
|
|
|
5542
5891
|
.chat-actions-menu-item,
|
|
5543
5892
|
.chat-actions-menu-meta { min-height: 30px; }
|
|
5544
5893
|
#input-area { padding: 2px 10px calc(6px + env(safe-area-inset-bottom, 0px)); }
|
|
5894
|
+
#jump-to-bottom-btn { width: 44px; height: 44px; right: calc(12px + env(safe-area-inset-right, 0px)); bottom: calc(100% + 10px); }
|
|
5545
5895
|
.input-row {
|
|
5546
5896
|
display: flex;
|
|
5547
5897
|
flex-wrap: wrap;
|
|
@@ -6058,7 +6408,7 @@
|
|
|
6058
6408
|
<button id="message-search-btn" class="header-icon-btn" onclick="openMessageSearchModal()" title="Search messages" aria-label="Search messages">🔍</button>
|
|
6059
6409
|
<button id="context-btn" class="header-icon-btn" onclick="showContext()" title="Check current context" aria-label="Check current context">🧠</button>
|
|
6060
6410
|
<div id="chat-actions-menu-wrap">
|
|
6061
|
-
<button id="chat-actions-menu-btn" class="header-icon-btn" type="button" title="
|
|
6411
|
+
<button id="chat-actions-menu-btn" class="header-icon-btn" type="button" title="Menu" aria-label="Menu" aria-haspopup="menu" aria-expanded="false">⋯</button>
|
|
6062
6412
|
<div id="chat-actions-menu" role="menu" hidden>
|
|
6063
6413
|
<div id="chat-menu-pwa-version" class="chat-actions-menu-meta chat-actions-menu-meta-stacked" aria-hidden="true"><span class="chat-actions-menu-meta-primary">PWA shell version</span><span class="chat-actions-menu-meta-secondary" id="chat-menu-pwa-version-value">v?</span></div>
|
|
6064
6414
|
<div id="chat-menu-queue-status" class="chat-actions-menu-meta chat-actions-menu-meta-stacked" aria-hidden="true"><div class="chat-actions-menu-meta-top"><span class="chat-actions-menu-meta-primary">Queue</span><button id="chat-menu-empty-queue" class="chat-actions-menu-meta-label" type="button">🚮 Empty queue</button></div><span class="chat-actions-menu-meta-secondary" id="chat-menu-queue-status-value">pending=?, processing=?, parked=?</span></div>
|
|
@@ -6085,13 +6435,15 @@
|
|
|
6085
6435
|
<button id="pending-question-banner" onclick="openPendingQuestionFromBanner()" title="Open pending question"></button>
|
|
6086
6436
|
</div>
|
|
6087
6437
|
<div id="messages">
|
|
6438
|
+
<!-- Replaced on start by conversation-view.js (no-conversation-state.mjs),
|
|
6439
|
+
which knows whether a provider is ready and whether chats exist. -->
|
|
6088
6440
|
<div class="empty-state">
|
|
6089
6441
|
<div class="icon">💬</div>
|
|
6090
|
-
<h3>No conversation
|
|
6091
|
-
<p>Click <b>+ New Conversation</b> to start chatting with your Copilot CLI</p>
|
|
6442
|
+
<h3>No conversation open</h3>
|
|
6092
6443
|
</div>
|
|
6093
6444
|
</div>
|
|
6094
6445
|
<div id="input-area">
|
|
6446
|
+
<button id="jump-to-bottom-btn" type="button" title="Jump to the latest message" aria-label="Jump to the latest message"><span class="jump-to-bottom-icon" aria-hidden="true">⌄</span><span class="jump-to-bottom-badge" hidden></span></button>
|
|
6095
6447
|
<div id="offline-banner">⚠️ CLI is offline — messages will queue until it reconnects</div>
|
|
6096
6448
|
<div id="model-banner"></div>
|
|
6097
6449
|
<div id="usage-limit-banner" role="status" aria-live="polite" hidden></div>
|
|
@@ -6167,6 +6519,7 @@
|
|
|
6167
6519
|
<button id="repo-toggle-hidden-btn" class="repo-browser-btn" onclick="toggleRepoBrowserHidden()">Hidden: Off</button>
|
|
6168
6520
|
<button id="repo-toggle-heavy-btn" class="repo-browser-btn" onclick="toggleRepoBrowserHeavy()">Heavy: Off</button>
|
|
6169
6521
|
<button class="repo-browser-btn" onclick="refreshRepoBrowser()">Refresh</button>
|
|
6522
|
+
<button id="repo-new-folder-btn" class="repo-browser-btn" onclick="startRepoNewFolder()" title="Create a folder inside the open folder">📁+ New folder</button>
|
|
6170
6523
|
</div>
|
|
6171
6524
|
<div class="repo-browser-layout">
|
|
6172
6525
|
<section class="repo-pane">
|
|
@@ -6356,6 +6709,17 @@
|
|
|
6356
6709
|
>
|
|
6357
6710
|
</div>
|
|
6358
6711
|
<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>
|
|
6712
|
+
<div class="settings-row" style="margin-top: 12px; margin-bottom: 0;">
|
|
6713
|
+
<label for="subagent-record-retention-select">Keep sub-agent tool output</label>
|
|
6714
|
+
<select id="subagent-record-retention-select" onchange="updateSubagentRecordRetentionSetting(this.value)" aria-label="Keep sub-agent tool output">
|
|
6715
|
+
<option value="7">7 days</option>
|
|
6716
|
+
<option value="14">14 days</option>
|
|
6717
|
+
<option value="30">30 days</option>
|
|
6718
|
+
<option value="90" selected>90 days</option>
|
|
6719
|
+
<option value="0">Always</option>
|
|
6720
|
+
</select>
|
|
6721
|
+
</div>
|
|
6722
|
+
<div class="settings-help" style="margin-top: 6px;">A sub-agent's record keeps every tool call's full input and the first 4 KB of each result. After this many days the relay removes them from finished agents, once a day; the thoughts, the reply, notes and the one-line summary of each call stay. Always keeps everything.</div>
|
|
6359
6723
|
<div id="windows-autostart-setting" hidden>
|
|
6360
6724
|
<div class="settings-row settings-row-column" style="margin-top: 12px; margin-bottom: 0;">
|
|
6361
6725
|
<label>Autostart (Windows)</label>
|
|
@@ -7215,8 +7579,13 @@
|
|
|
7215
7579
|
<select id="new-conversation-size-select"></select>
|
|
7216
7580
|
<div id="new-conversation-size-status" style="margin-top:8px;font-size:0.78rem;color:var(--muted);">Image size used for generated outputs in this chat.</div>
|
|
7217
7581
|
</div>
|
|
7582
|
+
<!-- No provider known to be ready (journal-view.js): the note says so and
|
|
7583
|
+
Settings is the main button; Start stays as "anyway", because the
|
|
7584
|
+
Copilot signal can be wrong. -->
|
|
7585
|
+
<div id="new-conversation-setup-note" class="new-conversation-setup-note" role="status" hidden></div>
|
|
7218
7586
|
<div class="summary-modal-actions">
|
|
7219
7587
|
<button type="button" class="summary-btn" onclick="closeNewConversationModelModal()">Cancel</button>
|
|
7588
|
+
<button id="new-conversation-open-settings" type="button" class="summary-btn summary-btn-accent" hidden>Open Settings → Providers</button>
|
|
7220
7589
|
<button id="new-conversation-model-confirm" type="button" class="summary-btn" onclick="confirmNewConversationModel()">Start chat</button>
|
|
7221
7590
|
</div>
|
|
7222
7591
|
</div>
|
|
@@ -7267,6 +7636,28 @@
|
|
|
7267
7636
|
</div>
|
|
7268
7637
|
</div>
|
|
7269
7638
|
|
|
7639
|
+
<!-- Sub-agent viewer: one sub-agent's thoughts, tool calls with their results
|
|
7640
|
+
and reply, in order, live while it runs (subagent-viewer.mjs). -->
|
|
7641
|
+
<div id="subagent-viewer-modal" aria-hidden="true">
|
|
7642
|
+
<div class="subagent-viewer-dialog" role="dialog" aria-modal="true" aria-labelledby="subagent-viewer-title">
|
|
7643
|
+
<div class="subagent-viewer-header">
|
|
7644
|
+
<button id="subagent-viewer-parent" type="button" hidden></button>
|
|
7645
|
+
<div class="subagent-viewer-title-row">
|
|
7646
|
+
<span aria-hidden="true">🤖</span>
|
|
7647
|
+
<h2 id="subagent-viewer-title">Sub-agent</h2>
|
|
7648
|
+
<button id="subagent-viewer-stop" class="subagent-viewer-btn" type="button" hidden>Stop</button>
|
|
7649
|
+
<button class="subagent-viewer-btn subagent-viewer-close" type="button" aria-label="Close">×</button>
|
|
7650
|
+
</div>
|
|
7651
|
+
<div id="subagent-viewer-meta"></div>
|
|
7652
|
+
</div>
|
|
7653
|
+
<div id="subagent-viewer-body">
|
|
7654
|
+
<div id="subagent-viewer-timeline"></div>
|
|
7655
|
+
<div id="subagent-viewer-status"></div>
|
|
7656
|
+
</div>
|
|
7657
|
+
<button id="subagent-viewer-jump" type="button" hidden>New entries ↓</button>
|
|
7658
|
+
</div>
|
|
7659
|
+
</div>
|
|
7660
|
+
|
|
7270
7661
|
<!-- Fullscreen file viewer. The content fills the screen; the chrome bars
|
|
7271
7662
|
float over it and a tap on the content shows or hides them. -->
|
|
7272
7663
|
<div id="file-preview-modal" aria-hidden="true">
|
|
@@ -346,6 +346,12 @@ export function createMessageRepository(db) {
|
|
|
346
346
|
//
|
|
347
347
|
// A turn blocked on an unanswered AskUserQuestion is never stale — it is
|
|
348
348
|
// waiting on the human, and relay_questions carries its own expiry.
|
|
349
|
+
//
|
|
350
|
+
// The ceiling counts from the last human interaction with the turn, not
|
|
351
|
+
// from its start: answering a question restarts the clock. Otherwise an
|
|
352
|
+
// interview that waited hours for its answers would be cut off by the
|
|
353
|
+
// ceiling the moment the last answer arrived (live 2026-10-10: a
|
|
354
|
+
// five-question interview lost its reply that way).
|
|
349
355
|
listRecoverableProcessing: db.prepare(`
|
|
350
356
|
SELECT id, conversation_id, kind, relay_mode, model, retry_count, consumed_at
|
|
351
357
|
FROM queue
|
|
@@ -353,7 +359,13 @@ export function createMessageRepository(db) {
|
|
|
353
359
|
AND COALESCE(kind, '') != 'continuation'
|
|
354
360
|
AND (
|
|
355
361
|
COALESCE(owner_last_claimed_at, processing_at, timestamp) < @inactiveBefore
|
|
356
|
-
OR (
|
|
362
|
+
OR (
|
|
363
|
+
@ceilingBefore IS NOT NULL
|
|
364
|
+
AND MAX(
|
|
365
|
+
COALESCE(processing_at, timestamp),
|
|
366
|
+
COALESCE((SELECT MAX(answered_at) FROM relay_questions WHERE queue_id = queue.id), '')
|
|
367
|
+
) < @ceilingBefore
|
|
368
|
+
)
|
|
357
369
|
)
|
|
358
370
|
AND id NOT IN (SELECT queue_id FROM relay_questions WHERE status = 'pending')
|
|
359
371
|
`),
|
|
@@ -431,15 +443,29 @@ export function createMessageRepository(db) {
|
|
|
431
443
|
// A continuation with no live worker can only be torn down — replaying
|
|
432
444
|
// it as deliverable work would send the CLI's own bookkeeping text back
|
|
433
445
|
// to the CLI as a user prompt.
|
|
446
|
+
//
|
|
447
|
+
// Same staleness rules as listRecoverableProcessing: a continuation
|
|
448
|
+
// waiting on an unanswered question is never stale, and an answer
|
|
449
|
+
// restarts the ceiling clock. A background-agent interview hangs its
|
|
450
|
+
// question cards on the continuation row, so without this a long
|
|
451
|
+
// interview was torn down by the ceiling while the human was still
|
|
452
|
+
// answering (live 2026-10-10).
|
|
434
453
|
listStaleProcessingContinuations: db.prepare(`
|
|
435
|
-
SELECT id, conversation_id
|
|
454
|
+
SELECT id, conversation_id, timestamp, processing_at, owner_last_claimed_at
|
|
436
455
|
FROM queue
|
|
437
456
|
WHERE status = 'processing'
|
|
438
457
|
AND kind = 'continuation'
|
|
439
458
|
AND (
|
|
440
459
|
COALESCE(owner_last_claimed_at, processing_at, timestamp) < @inactiveBefore
|
|
441
|
-
OR (
|
|
460
|
+
OR (
|
|
461
|
+
@ceilingBefore IS NOT NULL
|
|
462
|
+
AND MAX(
|
|
463
|
+
COALESCE(processing_at, timestamp),
|
|
464
|
+
COALESCE((SELECT MAX(answered_at) FROM relay_questions WHERE queue_id = queue.id), '')
|
|
465
|
+
) < @ceilingBefore
|
|
466
|
+
)
|
|
442
467
|
)
|
|
468
|
+
AND id NOT IN (SELECT queue_id FROM relay_questions WHERE status = 'pending')
|
|
443
469
|
`),
|
|
444
470
|
listQueueForPauseDrop: db.prepare(`SELECT id, conversation_id FROM queue WHERE status IN ('pending', 'processing', 'parked')`),
|
|
445
471
|
// Authoritative "is a turn in flight" signal for the conversation list.
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
import { createSubagentTimelineStatements } from './subagent-timeline-statements.mjs';
|
|
4
|
+
|
|
3
5
|
export function createQuestionRepository(db) {
|
|
6
|
+
const subagentTimeline = createSubagentTimelineStatements(db);
|
|
7
|
+
const runColumns = subagentTimeline.subagentRunSelectColumns;
|
|
8
|
+
const runColumnsAs = (alias) => runColumns.split(', ').map((column) => `${alias}.${column}`).join(', ');
|
|
4
9
|
return {
|
|
10
|
+
...subagentTimeline,
|
|
5
11
|
// relay questions
|
|
6
12
|
insertQuestion: db.prepare(`INSERT INTO relay_questions (id, queue_id, conversation_id, message_id, relay_mode, prompt, choices, request, request_schema, status, answer, structured_answer, sdk_session_id, owner_worker_id, continuation_id, continuation_question_id, created_at, answered_at, expires_at, attempt_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', NULL, NULL, ?, ?, ?, ?, ?, NULL, ?, ?)`),
|
|
7
13
|
getQuestion: db.prepare(`SELECT * FROM relay_questions WHERE id = ?`),
|
|
@@ -99,7 +105,7 @@ export function createQuestionRepository(db) {
|
|
|
99
105
|
WHERE id = ?
|
|
100
106
|
`),
|
|
101
107
|
listSubagentRunsByQueueMessage: db.prepare(`
|
|
102
|
-
SELECT
|
|
108
|
+
SELECT ${runColumns}
|
|
103
109
|
FROM subagent_runs
|
|
104
110
|
WHERE queue_message_id = ?
|
|
105
111
|
ORDER BY started_at ASC, id ASC
|
|
@@ -108,7 +114,7 @@ export function createQuestionRepository(db) {
|
|
|
108
114
|
// abort) also ends its subagent runs — nothing else ever will, and an
|
|
109
115
|
// un-reconciled row renders as a bubble stuck "running" forever.
|
|
110
116
|
listRunningSubagentRunsByQueueMessage: db.prepare(`
|
|
111
|
-
SELECT
|
|
117
|
+
SELECT ${runColumns}
|
|
112
118
|
FROM subagent_runs
|
|
113
119
|
WHERE queue_message_id = ? AND status = 'running'
|
|
114
120
|
`),
|
|
@@ -121,7 +127,7 @@ export function createQuestionRepository(db) {
|
|
|
121
127
|
WHERE queue_message_id = ? AND status = 'running'
|
|
122
128
|
`),
|
|
123
129
|
listSubagentRunsByResponse: db.prepare(`
|
|
124
|
-
SELECT sr
|
|
130
|
+
SELECT ${runColumnsAs('sr')}
|
|
125
131
|
FROM subagent_runs sr
|
|
126
132
|
WHERE sr.queue_message_id IN (
|
|
127
133
|
SELECT id FROM queue WHERE response_message_id = ?
|
|
@@ -29,7 +29,10 @@ export function createRemoteRelayRepository(db) {
|
|
|
29
29
|
const hasUnlock = db.prepare(`SELECT 1 FROM conversation_remote_relay_unlocks WHERE conversation_id = ? AND remote_relay_id = ?`);
|
|
30
30
|
const listUnlocks = db.prepare(`SELECT remote_relay_id, message_id, created_at FROM conversation_remote_relay_unlocks WHERE conversation_id = ? ORDER BY created_at ASC`);
|
|
31
31
|
const insertUnlock = db.prepare(`INSERT OR IGNORE INTO conversation_remote_relay_unlocks (conversation_id, remote_relay_id, message_id, created_at) VALUES (?, ?, ?, ?)`);
|
|
32
|
-
|
|
32
|
+
// The relay's unlocks and the approvals stored under its id (`<id>:watch`).
|
|
33
|
+
const deleteUnlocksForRelay = db.prepare(`
|
|
34
|
+
DELETE FROM conversation_remote_relay_unlocks WHERE remote_relay_id = ? OR substr(remote_relay_id, 1, length(?) + 1) = ? || ':'
|
|
35
|
+
`);
|
|
33
36
|
const deleteUnlocksForConversation = db.prepare(`DELETE FROM conversation_remote_relay_unlocks WHERE conversation_id = ?`);
|
|
34
37
|
const setMessageOrigin = db.prepare(`UPDATE messages SET origin_json = ? WHERE id = ?`);
|
|
35
38
|
const getMessageOrigin = db.prepare(`SELECT origin_json FROM messages WHERE id = ?`);
|
|
@@ -101,7 +104,8 @@ export function createRemoteRelayRepository(db) {
|
|
|
101
104
|
.map((row) => String(row.id));
|
|
102
105
|
},
|
|
103
106
|
forgetRelay(remoteRelayId) {
|
|
104
|
-
|
|
107
|
+
const id = String(remoteRelayId || '');
|
|
108
|
+
return deleteUnlocksForRelay.run(id, id, id).changes;
|
|
105
109
|
},
|
|
106
110
|
forgetConversation(conversationId) {
|
|
107
111
|
return deleteUnlocksForConversation.run(String(conversationId || '')).changes;
|