@oar-sh/oar 0.9.6 → 0.9.8
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 +285 -0
- package/README.md +222 -40
- package/bin/oar.js +320 -18
- package/package.json +1 -1
- package/server/README.md +174 -7
- package/server/claude-cloud-worker/claude-cloud-event-normalizer.mjs +340 -0
- package/server/claude-cloud-worker/claude-cloud-session-process.mjs +1325 -0
- package/server/claude-cloud-worker/claude-cloud-session-worker.mjs +155 -0
- package/server/claude-worker/claude-remote-relay-tool.mjs +2 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +18 -13
- package/server/claude-worker/claude-session-process.mjs +225 -24
- package/server/claude-worker/claude-session-worker.mjs +6 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +28 -0
- package/server/db-schema.mjs +17 -0
- package/server/migrations/0006-workspace-root-settings.mjs +67 -0
- package/server/migrations/0007-claude-cloud.mjs +82 -0
- package/server/public/app/activity-replay-state.mjs +68 -5
- package/server/public/app/agent-sessions-settings-ui.js +197 -0
- package/server/public/app/agent-sessions-settings.mjs +134 -0
- package/server/public/app/api-client.js +149 -6
- package/server/public/app/attachments-view.js +20 -1
- package/server/public/app/attribution-control.mjs +46 -0
- package/server/public/app/bootstrap.js +468 -88
- package/server/public/app/claude-cloud-conversation-ui.js +110 -0
- package/server/public/app/claude-cloud-settings-ui.js +374 -0
- package/server/public/app/claude-cloud-ui.mjs +611 -0
- package/server/public/app/composer-control-state.mjs +11 -0
- package/server/public/app/context-menu.mjs +140 -0
- package/server/public/app/context-tier-options.mjs +4 -0
- package/server/public/app/context-usage-view.mjs +127 -8
- package/server/public/app/conversation-origin-ui.js +27 -0
- package/server/public/app/conversation-provider-indicator.mjs +22 -4
- package/server/public/app/conversation-view.js +145 -11
- package/server/public/app/cwd-picker.js +12 -0
- package/server/public/app/journal-view.js +698 -28
- package/server/public/app/math-dollars.mjs +89 -0
- package/server/public/app/model-selector-options.mjs +1 -1
- package/server/public/app/plan-usage-view.mjs +36 -8
- package/server/public/app/pwa-install.js +5 -0
- package/server/public/app/relay-error-ctas.mjs +16 -2
- package/server/public/app/remote-relay-origin-view.mjs +120 -4
- package/server/public/app/router.js +49 -12
- package/server/public/app/scrollbar-hover.mjs +78 -0
- package/server/public/app/settings-modal.js +37 -3
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/socket-handlers.js +53 -1
- package/server/public/app/store.js +26 -0
- package/server/public/app/transcript-separators.mjs +48 -11
- package/server/public/app-icon-192.png +0 -0
- package/server/public/app-icon-512.png +0 -0
- package/server/public/app-icon-light-192.png +0 -0
- package/server/public/app-icon-light-512.png +0 -0
- package/server/public/app-icon-light-maskable-512.png +0 -0
- package/server/public/app-icon-light.svg +4 -0
- package/server/public/app-icon-maskable-512.png +0 -0
- package/server/public/app-icon.svg +3 -3
- package/server/public/favicon.ico +0 -0
- package/server/public/index.html +564 -20
- package/server/public/manifest.webmanifest +9 -3
- package/server/public/sw.js +6 -1
- package/server/relay-tools.md +13 -1
- package/server/repositories/remote-relay-repository.mjs +42 -1
- package/server/repositories/session-repository.mjs +47 -1
- package/server/routes/claude-cloud-routes.mjs +140 -0
- package/server/routes/git-routes.mjs +37 -0
- package/server/routes/messages-routes.mjs +123 -16
- package/server/routes/remote-relay-routes.mjs +15 -1
- package/server/routes/sessions-routes.mjs +517 -74
- package/server/server-runtime.mjs +268 -33
- package/server/services/claude-account-usage-service.mjs +176 -0
- package/server/services/claude-cloud-repo-service.mjs +408 -0
- package/server/services/claude-cloud-session-service.mjs +326 -0
- package/server/services/claude-cloud-settings-service.mjs +269 -0
- package/server/services/claude-context-usage.mjs +51 -3
- package/server/services/git-remote-service.mjs +115 -0
- package/server/services/oar-cli-helpers.mjs +67 -1
- package/server/services/plan-usage-claude-cloud.mjs +211 -0
- package/server/services/plan-usage-claude.mjs +202 -40
- package/server/services/plan-usage-contract.mjs +39 -3
- package/server/services/plan-usage-service.mjs +82 -2
- package/server/services/provider-reasoning-effort.mjs +3 -0
- package/server/services/pwa-manifest-theme.mjs +30 -0
- package/server/services/remote-relay-caller-context.mjs +49 -10
- package/server/services/remote-relay-client.mjs +9 -0
- package/server/services/remote-relay-dispatcher.mjs +383 -35
- package/server/services/remote-relay-inbound.mjs +49 -2
- package/server/services/remote-relay-loopback.mjs +245 -0
- package/server/services/remote-relay-registry-service.mjs +52 -0
- package/server/services/session-worker-launch-service.mjs +100 -15
- package/server/services/session-worker-process-service.mjs +348 -129
- package/server/services/session-worker-registry-service.mjs +18 -1
- package/server/services/session-worker-stop-service.mjs +24 -13
- package/server/services/session-worker-supervisor-service.mjs +56 -1
- package/server/services/update-install-service.mjs +9 -3
- package/server/services/usage-limit-pause-service.mjs +3 -1
- package/server/services/worker-launch-note-service.mjs +151 -0
- package/shared/claude-attribution.mjs +117 -0
- package/shared/claude-cloud/api-client.mjs +809 -0
- package/shared/claude-cloud/base-url.mjs +47 -0
- package/shared/claude-cloud/credentials.mjs +221 -0
- package/shared/claude-cloud/repo-url.mjs +111 -0
- package/shared/provider-routing.mjs +4 -2
- package/shared/remote-relay-contract.mjs +153 -22
- package/shared/remote-relay-tool-core.mjs +11 -8
- package/shared/steer-settle-failure.mjs +1 -0
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
closeSummaryModal,
|
|
50
50
|
} from './store.js';
|
|
51
51
|
import { getPendingQuestionCountsByConversation } from './ask-user-view.js';
|
|
52
|
-
import { sendMessage as sendMessageApi, cancelConversationTurn, cancelQueuedConversationTurn, cancelSubagentRun, compactConversation as compactConversationApi, scheduleContextUsageRefresh, loadConversation as loadConversationApi, loadSharedConversation, updateConversationDraft as updateConversationDraftApi, updateMessageShareVisibility } from './api-client.js';
|
|
52
|
+
import { sendMessage as sendMessageApi, cancelConversationTurn, cancelQueuedConversationTurn, cancelSubagentRun, compactConversation as compactConversationApi, scheduleContextUsageRefresh, loadConversation as loadConversationApi, loadSharedConversation, updateConversationDraft as updateConversationDraftApi, updateMessageShareVisibility, launchSessionWorker } from './api-client.js';
|
|
53
53
|
import { enqueueOutboxRequest, registerOutboxSync } from './sync-outbox.mjs';
|
|
54
54
|
import { linkifyWorkspaceMentionsInNode, parseAppFileHref, renderMarkdownPreview, rewriteLocalAssetUrlsInNode } from './router.js';
|
|
55
55
|
import { renderAttachmentMarkup, removeComposerAttachments, uploadAttachments, setComposerAttachments, setRepoBrowserSessionInfo, openDriveFilePreview, openWorkspaceFilePreview, openUploadedAttachmentViewer } from './attachments-view.js';
|
|
@@ -88,8 +88,14 @@ import {
|
|
|
88
88
|
normalizeRelayActivityEntry,
|
|
89
89
|
promotedCompactBoundaryEntry,
|
|
90
90
|
relayActivityEntryText,
|
|
91
|
+
visibleActivityEntries,
|
|
91
92
|
} from './activity-replay-state.mjs';
|
|
92
|
-
import {
|
|
93
|
+
import {
|
|
94
|
+
COMPACT_PENDING_STAMP,
|
|
95
|
+
SEPARATOR_CLASS,
|
|
96
|
+
syncSeparatorRail,
|
|
97
|
+
syncTranscriptSeparators,
|
|
98
|
+
} from './transcript-separators.mjs';
|
|
93
99
|
import { ABSORBED_MSG_CLASS, STEER_MARKER_CLASS_BY_KIND, syncSteeredTurnMerge } from './steered-turn-merge.mjs';
|
|
94
100
|
import { resolveSubagentFold, subagentFoldSummary, toggledSubagentFoldChoice } from './subagent-fold.mjs';
|
|
95
101
|
import { deriveComposerControlState, hasComposerDraft, hasUploadingAttachments } from './composer-control-state.mjs';
|
|
@@ -546,6 +552,7 @@ function syncSendButtonState() {
|
|
|
546
552
|
steeringSupported: conversationSupportsSteering(currentConvId),
|
|
547
553
|
steeringHeld: steeringHold.held,
|
|
548
554
|
steeringHoldReason: steeringHold.reason,
|
|
555
|
+
archived: conversations[String(currentConvId || '').trim()]?.archived === true,
|
|
549
556
|
});
|
|
550
557
|
btn.disabled = state.disabled;
|
|
551
558
|
btn.dataset.action = state.action;
|
|
@@ -1283,6 +1290,15 @@ function createMessageNode(msg, msgId = null, force = false) {
|
|
|
1283
1290
|
const steerMarkerClass = msg.role === 'assistant' ? STEER_MARKER_CLASS_BY_KIND[messageKind] : null;
|
|
1284
1291
|
if (steerMarkerClass) div.classList.add(steerMarkerClass);
|
|
1285
1292
|
const isStoppedSteer = msg.role === 'assistant' && messageKind === 'stopped';
|
|
1293
|
+
// The relay's note while this session's worker cannot be started
|
|
1294
|
+
// (worker-launch-note-service.mjs), rewritten in place as the episode goes
|
|
1295
|
+
// on. Once the relay has stopped trying, the note carries Retry: a launch
|
|
1296
|
+
// for once the cause is fixed.
|
|
1297
|
+
const isWorkerLaunchNote = msg.role === 'assistant' && messageKind.startsWith('worker-launch-');
|
|
1298
|
+
if (isWorkerLaunchNote) div.classList.add('msg-worker-launch', `msg-${messageKind}`);
|
|
1299
|
+
const workerLaunchActionsHtml = (!IS_SHARED_VIEW && msgId && messageKind === 'worker-launch-stopped')
|
|
1300
|
+
? `<div class="msg-bubble-actions"><button type="button" class="bubble-action-btn" data-action="retry-worker-launch" data-message-id="${escHtml(msgId)}" title="Start the worker for this conversation again now">Retry</button></div>`
|
|
1301
|
+
: '';
|
|
1286
1302
|
// A turn answered by a different provider than the conversation is bound to
|
|
1287
1303
|
// (e.g. the Copilot relay answering a Cursor conversation) must be visible,
|
|
1288
1304
|
// not silent: it ran on another plan than the header indicates.
|
|
@@ -1328,12 +1344,14 @@ function createMessageNode(msg, msgId = null, force = false) {
|
|
|
1328
1344
|
// break row immediately above this message (syncTranscriptSeparators reads
|
|
1329
1345
|
// the stamp), so it must not also show up inside the bubble's activity list.
|
|
1330
1346
|
// Only the promoted (last) boundary is hidden — see renderActivityMarkup.
|
|
1347
|
+
// A rendered row is a finished message, so a compaction start without an
|
|
1348
|
+
// end draws no line here (the live bubble owns the running one).
|
|
1331
1349
|
const promotedBoundaryEntry = promotedCompactBoundaryEntry(activities);
|
|
1332
1350
|
const compactBoundary = compactBoundaryFromActivities(activities);
|
|
1333
1351
|
if (compactBoundary) {
|
|
1334
1352
|
div.dataset.compactBoundary = `${compactBoundary.preTokens ?? ''}|${compactBoundary.postTokens ?? ''}`;
|
|
1335
1353
|
}
|
|
1336
|
-
if (activities
|
|
1354
|
+
if (visibleActivityEntries(activities, promotedBoundaryEntry).length) div.classList.add('msg-with-activity');
|
|
1337
1355
|
const thoughts = Array.isArray(msg.thoughts) ? msg.thoughts.filter((t) => t && String(t.text || '').trim()) : [];
|
|
1338
1356
|
const attachmentHtml = attachments.length ? renderAttachmentMarkup(attachments, { messageId: msgId }) : '';
|
|
1339
1357
|
const subagentRuns = Array.isArray(msg.subagentRuns) ? msg.subagentRuns.filter(Boolean) : [];
|
|
@@ -1404,13 +1422,14 @@ function createMessageNode(msg, msgId = null, force = false) {
|
|
|
1404
1422
|
? `<div class="msg-bubble-actions"><button type="button" class="bubble-action-btn" data-action="resend-stopped-steer" data-message-id="${escHtml(msgId)}" title="Send this message again as a new turn"${resendState ? ' disabled' : ''}>${resendLabel}</button></div>`
|
|
1405
1423
|
: '';
|
|
1406
1424
|
|
|
1407
|
-
// Shared viewers get the badge without the link to the other relay
|
|
1425
|
+
// Shared viewers get the badge without the link to the other relay (or, for
|
|
1426
|
+
// an agent on this relay, to the conversation it runs in).
|
|
1408
1427
|
const originBadgeHtml = remoteOrigin
|
|
1409
|
-
? renderRemoteOriginBadgeHtml(remoteOrigin, { linkable: !IS_SHARED_VIEW })
|
|
1428
|
+
? renderRemoteOriginBadgeHtml(remoteOrigin, { linkable: !IS_SHARED_VIEW, conversations })
|
|
1410
1429
|
: '';
|
|
1411
1430
|
|
|
1412
1431
|
div.innerHTML = `
|
|
1413
|
-
<div class="${bubbleClass}">${shareVisibilityActionHtml}${thoughtsHtml}${originBadgeHtml}${content}${relayErrorCtaHtml}${attachmentHtml}${activityHtml}${subagentHtml}${workflowRunsHtml}${previewCardsHtml}${userBubbleActionsHtml}${stoppedSteerActionsHtml}</div>
|
|
1432
|
+
<div class="${bubbleClass}">${shareVisibilityActionHtml}${thoughtsHtml}${originBadgeHtml}${content}${relayErrorCtaHtml}${attachmentHtml}${activityHtml}${subagentHtml}${workflowRunsHtml}${previewCardsHtml}${userBubbleActionsHtml}${stoppedSteerActionsHtml}${workerLaunchActionsHtml}</div>
|
|
1414
1433
|
<div class="msg-label">${label}${modelTag}${reasoningTag}${modeTag}${autoTag}${continuationTag}${crossProviderTag}${usageTurnTag}${usageRemainingTag}${usageStaleTag} · ${fmtDate(msg.timestamp)}</div>`;
|
|
1415
1434
|
|
|
1416
1435
|
const bubble = div.querySelector('.msg-bubble');
|
|
@@ -1468,7 +1487,15 @@ function insertMessageNode(node, insertAfterId = null) {
|
|
|
1468
1487
|
const anchorId = String(insertAfterId || '').trim();
|
|
1469
1488
|
const anchor = anchorId ? el.querySelector(`[data-message-id="${anchorId}"]`) : null;
|
|
1470
1489
|
if (anchor && anchor.parentNode === el) {
|
|
1471
|
-
|
|
1490
|
+
let next = anchor.nextSibling;
|
|
1491
|
+
// The line of a compaction that ran in this turn sits between the user
|
|
1492
|
+
// message and its live bubble; the reply lands under it, so the
|
|
1493
|
+
// separator pass relabels that line instead of replacing it.
|
|
1494
|
+
if (next?.nodeType === 1
|
|
1495
|
+
&& next.classList?.contains(SEPARATOR_CLASS)
|
|
1496
|
+
&& next.dataset?.separatorKey === `compact:${anchorId}`) {
|
|
1497
|
+
next = next.nextSibling;
|
|
1498
|
+
}
|
|
1472
1499
|
if (next) el.insertBefore(node, next);
|
|
1473
1500
|
else el.appendChild(node);
|
|
1474
1501
|
} else {
|
|
@@ -1593,11 +1620,12 @@ export function renderActivityMarkup(activities, promotedBoundaryEntry = undefin
|
|
|
1593
1620
|
// ones stay visible here as prose rather than disappearing from the
|
|
1594
1621
|
// transcript altogether (one break row per message is all the separator
|
|
1595
1622
|
// planner can place).
|
|
1623
|
+
// A compaction's start and cancel steps are never prose either.
|
|
1596
1624
|
const list = Array.isArray(activities) ? activities : [];
|
|
1597
1625
|
const promoted = promotedBoundaryEntry === undefined
|
|
1598
1626
|
? promotedCompactBoundaryEntry(list)
|
|
1599
1627
|
: promotedBoundaryEntry;
|
|
1600
|
-
const visible = list
|
|
1628
|
+
const visible = visibleActivityEntries(list, promoted);
|
|
1601
1629
|
const progress = visible.filter((item) => relayActivityEntryText(item).startsWith('● '));
|
|
1602
1630
|
const tools = visible.filter((item) => !relayActivityEntryText(item).startsWith('● '));
|
|
1603
1631
|
const progressHtml = progress.length
|
|
@@ -1700,10 +1728,16 @@ function anchorThinkingBubble(div, messageId) {
|
|
|
1700
1728
|
const id = String(messageId || '').trim();
|
|
1701
1729
|
const target = id ? el.querySelector(`[data-message-id="${id}"]`) : null;
|
|
1702
1730
|
if (target && target.parentNode === el) {
|
|
1703
|
-
|
|
1731
|
+
// The bubble's own compaction line sits between the two.
|
|
1732
|
+
let follower = target.nextSibling;
|
|
1733
|
+
while (follower && follower !== div && follower.classList?.contains('transcript-separator')) {
|
|
1734
|
+
follower = follower.nextSibling;
|
|
1735
|
+
}
|
|
1736
|
+
if (follower === div) return;
|
|
1704
1737
|
const next = target.nextSibling;
|
|
1705
1738
|
if (next) el.insertBefore(div, next);
|
|
1706
1739
|
else el.appendChild(div);
|
|
1740
|
+
if (div.dataset.compactBoundary) syncSeparatorsNow();
|
|
1707
1741
|
} else if (div.parentNode !== el) {
|
|
1708
1742
|
el.appendChild(div);
|
|
1709
1743
|
}
|
|
@@ -1760,11 +1794,13 @@ export function showThinking(messageId = null, autoScroll = true) {
|
|
|
1760
1794
|
<div class="thinking-thoughts-list"></div>
|
|
1761
1795
|
</details>
|
|
1762
1796
|
<div id="thinking-stream" class="thinking-stream" hidden></div>
|
|
1797
|
+
<div class="thinking-compacting-note" hidden>Compacting the conversation…</div>
|
|
1763
1798
|
<div class="dots"><span></span><span></span><span></span></div>
|
|
1764
1799
|
<div id="thinking-activity" class="thinking-activity"></div>
|
|
1765
1800
|
<div class="subagent-bubbles-container" data-subagent-bubbles-root="1"></div>
|
|
1766
1801
|
</div>`;
|
|
1767
1802
|
anchorThinkingBubble(div, nextMessageId);
|
|
1803
|
+
syncThinkingCompaction(div);
|
|
1768
1804
|
renderThinkingThoughts();
|
|
1769
1805
|
renderThinkingStream();
|
|
1770
1806
|
// A board the turn has posted already lives in its live bubble.
|
|
@@ -1777,8 +1813,33 @@ export function showThinking(messageId = null, autoScroll = true) {
|
|
|
1777
1813
|
export function removeThinking() {
|
|
1778
1814
|
thinkingMessageId = null;
|
|
1779
1815
|
lastInFlightSnapshotKey = '';
|
|
1816
|
+
const wasCompacting = !!document.getElementById('thinking-indicator')?.dataset?.compactBoundary;
|
|
1780
1817
|
unobserveThinkingBubble();
|
|
1781
1818
|
document.getElementById('thinking-indicator')?.remove();
|
|
1819
|
+
// The bubble's compaction line goes with it, but only after the
|
|
1820
|
+
// caller's same-tick work: a reply appended right after this call takes
|
|
1821
|
+
// the line over (relabelled in place) instead of it being dropped first.
|
|
1822
|
+
if (wasCompacting) queueMicrotask(syncSeparatorsNow);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// The live turn's compaction line: the bubble stamps the turn's compaction
|
|
1826
|
+
// for the separator pass — "Compacting context…" while the last compaction
|
|
1827
|
+
// entry is a start without an end, the final label once the boundary is in —
|
|
1828
|
+
// so the line sits above the bubble and the reply takes it over unchanged.
|
|
1829
|
+
// The bubble's own note shows only while it runs.
|
|
1830
|
+
function syncThinkingCompaction(bubble = document.getElementById('thinking-indicator')) {
|
|
1831
|
+
if (!bubble) return;
|
|
1832
|
+
const items = thinkingMessageId ? (relayActivities.get(thinkingMessageId) || []) : [];
|
|
1833
|
+
const boundary = compactBoundaryFromActivities(items, { processing: true });
|
|
1834
|
+
const note = bubble.querySelector('.thinking-compacting-note');
|
|
1835
|
+
if (note) note.hidden = !boundary?.pending;
|
|
1836
|
+
const stamp = !boundary
|
|
1837
|
+
? ''
|
|
1838
|
+
: (boundary.pending ? COMPACT_PENDING_STAMP : `${boundary.preTokens ?? ''}|${boundary.postTokens ?? ''}`);
|
|
1839
|
+
if ((bubble.dataset.compactBoundary || '') === stamp) return;
|
|
1840
|
+
if (stamp) bubble.dataset.compactBoundary = stamp;
|
|
1841
|
+
else delete bubble.dataset.compactBoundary;
|
|
1842
|
+
if (bubble.parentNode) syncSeparatorsNow();
|
|
1782
1843
|
}
|
|
1783
1844
|
|
|
1784
1845
|
// Stop is one tap away on a sticky header, so a stray touch must not kill a
|
|
@@ -1812,6 +1873,19 @@ function openStopTurnConfirmation(conversationId, messageId) {
|
|
|
1812
1873
|
}, { once: true });
|
|
1813
1874
|
}
|
|
1814
1875
|
|
|
1876
|
+
/** The Stop confirmation for a conversation's running turn, from outside the bubble (the sidebar menu). */
|
|
1877
|
+
export function openStopTurnConfirmationForConversation(conversationId) {
|
|
1878
|
+
const turn = getActiveTurnForConversation(conversationId);
|
|
1879
|
+
if (!turn?.messageId) return false;
|
|
1880
|
+
openStopTurnConfirmation(conversationId, turn.messageId);
|
|
1881
|
+
return true;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
/** Whether a turn of this conversation is running as far as this page knows. */
|
|
1885
|
+
export function conversationHasActiveTurn(conversationId) {
|
|
1886
|
+
return !!getActiveTurnForConversation(conversationId);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1815
1889
|
export function collapseThinkingThoughts() {
|
|
1816
1890
|
const panel = document.getElementById('thinking-thoughts');
|
|
1817
1891
|
if (!(panel instanceof HTMLDetailsElement)) return;
|
|
@@ -1931,6 +2005,7 @@ export function renderThinkingActivities() {
|
|
|
1931
2005
|
if (!selectionIntersectsNode(box)) {
|
|
1932
2006
|
patchActivityList(box, mainExpected, 'thinking-activity-item');
|
|
1933
2007
|
}
|
|
2008
|
+
syncThinkingCompaction();
|
|
1934
2009
|
for (const [subagentRunId, expected] of bySubagentRun) {
|
|
1935
2010
|
const bubble = ensureSubagentBubble(subagentRunId);
|
|
1936
2011
|
const runBox = bubble?.querySelector('.subagent-activity');
|
|
@@ -2036,8 +2111,12 @@ export function appendThinkingActivity(item, subagentRunId = null, autoScroll =
|
|
|
2036
2111
|
);
|
|
2037
2112
|
if (!entry) return;
|
|
2038
2113
|
// Same rule as renderThinkingActivities: a boundary becomes a break row on
|
|
2039
|
-
// the persisted message, so it never shows as live prose.
|
|
2040
|
-
|
|
2114
|
+
// the persisted message, so it never shows as live prose. Every
|
|
2115
|
+
// compaction step does move the live turn's compaction line, though.
|
|
2116
|
+
if (isCompactBoundaryActivityEntry(entry)) {
|
|
2117
|
+
syncThinkingCompaction();
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2041
2120
|
const decorated = decorateActivityText(entry.text);
|
|
2042
2121
|
|
|
2043
2122
|
if (subagentRunId) {
|
|
@@ -2773,6 +2852,55 @@ function releaseStoppedSteerResendsOf(messageId) {
|
|
|
2773
2852
|
}
|
|
2774
2853
|
}
|
|
2775
2854
|
|
|
2855
|
+
// Retry on the worker-launch note: ask the relay for a launch now. The relay
|
|
2856
|
+
// rewrites the note itself — the worker started, or the launches are
|
|
2857
|
+
// exhausted again — so the button only has to report a refused request.
|
|
2858
|
+
async function retryWorkerLaunch(conversationId, noteId) {
|
|
2859
|
+
const conversationKey = String(conversationId || '').trim();
|
|
2860
|
+
const id = String(noteId || '').trim();
|
|
2861
|
+
if (!conversationKey || !id) return;
|
|
2862
|
+
const btn = document.querySelector(`.bubble-action-btn[data-message-id="${CSS.escape(id)}"][data-action="retry-worker-launch"]`);
|
|
2863
|
+
if (btn?.disabled) return;
|
|
2864
|
+
const sdkSessionId = String(conversations[conversationKey]?.sdkSessionId || '').trim();
|
|
2865
|
+
if (!sdkSessionId) {
|
|
2866
|
+
showTransientRelayNotice('This conversation has no session to start a worker for.');
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
if (btn) {
|
|
2870
|
+
btn.disabled = true;
|
|
2871
|
+
btn.textContent = 'Retrying…';
|
|
2872
|
+
}
|
|
2873
|
+
try {
|
|
2874
|
+
// apiFetch answers null for a refused request (the route says 409 with
|
|
2875
|
+
// the reason) as for a network failure: both re-arm the button.
|
|
2876
|
+
const result = await launchSessionWorker(sdkSessionId);
|
|
2877
|
+
if (!result || result.ok === false) throw new Error(String(result?.error || 'the relay refused the launch'));
|
|
2878
|
+
} catch (error) {
|
|
2879
|
+
showTransientRelayNotice(`The worker could not be started: ${error?.message || error}`);
|
|
2880
|
+
if (btn) {
|
|
2881
|
+
btn.disabled = false;
|
|
2882
|
+
btn.textContent = 'Retry';
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
// A message the relay rewrote in place (the worker-launch note): the bubble is
|
|
2888
|
+
// rebuilt where it stands, so the transcript keeps its order. False when the
|
|
2889
|
+
// message is not on the page.
|
|
2890
|
+
export function replaceRenderedMessage(msg, msgId) {
|
|
2891
|
+
const el = getMessagesElement();
|
|
2892
|
+
const id = String(msgId || '').trim();
|
|
2893
|
+
if (!el || !id) return false;
|
|
2894
|
+
const existing = el.querySelector(`[data-message-id="${CSS.escape(id)}"]`);
|
|
2895
|
+
if (!existing) return false;
|
|
2896
|
+
const placeholder = document.createComment('replacing');
|
|
2897
|
+
existing.replaceWith(placeholder);
|
|
2898
|
+
seenMessageIds.delete(id);
|
|
2899
|
+
const fresh = createMessageNode(msg, id, true);
|
|
2900
|
+
placeholder.replaceWith(fresh || existing);
|
|
2901
|
+
return Boolean(fresh);
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2776
2904
|
async function resendStoppedSteer(conversationId, markerId) {
|
|
2777
2905
|
const conversationKey = String(conversationId || '').trim();
|
|
2778
2906
|
const id = String(markerId || '').trim();
|
|
@@ -3112,6 +3240,12 @@ function handleBubbleActionClick(event) {
|
|
|
3112
3240
|
void resendStoppedSteer(currentConvId, messageId);
|
|
3113
3241
|
}
|
|
3114
3242
|
|
|
3243
|
+
if (action === 'retry-worker-launch' && messageId) {
|
|
3244
|
+
event.preventDefault();
|
|
3245
|
+
event.stopPropagation();
|
|
3246
|
+
void retryWorkerLaunch(currentConvId, messageId);
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3115
3249
|
if (action === 'stop-subagent' && subagentRunId) {
|
|
3116
3250
|
event.preventDefault();
|
|
3117
3251
|
event.stopPropagation();
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
buildKnownCwdOptions as buildKnownCwdOptionsFromInputs,
|
|
24
24
|
normalizeKnownCwdPath,
|
|
25
25
|
} from './known-cwd-options.mjs';
|
|
26
|
+
import { buildCloudLineModel, cloudHeaderLabel, isClaudeCloudConversation } from './claude-cloud-ui.mjs';
|
|
26
27
|
|
|
27
28
|
export { normalizeKnownCwdPath };
|
|
28
29
|
|
|
@@ -462,6 +463,17 @@ export function syncChatHeaderWorkspaceLabel() {
|
|
|
462
463
|
const labelEl = document.getElementById('chat-title-cwd');
|
|
463
464
|
if (!labelEl) return;
|
|
464
465
|
const convId = String(currentConvId || '').trim();
|
|
466
|
+
const conversation = convId ? (conversations?.[convId] || null) : null;
|
|
467
|
+
if (isClaudeCloudConversation(conversation)) {
|
|
468
|
+
// A cloud chat runs on a clone at Anthropic, so the line names the
|
|
469
|
+
// repository instead of a folder on the relay host.
|
|
470
|
+
const label = cloudHeaderLabel(conversation.cloud);
|
|
471
|
+
labelEl.hidden = !label;
|
|
472
|
+
labelEl.textContent = label;
|
|
473
|
+
if (label) labelEl.title = buildCloudLineModel(conversation.cloud)?.repoUrl || label;
|
|
474
|
+
else labelEl.removeAttribute('title');
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
465
477
|
const cwd = getSelectedConversationCurrentCwd();
|
|
466
478
|
if (!convId || !cwd) {
|
|
467
479
|
labelEl.hidden = true;
|