@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
|
@@ -43,6 +43,8 @@ import { ANSWERED_ELSEWHERE_KIND, STEER_FOLDED_TEXT } from '../../shared/steer-s
|
|
|
43
43
|
import { isLiveResend } from '../services/steer-resend.mjs';
|
|
44
44
|
import { resolveProviderModelSelection } from '../services/provider-model-selection.mjs';
|
|
45
45
|
import { stripRemotePromptHeader } from '../../shared/remote-relay-contract.mjs';
|
|
46
|
+
import { stripModelTierSuffix } from '../../shared/claude-cloud/repo-url.mjs';
|
|
47
|
+
import { buildClaudeCloudDelivery, isClaudeCloudProviderType } from '../services/claude-cloud-session-service.mjs';
|
|
46
48
|
import {
|
|
47
49
|
admitRemoteRelayRequest,
|
|
48
50
|
findMentionedRemoteRelays,
|
|
@@ -114,6 +116,8 @@ function normalizeRequestedProviderType(value = '') {
|
|
|
114
116
|
// "no provider stated" and the guards below could never fire.
|
|
115
117
|
if (normalized === 'grok' || normalized === 'xai' || normalized === 'xai-grok') return 'grok';
|
|
116
118
|
if (normalized === 'claude' || normalized === 'anthropic') return 'claude';
|
|
119
|
+
// Its own provider, never read as `claude`.
|
|
120
|
+
if (normalized === 'claude-cloud') return 'claude-cloud';
|
|
117
121
|
return '';
|
|
118
122
|
}
|
|
119
123
|
|
|
@@ -121,6 +125,7 @@ const BOOTSTRAP_ONLY_PROVIDER_LABELS = Object.freeze({
|
|
|
121
125
|
cursor: 'Cursor',
|
|
122
126
|
grok: 'Grok',
|
|
123
127
|
claude: 'Claude',
|
|
128
|
+
'claude-cloud': 'Claude Cloud',
|
|
124
129
|
});
|
|
125
130
|
|
|
126
131
|
function deriveModelOrigin(model) {
|
|
@@ -589,9 +594,17 @@ async function resolveRelayResponseText({
|
|
|
589
594
|
}
|
|
590
595
|
}
|
|
591
596
|
|
|
592
|
-
|
|
597
|
+
/**
|
|
598
|
+
* `inferFromText`: whether a text shaped like a failure note counts as one
|
|
599
|
+
* when no record came with it. A session worker always sends the record
|
|
600
|
+
* (`terminalError`), so for its publishes (an `attemptId` names the attempt)
|
|
601
|
+
* the text is a reply whatever it says — a reply that names a code after
|
|
602
|
+
* fixing a failed test is a reply. Senders without an attempt (the Copilot
|
|
603
|
+
* extension, a note read from another relay) still report as text.
|
|
604
|
+
*/
|
|
605
|
+
export function resolveTerminalFailurePayload(payload = {}, { fallbackText = null, inferFromText = true } = {}) {
|
|
593
606
|
const body = payload && typeof payload === 'object' ? payload : {};
|
|
594
|
-
const parsedTextFailure = parseTerminalFailureText(body.text);
|
|
607
|
+
const parsedTextFailure = inferFromText ? parseTerminalFailureText(body.text) : null;
|
|
595
608
|
const direct = body.terminalError && typeof body.terminalError === 'object'
|
|
596
609
|
? body.terminalError
|
|
597
610
|
: null;
|
|
@@ -1121,6 +1134,7 @@ export function buildDequeuedRelayMessage({
|
|
|
1121
1134
|
defaultRelayMode,
|
|
1122
1135
|
defaultModel,
|
|
1123
1136
|
getCursorProviderSettings = () => null,
|
|
1137
|
+
getClaudeCloudProviderSettings = () => null,
|
|
1124
1138
|
} = {}) {
|
|
1125
1139
|
if (!msg) return null;
|
|
1126
1140
|
const attachments = parseAttachments(msg.attachments).map(hydrateAttachment).filter(Boolean);
|
|
@@ -1150,6 +1164,17 @@ export function buildDequeuedRelayMessage({
|
|
|
1150
1164
|
.map((value) => String(value || '').trim())
|
|
1151
1165
|
.filter(Boolean);
|
|
1152
1166
|
}
|
|
1167
|
+
// A Claude Cloud worker creates its session from the first delivered
|
|
1168
|
+
// message and sends into it afterwards: the repository, the branch and the
|
|
1169
|
+
// binding it reported ride on every message, so a restarted worker needs
|
|
1170
|
+
// nothing but the delivery. Other providers never get the field.
|
|
1171
|
+
const claudeCloud = isClaudeCloudProviderType(providerType)
|
|
1172
|
+
? buildClaudeCloudDelivery({
|
|
1173
|
+
conversation: stmts.getConvAnyStatus?.get?.(msg.conversation_id) || null,
|
|
1174
|
+
runtimeSession,
|
|
1175
|
+
fallbackEnvironmentId: getClaudeCloudProviderSettings()?.environmentId || '',
|
|
1176
|
+
})
|
|
1177
|
+
: null;
|
|
1153
1178
|
return {
|
|
1154
1179
|
id: msg.id,
|
|
1155
1180
|
conversationId: msg.conversation_id,
|
|
@@ -1163,6 +1188,7 @@ export function buildDequeuedRelayMessage({
|
|
|
1163
1188
|
cursorAgentId: String(runtimeSession?.cursor_agent_id || '').trim() || null,
|
|
1164
1189
|
cursorSubagentModels,
|
|
1165
1190
|
grokNativeSessionId: String(runtimeSession?.grok_native_session_id || '').trim() || null,
|
|
1191
|
+
...(isClaudeCloudProviderType(providerType) ? { claudeCloud } : {}),
|
|
1166
1192
|
reasoningEffort: String(msg.reasoning_effort || '').trim() || null,
|
|
1167
1193
|
contextTier: String(msg.context_tier || '').trim() || 'default',
|
|
1168
1194
|
quality: normalizeOpenAIImageQuality(msg.reasoning_effort),
|
|
@@ -1783,6 +1809,7 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
1783
1809
|
getClaudeProviderSettings = () => ({ enabled: false, model: '', models: [] }),
|
|
1784
1810
|
getCursorProviderSettings = () => ({ enabled: false, model: '', models: [] }),
|
|
1785
1811
|
getGrokProviderSettings = () => ({ enabled: false, model: '', models: [] }),
|
|
1812
|
+
getClaudeCloudProviderSettings = () => ({ enabled: false, defaultModel: '', environmentId: '', models: [] }),
|
|
1786
1813
|
rebindUnstartedOpenAIConversationModel = null,
|
|
1787
1814
|
normalizeRelayMode,
|
|
1788
1815
|
DEFAULT_RELAY_MODE,
|
|
@@ -4166,7 +4193,8 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
4166
4193
|
const runtimeBoundToExplicitProvider = runtimeProviderType === 'openai'
|
|
4167
4194
|
|| runtimeProviderType === 'claude'
|
|
4168
4195
|
|| runtimeProviderType === 'cursor'
|
|
4169
|
-
|| runtimeProviderType === 'grok'
|
|
4196
|
+
|| runtimeProviderType === 'grok'
|
|
4197
|
+
|| runtimeProviderType === 'claude-cloud';
|
|
4170
4198
|
const configuredOpenAIModel = String(configuredOpenAI?.model || '').trim();
|
|
4171
4199
|
const availableOpenAIModels = new Set(
|
|
4172
4200
|
(Array.isArray(configuredOpenAI?.models) ? configuredOpenAI.models : [])
|
|
@@ -4290,6 +4318,17 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
4290
4318
|
});
|
|
4291
4319
|
}
|
|
4292
4320
|
}
|
|
4321
|
+
// Claude Cloud conversations are bootstrap-created only as well: each
|
|
4322
|
+
// carries the repository its cloud session clones. Only an explicit
|
|
4323
|
+
// provider counts here — the cloud's model ids are the Claude provider's
|
|
4324
|
+
// own, so an id alone never means "cloud".
|
|
4325
|
+
const runtimeUsesClaudeCloud = runtimeProviderType === 'claude-cloud';
|
|
4326
|
+
if (!shouldCreateConversation && !runtimeUsesClaudeCloud && requestedProviderType === 'claude-cloud') {
|
|
4327
|
+
return res.status(409).json({
|
|
4328
|
+
error: 'Claude Cloud requires creating a new Claude Cloud conversation',
|
|
4329
|
+
code: 'CLAUDE_CLOUD_REQUIRES_NEW_CONVERSATION',
|
|
4330
|
+
});
|
|
4331
|
+
}
|
|
4293
4332
|
if (
|
|
4294
4333
|
useOpenAIProvider
|
|
4295
4334
|
&& requestedOpenAIModel
|
|
@@ -4422,6 +4461,32 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
4422
4461
|
const grokModel = runtimeUsesGrok
|
|
4423
4462
|
? (pinnedGrokModel || String(configuredGrok?.model || '').trim())
|
|
4424
4463
|
: '';
|
|
4464
|
+
// Claude Cloud conversations resolve models against the cloud provider's
|
|
4465
|
+
// own list (plain ids, no "[1m]" tier). The model is part of the cloud
|
|
4466
|
+
// session's configuration: it can still change before the first message
|
|
4467
|
+
// creates that session, and is locked afterwards.
|
|
4468
|
+
const configuredClaudeCloud = runtimeUsesClaudeCloud ? getClaudeCloudProviderSettings() : null;
|
|
4469
|
+
const pinnedClaudeCloudModel = runtimeUsesClaudeCloud
|
|
4470
|
+
? stripModelTierSuffix(existingRuntimeSession?.provider_model)
|
|
4471
|
+
: '';
|
|
4472
|
+
const claudeCloudModelSelection = runtimeUsesClaudeCloud
|
|
4473
|
+
? resolveProviderModelSelection({
|
|
4474
|
+
requestedModel: stripModelTierSuffix(model),
|
|
4475
|
+
configuredModel: pinnedClaudeCloudModel || String(configuredClaudeCloud?.defaultModel || '').trim(),
|
|
4476
|
+
availableModels: Array.isArray(configuredClaudeCloud?.models) ? configuredClaudeCloud.models : [],
|
|
4477
|
+
})
|
|
4478
|
+
: null;
|
|
4479
|
+
if (claudeCloudModelSelection && !claudeCloudModelSelection.ok) {
|
|
4480
|
+
return res.status(400).json({
|
|
4481
|
+
error: `Claude Cloud model "${claudeCloudModelSelection.requestedModel}" is not available`,
|
|
4482
|
+
code: 'CLAUDE_CLOUD_MODEL_UNAVAILABLE',
|
|
4483
|
+
supportedModels: claudeCloudModelSelection.availableModels || [],
|
|
4484
|
+
});
|
|
4485
|
+
}
|
|
4486
|
+
// A cloud session's model can be switched between turns (the worker
|
|
4487
|
+
// sends set_model before the message), so a changed model is taken as
|
|
4488
|
+
// for a local Claude chat.
|
|
4489
|
+
const claudeCloudModel = claudeCloudModelSelection ? claudeCloudModelSelection.model : '';
|
|
4425
4490
|
const modelResolution = useOpenAIProvider
|
|
4426
4491
|
? {
|
|
4427
4492
|
ok: !!openAIModel,
|
|
@@ -4454,7 +4519,15 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
4454
4519
|
reasoningEffort: null,
|
|
4455
4520
|
error: grokModel ? null : 'Grok model is not configured',
|
|
4456
4521
|
}
|
|
4457
|
-
:
|
|
4522
|
+
: (runtimeUsesClaudeCloud
|
|
4523
|
+
? {
|
|
4524
|
+
ok: !!claudeCloudModel,
|
|
4525
|
+
model: claudeCloudModel,
|
|
4526
|
+
modelVariantId: claudeCloudModel,
|
|
4527
|
+
reasoningEffort: null,
|
|
4528
|
+
error: claudeCloudModel ? null : 'Claude Cloud model is not configured',
|
|
4529
|
+
}
|
|
4530
|
+
: resolveRequestedModel(model)))));
|
|
4458
4531
|
if (!modelResolution.ok) return res.status(400).json({ error: modelResolution.error, supportedModels: modelResolution.available || [] });
|
|
4459
4532
|
const requestedModel = String(modelResolution.model || '').trim();
|
|
4460
4533
|
const requestedAutoModel = requestedModel.toLowerCase() === AUTO_MODEL_SENTINEL;
|
|
@@ -4523,10 +4596,13 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
4523
4596
|
? resolveCursorReasoningEffort()
|
|
4524
4597
|
: (runtimeUsesGrok
|
|
4525
4598
|
? resolveGrokReasoningEffort()
|
|
4526
|
-
:
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4599
|
+
// A cloud session takes no effort: whatever was asked, it is none.
|
|
4600
|
+
: (runtimeUsesClaudeCloud
|
|
4601
|
+
? { ok: true, effort: 'none', supported: ['none'] }
|
|
4602
|
+
: resolveRequestedReasoningEffort(
|
|
4603
|
+
requestedModel,
|
|
4604
|
+
explicitReasoningEffort || modelResolution.reasoningEffort || null,
|
|
4605
|
+
)))));
|
|
4530
4606
|
if (!reasoningResolution?.ok && !explicitReasoningEffort) {
|
|
4531
4607
|
const supportedEfforts = Array.isArray(reasoningResolution?.supported)
|
|
4532
4608
|
? reasoningResolution.supported.map((value) => String(value || '').trim().toLowerCase()).filter(Boolean)
|
|
@@ -4553,6 +4629,9 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
4553
4629
|
}
|
|
4554
4630
|
} else if (!['default', 'long_context'].includes(requestedContextTier)) {
|
|
4555
4631
|
return res.status(400).json({ error: 'Unsupported context tier' });
|
|
4632
|
+
} else if (runtimeUsesClaudeCloud) {
|
|
4633
|
+
// The cloud has no long-context tier to pick.
|
|
4634
|
+
normalizedContextTier = 'default';
|
|
4556
4635
|
}
|
|
4557
4636
|
if (!requestedRelayMode) return res.status(400).json({ error: 'Unsupported relay mode' });
|
|
4558
4637
|
const conversationWorkspaceState = (!shouldCreateConversation && typeof resolveConversationWorkspaceState === 'function')
|
|
@@ -4784,6 +4863,17 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
4784
4863
|
) {
|
|
4785
4864
|
stmts.updateRuntimeSessionProvider.run('claude', claudeModel, claudeModel, now, runtimeSession.id);
|
|
4786
4865
|
}
|
|
4866
|
+
// A cloud conversation follows the composer like a Claude one: the
|
|
4867
|
+
// worker switches the sandbox's model before the message.
|
|
4868
|
+
if (
|
|
4869
|
+
runtimeUsesClaudeCloud
|
|
4870
|
+
&& claudeCloudModel
|
|
4871
|
+
&& runtimeSession?.id
|
|
4872
|
+
&& claudeCloudModel !== String(existingRuntimeSession?.provider_model || '').trim()
|
|
4873
|
+
&& typeof stmts.updateRuntimeSessionProvider?.run === 'function'
|
|
4874
|
+
) {
|
|
4875
|
+
stmts.updateRuntimeSessionProvider.run('claude-cloud', claudeCloudModel, claudeCloudModel, now, runtimeSession.id);
|
|
4876
|
+
}
|
|
4787
4877
|
// Another relay's agent picks the model and mode of its own turn; the
|
|
4788
4878
|
// composer keeps restoring the user's choice for this conversation.
|
|
4789
4879
|
conversationPreferences = remoteRequest.remote && !shouldCreateConversation
|
|
@@ -5057,7 +5147,9 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
5057
5147
|
// in either must reach the status payload (composer gate, bubble
|
|
5058
5148
|
// Cancel), so it counts as a change like the hold fields do.
|
|
5059
5149
|
|| (stored.supported === true) !== (steering.supported === true)
|
|
5060
|
-
|| storedCancellableIds.join('\n') !== steering.cancellableIds.join('\n')
|
|
5150
|
+
|| storedCancellableIds.join('\n') !== steering.cancellableIds.join('\n')
|
|
5151
|
+
// The running CLI's compaction window (context-usage modal).
|
|
5152
|
+
|| JSON.stringify(stored.autoCompactWindow ?? null) !== JSON.stringify(steering.autoCompactWindow ?? null);
|
|
5061
5153
|
if (workerForSteering && changed) {
|
|
5062
5154
|
sessionWorkerRegistry?.upsertWorker?.({ ...workerForSteering, steering });
|
|
5063
5155
|
// Pushed rather than left for the clients' status poll: the
|
|
@@ -5285,7 +5377,10 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
5285
5377
|
if (!runtimeSession) {
|
|
5286
5378
|
return res.status(404).json({ error: 'Runtime session not found for conversation' });
|
|
5287
5379
|
}
|
|
5288
|
-
|
|
5380
|
+
// The Claude Cloud worker reports the same shape (the cloud session's
|
|
5381
|
+
// context and token usage), so both providers are taken here.
|
|
5382
|
+
const contextUsageProvider = String(runtimeSession.provider_type || 'github').trim().toLowerCase();
|
|
5383
|
+
if (contextUsageProvider !== 'claude' && contextUsageProvider !== 'claude-cloud') {
|
|
5289
5384
|
return res.status(409).json({ error: 'Conversation is not bound to the Claude provider' });
|
|
5290
5385
|
}
|
|
5291
5386
|
if (typeof stmts.updateRuntimeSessionContextUsage?.run !== 'function') {
|
|
@@ -5345,13 +5440,17 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
5345
5440
|
// Claude-bound conversation's worker may write the Claude card. The
|
|
5346
5441
|
// snapshot itself is global, so a misrouted poster would corrupt it for
|
|
5347
5442
|
// everyone.
|
|
5443
|
+
// A Claude Cloud worker posts here too; its report is kept under its own
|
|
5444
|
+
// snapshot key, so the Claude card stays what the Claude workers wrote.
|
|
5348
5445
|
const conversationId = String(req.body?.conversationId || '').trim();
|
|
5446
|
+
let snapshotProvider = 'claude';
|
|
5349
5447
|
if (conversationId) {
|
|
5350
5448
|
const runtimeSession = stmts.getRuntimeSessionByConversation?.get?.(conversationId) || null;
|
|
5351
5449
|
const boundProvider = String(runtimeSession?.provider_type || 'github').trim().toLowerCase();
|
|
5352
|
-
if (runtimeSession && boundProvider !== 'claude') {
|
|
5450
|
+
if (runtimeSession && boundProvider !== 'claude' && boundProvider !== 'claude-cloud') {
|
|
5353
5451
|
return res.status(409).json({ error: 'Conversation is not bound to the Claude provider' });
|
|
5354
5452
|
}
|
|
5453
|
+
if (runtimeSession && boundProvider === 'claude-cloud') snapshotProvider = 'claude-cloud';
|
|
5355
5454
|
}
|
|
5356
5455
|
const usage = normalizeClaudePlanUsage(req.body?.usage)
|
|
5357
5456
|
|| claudePlanUsageFromResult({
|
|
@@ -5359,7 +5458,7 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
5359
5458
|
totalCostUsd: req.body?.totalCostUsd,
|
|
5360
5459
|
});
|
|
5361
5460
|
if (!usage) return res.status(400).json({ error: 'Missing usable usage payload' });
|
|
5362
|
-
planUsageService.saveSnapshot(
|
|
5461
|
+
planUsageService.saveSnapshot(snapshotProvider, usage, {
|
|
5363
5462
|
source: 'worker',
|
|
5364
5463
|
error: String(req.body?.error || '').trim() || null,
|
|
5365
5464
|
});
|
|
@@ -5373,12 +5472,13 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
5373
5472
|
touchCli();
|
|
5374
5473
|
if (!usageLimitPauseService) return res.status(501).json({ error: 'Usage limit handling is unavailable' });
|
|
5375
5474
|
// The report is the account's, so only a Claude-bound conversation's
|
|
5376
|
-
// worker may write it (same validation as the plan usage card)
|
|
5475
|
+
// worker may write it (same validation as the plan usage card); a Claude
|
|
5476
|
+
// Cloud chat runs on the same account and reports the same limit.
|
|
5377
5477
|
const conversationId = String(req.body?.conversationId || '').trim();
|
|
5378
5478
|
if (conversationId) {
|
|
5379
5479
|
const runtimeSession = stmts.getRuntimeSessionByConversation?.get?.(conversationId) || null;
|
|
5380
5480
|
const boundProvider = String(runtimeSession?.provider_type || 'github').trim().toLowerCase();
|
|
5381
|
-
if (runtimeSession && boundProvider !== 'claude') {
|
|
5481
|
+
if (runtimeSession && boundProvider !== 'claude' && boundProvider !== 'claude-cloud') {
|
|
5382
5482
|
return res.status(409).json({ error: 'Conversation is not bound to the Claude provider' });
|
|
5383
5483
|
}
|
|
5384
5484
|
}
|
|
@@ -5982,6 +6082,7 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
5982
6082
|
defaultRelayMode: DEFAULT_RELAY_MODE,
|
|
5983
6083
|
defaultModel: DEFAULT_MODEL,
|
|
5984
6084
|
getCursorProviderSettings,
|
|
6085
|
+
getClaudeCloudProviderSettings,
|
|
5985
6086
|
});
|
|
5986
6087
|
|
|
5987
6088
|
if (out.attachments.length) {
|
|
@@ -6576,7 +6677,10 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
6576
6677
|
}
|
|
6577
6678
|
};
|
|
6578
6679
|
let trimmedText = String(text || '').trim();
|
|
6579
|
-
let terminalFailure = resolveTerminalFailurePayload(req.body, {
|
|
6680
|
+
let terminalFailure = resolveTerminalFailurePayload(req.body, {
|
|
6681
|
+
fallbackText: trimmedText,
|
|
6682
|
+
inferFromText: !String(req.body?.attemptId || '').trim(),
|
|
6683
|
+
});
|
|
6580
6684
|
// Final digests of background workflows that settled during this turn —
|
|
6581
6685
|
// the transcript's "Finished background task" cards. Same structural
|
|
6582
6686
|
// sanitizer as the live panel rows; junk entries drop silently (this
|
|
@@ -7106,7 +7210,10 @@ export function registerMessagesRoutes(app, deps) {
|
|
|
7106
7210
|
// Providers whose workers turn runtime-initiated activity into continuation
|
|
7107
7211
|
// rows: the Claude worker (background tasks/agents) and the Copilot SDK
|
|
7108
7212
|
// worker (detached background shells) serving both Copilot-CLI providers.
|
|
7109
|
-
|
|
7213
|
+
// A Claude Cloud session can take a turn of its own too: one the agent
|
|
7214
|
+
// starts when its background work reports back, or one started on
|
|
7215
|
+
// claude.ai; the cloud worker follows it and registers it here.
|
|
7216
|
+
const CONTINUATION_PROVIDER_TYPES = new Set(['claude', 'github', 'openai', 'claude-cloud']);
|
|
7110
7217
|
|
|
7111
7218
|
// POST /api/continuation-turn — a session worker's CLI started a turn on its
|
|
7112
7219
|
// own (a background task's notification). The synthetic queue row gives that
|
|
@@ -42,12 +42,15 @@ export function registerRemoteRelayRoutes(app, deps) {
|
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
// Workers ask at start whether to register the remote_relay tool at all
|
|
45
|
+
// Workers ask at start whether to register the remote_relay tool at all:
|
|
46
|
+
// when a relay is paired (`count`), or when this relay's own sessions are
|
|
47
|
+
// open to its agents (`localEnabled`, the local target).
|
|
46
48
|
app.get('/api/remote-relays/summary', auth, (_req, res) => {
|
|
47
49
|
const relays = registry.listPublic();
|
|
48
50
|
res.json({
|
|
49
51
|
count: relays.length,
|
|
50
52
|
online: relays.filter((relay) => relay.lastStatus === 'online').length,
|
|
53
|
+
localEnabled: registry.getAgentSessionsSettings?.().enabled === true,
|
|
51
54
|
});
|
|
52
55
|
});
|
|
53
56
|
|
|
@@ -111,6 +114,17 @@ export function registerRemoteRelayRoutes(app, deps) {
|
|
|
111
114
|
sendResult(res, registry.setSelfSettings(req.body || {}));
|
|
112
115
|
});
|
|
113
116
|
|
|
117
|
+
// Agent sessions: whether this relay's agents may start and use sessions on
|
|
118
|
+
// it, and the longest wait of one tool call. POST takes
|
|
119
|
+
// `{ enabled?, maxWaitSeconds? }` and answers the GET shape plus `ok`.
|
|
120
|
+
app.get('/api/settings/agent-sessions', auth, (_req, res) => {
|
|
121
|
+
res.json(registry.getAgentSessionsSettings());
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
app.post('/api/settings/agent-sessions', auth, (req, res) => {
|
|
125
|
+
sendResult(res, registry.setAgentSessionsSettings(req.body || {}));
|
|
126
|
+
});
|
|
127
|
+
|
|
114
128
|
// PATCH /api/remote-relays/:id — { permission, url, token, tokenMode }.
|
|
115
129
|
app.patch('/api/remote-relays/:id', auth, (req, res) => {
|
|
116
130
|
sendResult(res, registry.update(req.params?.id, req.body || {}));
|