@oar-sh/oar 0.9.15 → 0.9.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/extensions/web-relay/polling/polling-loop.mjs +14 -87
- package/CHANGELOG.md +305 -0
- package/README.md +12 -11
- package/package.json +2 -1
- package/server/claude-worker/claude-attachments.mjs +7 -4
- package/server/claude-worker/claude-remote-relay-tool.mjs +36 -1
- package/server/claude-worker/claude-sdk-adapter.mjs +167 -9
- package/server/claude-worker/claude-session-process.mjs +131 -33
- package/server/claude-worker/claude-session-worker-main.mjs +266 -0
- package/server/claude-worker/claude-session-worker.mjs +2 -253
- package/server/claude-worker/claude-turn-failure.mjs +8 -0
- package/server/claude-worker/claude-turn-publisher.mjs +282 -13
- package/server/claude-worker/claude-usage-totals.mjs +108 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +55 -0
- package/server/copilot-worker/copilot-sdk-adapter.mjs +18 -4
- package/server/copilot-worker/copilot-sdk-session-process.mjs +73 -7
- package/server/db-schema.mjs +115 -0
- package/server/deepseek-worker/deepseek-claude-backend.mjs +433 -0
- package/server/deepseek-worker/deepseek-session-worker.mjs +37 -0
- package/server/migrations/0019-relay-event-subscription-options.mjs +106 -0
- package/server/migrations/0020-conversation-may-create-sessions.mjs +67 -0
- package/server/migrations/0021-conversation-group-agent-marks.mjs +77 -0
- package/server/migrations/0022-relay-schedules.mjs +142 -0
- package/server/migrations/0023-relay-asks.mjs +141 -0
- package/server/migrations/0024-deepseek-usage-totals.mjs +151 -0
- package/server/migrations/0025-deepseek-usage-requests-daily.mjs +179 -0
- package/server/migrations/0026-deepseek-usage-served-models.mjs +222 -0
- package/server/migrations/0027-deepseek-usage-checkpoint-runs.mjs +101 -0
- package/server/migrations/0028-deepseek-usage-checkpoint-request-runs.mjs +99 -0
- package/server/migrations/0029-deepseek-balance-rises.mjs +99 -0
- package/server/migrations/0030-deepseek-usage-actuals.mjs +123 -0
- package/server/public/app/api-client.js +53 -0
- package/server/public/app/ask-user-view.js +3 -0
- package/server/public/app/bootstrap.js +162 -16
- package/server/public/app/context-tier-options.mjs +4 -0
- package/server/public/app/context-usage-view.mjs +17 -0
- package/server/public/app/conversation-group-order.mjs +35 -0
- package/server/public/app/conversation-provider-indicator.mjs +7 -0
- package/server/public/app/conversation-view.js +2 -0
- package/server/public/app/deepseek-settings-ui.js +342 -0
- package/server/public/app/inbox-ui.js +367 -0
- package/server/public/app/inbox-view.mjs +370 -0
- package/server/public/app/journal-view.js +54 -5
- package/server/public/app/model-selector-options.mjs +11 -0
- package/server/public/app/model-vision-support.mjs +2 -0
- package/server/public/app/no-conversation-state.mjs +1 -1
- package/server/public/app/plan-usage-view.mjs +165 -13
- package/server/public/app/provider-readiness.mjs +4 -1
- package/server/public/app/relay-error-ctas.mjs +8 -0
- package/server/public/app/relay-event-subscriptions-ui.js +4 -2
- package/server/public/app/relay-event-view.mjs +33 -7
- package/server/public/app/relay-schedule-view.mjs +119 -0
- package/server/public/app/relay-schedules-ui.js +172 -0
- package/server/public/app/settings-modal.js +11 -0
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/socket-handlers.js +37 -3
- package/server/public/app/store.js +18 -5
- package/server/public/index.html +567 -35
- package/server/relay-tools.md +18 -3
- package/server/repositories/conversation-group-repository.mjs +110 -17
- package/server/repositories/remote-relay-repository.mjs +20 -2
- package/server/repositories/session-repository.mjs +7 -1
- package/server/routes/ask-user-routes.mjs +57 -3
- package/server/routes/cache-routes.mjs +6 -0
- package/server/routes/conversation-group-routes.mjs +19 -3
- package/server/routes/deepseek-usage-import-routes.mjs +72 -0
- package/server/routes/messages-routes.mjs +195 -18
- package/server/routes/relay-ask-routes.mjs +387 -0
- package/server/routes/relay-event-routes.mjs +3 -1
- package/server/routes/relay-schedule-routes.mjs +57 -0
- package/server/routes/sessions-routes.mjs +932 -97
- package/server/server-runtime.mjs +203 -11
- package/server/services/about-service.mjs +1 -0
- package/server/services/cache-rebuild-service.mjs +19 -2
- package/server/services/claude-session-leftovers.mjs +226 -0
- package/server/services/conversation-preferences-service.mjs +17 -0
- package/server/services/deepseek-balance-service.mjs +102 -0
- package/server/services/deepseek-settings-service.mjs +380 -0
- package/server/services/deepseek-usage-import.mjs +471 -0
- package/server/services/model-variant-catalog-service.mjs +1 -0
- package/server/services/plan-usage-contract.mjs +100 -5
- package/server/services/plan-usage-deepseek.mjs +931 -0
- package/server/services/plan-usage-service.mjs +431 -0
- package/server/services/provider-model-selection.mjs +1 -0
- package/server/services/provider-reasoning-effort.mjs +15 -2
- package/server/services/relay-ask-link-service.mjs +174 -0
- package/server/services/relay-ask-service.mjs +604 -0
- package/server/services/relay-event-service.mjs +589 -73
- package/server/services/relay-schedule-service.mjs +596 -0
- package/server/services/remote-relay-caller-context.mjs +89 -20
- package/server/services/remote-relay-dispatcher.mjs +958 -31
- package/server/services/session-worker-launch-service.mjs +233 -82
- package/server/services/worker-secret-redaction.mjs +318 -0
- package/shared/context-window-fallbacks.mjs +3 -0
- package/shared/deepseek-api.mjs +137 -0
- package/shared/deepseek-models.mjs +114 -0
- package/shared/deepseek-pricing.mjs +146 -0
- package/shared/host-claude-session-env.mjs +101 -0
- package/shared/model-id.mjs +7 -0
- package/shared/preview-tool-core.mjs +1 -1
- package/shared/provider-routing.mjs +2 -2
- package/shared/relay-asks.mjs +145 -0
- package/shared/relay-events.mjs +268 -18
- package/shared/relay-schedules.mjs +128 -0
- package/shared/remote-relay-contract.mjs +554 -26
- package/shared/remote-relay-tool-core.mjs +16 -5
- package/shared/steer-settle-failure.mjs +1 -0
- package/shared/worker-runtime/direct-openai-image.mjs +128 -0
- package/shared/worker-runtime/worker-log-file.mjs +60 -0
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
STEER_STOPPED_TEXT,
|
|
54
54
|
} from '../../shared/steer-settle-markers.mjs';
|
|
55
55
|
import { stripSteerNote, withSteerNoteContent } from '../../shared/steer-note.mjs';
|
|
56
|
-
import {
|
|
56
|
+
import { isRelayAutomatedDelivery } from '../../shared/relay-events.mjs';
|
|
57
57
|
import {
|
|
58
58
|
stripSlashCommandGuard,
|
|
59
59
|
stripSlashCommandGuardContent,
|
|
@@ -532,6 +532,9 @@ export function createClaudeSessionRunner({
|
|
|
532
532
|
// by the first turn to open regardless.
|
|
533
533
|
compactReplayAdoptionMs = 60_000,
|
|
534
534
|
continuationRetryDelayMs = 500,
|
|
535
|
+
// How long a delivered turn that opens over a finished continuation waits
|
|
536
|
+
// for that continuation's row to register, so its answer publishes first.
|
|
537
|
+
continuationRegistrationWaitMs = 10_000,
|
|
535
538
|
// Backoff between attempts to save a consumed steer's settle marker (~31 s
|
|
536
539
|
// in all); after the last one the row fails terminally instead.
|
|
537
540
|
settleRetryDelaysMs = [1_000, 2_000, 4_000, 8_000, 8_000, 8_000],
|
|
@@ -574,13 +577,30 @@ export function createClaudeSessionRunner({
|
|
|
574
577
|
// Records every sub-agent's frames as its timeline, run-scoped (see
|
|
575
578
|
// subagent-timeline-recorder.mjs). Injectable for tests.
|
|
576
579
|
subagentTimelineRecorder = null,
|
|
580
|
+
// Another endpoint for the CLI (../deepseek-worker/deepseek-claude-backend.mjs):
|
|
581
|
+
// its spawn environment, model ids and effort, and what it reports. null is
|
|
582
|
+
// Claude through the host's login.
|
|
583
|
+
backend = null,
|
|
577
584
|
dbg = () => {},
|
|
578
585
|
} = {}) {
|
|
586
|
+
// Neither Claude's plan windows nor its usage limit exist on another backend.
|
|
587
|
+
const readPlanUsageForTurn = backend && !backend.reportsPlanUsage ? async () => null : readPlanUsageImpl;
|
|
579
588
|
const timelineRecorder = subagentTimelineRecorder || createSubagentTimelineRecorder({ api, conversationId: sdkSessionId, dbg });
|
|
580
589
|
const publisher = createClaudeTurnPublisher({
|
|
581
590
|
api,
|
|
582
591
|
dbg,
|
|
583
592
|
takeWorkflowRuns: () => drainSettledWorkflowRuns(),
|
|
593
|
+
...(backend
|
|
594
|
+
? {
|
|
595
|
+
describeFailure: backend.describeFailure,
|
|
596
|
+
runtimeLabel: backend.label || 'DeepSeek',
|
|
597
|
+
failureCodePrefix: backend.id || 'deepseek',
|
|
598
|
+
reportsPlanUsage: backend.reportsPlanUsage !== false,
|
|
599
|
+
usageRoute: backend.usageRoute || null,
|
|
600
|
+
usageModelKey: backend.relayModel,
|
|
601
|
+
relayModel: (value) => (value ? backend.relayModel(value) : value),
|
|
602
|
+
}
|
|
603
|
+
: {}),
|
|
584
604
|
});
|
|
585
605
|
// `remote_relay` is registered only while this relay has paired remotes.
|
|
586
606
|
// Asked once when the worker starts, and again whenever a CLI process winds
|
|
@@ -591,7 +611,9 @@ export function createClaudeSessionRunner({
|
|
|
591
611
|
void remoteRelayGate.refresh();
|
|
592
612
|
// The subscription's usage limit as the CLI reports it. Runner-level: the
|
|
593
613
|
// limit is the account's, not the CLI process's.
|
|
594
|
-
const usageLimit =
|
|
614
|
+
const usageLimit = backend && !backend.reportsUsageLimit
|
|
615
|
+
? { observe: () => null, classifyResult: () => null, classifyException: () => null }
|
|
616
|
+
: createUsageLimitTracker();
|
|
595
617
|
const resolveWorkflowSessionDir = resolveWorkflowSessionDirImpl || createDefaultWorkflowSessionDirResolver();
|
|
596
618
|
let claudeNativeSessionId = '';
|
|
597
619
|
let proc = null;
|
|
@@ -657,13 +679,19 @@ export function createClaudeSessionRunner({
|
|
|
657
679
|
// Row id → the latest in-flight /api/queue-consumed POST touching it.
|
|
658
680
|
const consumedMarkChains = new Map();
|
|
659
681
|
|
|
660
|
-
|
|
682
|
+
/**
|
|
683
|
+
* `cliStartedAt` (ms) is a backend CLI's spawn time, sent at its first
|
|
684
|
+
* init even when the session id is the known one: the relay records each
|
|
685
|
+
* CLI start of a DeepSeek session to find its shell snapshot on delete.
|
|
686
|
+
*/
|
|
687
|
+
async function persistNativeSessionId(conversationId, sessionId, { cliStartedAt = null } = {}) {
|
|
661
688
|
const normalized = String(sessionId || '').trim();
|
|
662
|
-
if (!normalized || normalized === claudeNativeSessionId) return;
|
|
689
|
+
if (!normalized || (normalized === claudeNativeSessionId && !cliStartedAt)) return;
|
|
663
690
|
try {
|
|
664
691
|
await api('POST', '/api/claude-native-session', {
|
|
665
692
|
conversationId,
|
|
666
693
|
claudeNativeSessionId: normalized,
|
|
694
|
+
...(cliStartedAt ? { cliStartedAt } : {}),
|
|
667
695
|
});
|
|
668
696
|
// Only cache after the server accepted it, so a failed persist is
|
|
669
697
|
// retried on the next init — resume across worker restarts depends on
|
|
@@ -678,7 +706,8 @@ export function createClaudeSessionRunner({
|
|
|
678
706
|
// Per-message model wins so the composer can switch Claude models between
|
|
679
707
|
// turns; the conversation's provider model and worker default are fallbacks.
|
|
680
708
|
const requestedModel = String(message.model || '').trim();
|
|
681
|
-
const
|
|
709
|
+
const ownModel = backend ? backend.isModel(requestedModel) : requestedModel.toLowerCase().startsWith('claude-');
|
|
710
|
+
const perTurnModel = requestedModel.toLowerCase() !== 'auto' && ownModel
|
|
682
711
|
? requestedModel
|
|
683
712
|
: '';
|
|
684
713
|
return perTurnModel
|
|
@@ -813,6 +842,12 @@ export function createClaudeSessionRunner({
|
|
|
813
842
|
modelUsage: null,
|
|
814
843
|
},
|
|
815
844
|
planBoardPosted: false,
|
|
845
|
+
// The CLI process the turn ran in, as its usage report names it: taken
|
|
846
|
+
// from the process here and again when the context is made active, and
|
|
847
|
+
// read from the context at finalize — a late continuation flush
|
|
848
|
+
// finalizes after the process died, when `proc` is null or a newer one.
|
|
849
|
+
runId: proc?.runId || '',
|
|
850
|
+
resumedSessionId: proc?.resumedSessionId || '',
|
|
816
851
|
// Set only on a context attached by post-compaction adoption, and only
|
|
817
852
|
// until that turn produces output — see handBackAdoptedContext.
|
|
818
853
|
adoptedFromCompaction: false,
|
|
@@ -846,6 +881,8 @@ export function createClaudeSessionRunner({
|
|
|
846
881
|
|
|
847
882
|
function activateContext(ctx) {
|
|
848
883
|
proc.activeCtx = ctx;
|
|
884
|
+
ctx.runId = proc.runId || '';
|
|
885
|
+
ctx.resumedSessionId = proc.resumedSessionId || '';
|
|
849
886
|
proc.lastBoundary = null;
|
|
850
887
|
proc.continuationInitPending = false;
|
|
851
888
|
proc.notificationPendingAt = 0;
|
|
@@ -1173,7 +1210,7 @@ export function createClaudeSessionRunner({
|
|
|
1173
1210
|
}
|
|
1174
1211
|
|
|
1175
1212
|
/** Wait until a continuation context has its relay queue row (or gave up). */
|
|
1176
|
-
async function waitForContextRegistration(ctx, timeoutMs =
|
|
1213
|
+
async function waitForContextRegistration(ctx, timeoutMs = continuationRegistrationWaitMs) {
|
|
1177
1214
|
const deadline = Date.now() + timeoutMs;
|
|
1178
1215
|
while (!ctx.registered && !ctx.discarded && Date.now() < deadline) {
|
|
1179
1216
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
@@ -1432,9 +1469,13 @@ export function createClaudeSessionRunner({
|
|
|
1432
1469
|
ctx.finalized = true;
|
|
1433
1470
|
controlPoller?.stop?.(ctx.controlState);
|
|
1434
1471
|
// A late continuation flush can land after the process died; usage reads
|
|
1435
|
-
// and model fallbacks must survive that.
|
|
1472
|
+
// and model fallbacks must survive that. The process the report names is
|
|
1473
|
+
// the context's own (set when it was made active), never the one that
|
|
1474
|
+
// happens to be current now.
|
|
1436
1475
|
const turnRef = proc?.turn || null;
|
|
1437
1476
|
const procModel = proc?.model || null;
|
|
1477
|
+
const runId = ctx.runId || '';
|
|
1478
|
+
const resumedSessionId = ctx.resumedSessionId || '';
|
|
1438
1479
|
// Steers pushed into this turn and still unsettled: the CLI has their
|
|
1439
1480
|
// prompts, so the relay marks them consumed with this turn's commit and
|
|
1440
1481
|
// never re-runs them even if this worker dies before settling them.
|
|
@@ -1461,12 +1502,12 @@ export function createClaudeSessionRunner({
|
|
|
1461
1502
|
await publisher.publishFinalStream(ctx.message, ctx.state.lastStreamedText);
|
|
1462
1503
|
const [contextUsage, planUsage] = await Promise.all([
|
|
1463
1504
|
readContextUsageImpl(turnRef, dbg),
|
|
1464
|
-
|
|
1505
|
+
readPlanUsageForTurn(turnRef, dbg),
|
|
1465
1506
|
]);
|
|
1466
1507
|
ctx.state.contextUsage = contextUsage;
|
|
1467
1508
|
ctx.state.planUsage = planUsage;
|
|
1468
1509
|
await publisher.publishContextUsage({ message: ctx.message, state: ctx.state, model: procModel, sdkSessionId });
|
|
1469
|
-
await publisher.publishPlanUsage({ message: ctx.message, state: ctx.state, sdkSessionId });
|
|
1510
|
+
await publisher.publishPlanUsage({ message: ctx.message, state: ctx.state, sdkSessionId, runId, resumedSessionId });
|
|
1470
1511
|
})().catch((error) => dbg('stopped-turn publish failed', error?.message || String(error)));
|
|
1471
1512
|
lingerStoppedRow(ctx, publishChain);
|
|
1472
1513
|
closeContext(ctx, true);
|
|
@@ -1478,13 +1519,13 @@ export function createClaudeSessionRunner({
|
|
|
1478
1519
|
// indicator updates with each reply.
|
|
1479
1520
|
const [contextUsage, planUsage] = await Promise.all([
|
|
1480
1521
|
readContextUsageImpl(turnRef, dbg),
|
|
1481
|
-
|
|
1522
|
+
readPlanUsageForTurn(turnRef, dbg),
|
|
1482
1523
|
]);
|
|
1483
1524
|
ctx.state.contextUsage = contextUsage;
|
|
1484
1525
|
ctx.state.planUsage = planUsage;
|
|
1485
1526
|
const responseModel = ctx.state.responseModel || procModel || null;
|
|
1486
1527
|
await publisher.publishContextUsage({ message: ctx.message, state: ctx.state, model: procModel, sdkSessionId });
|
|
1487
|
-
await publisher.publishPlanUsage({ message: ctx.message, state: ctx.state, sdkSessionId });
|
|
1528
|
+
await publisher.publishPlanUsage({ message: ctx.message, state: ctx.state, sdkSessionId, runId, resumedSessionId });
|
|
1488
1529
|
|
|
1489
1530
|
if (ctx.state.result?.isError) {
|
|
1490
1531
|
await publisher.publishErrorResult({ message: ctx.message, state: ctx.state, responseModel, consumedSteerIds });
|
|
@@ -1511,10 +1552,17 @@ export function createClaudeSessionRunner({
|
|
|
1511
1552
|
}
|
|
1512
1553
|
// The transport is gone, so nothing new can be read — but usage already
|
|
1513
1554
|
// captured on this context (a result seen before the stream died) still
|
|
1514
|
-
// reaches the relay. Best-effort by
|
|
1555
|
+
// reaches the relay, under the process the context ran in. Best-effort by
|
|
1556
|
+
// definition of this path.
|
|
1515
1557
|
if (ctx.state.contextUsage || ctx.state.planUsage) {
|
|
1516
1558
|
await publisher.publishContextUsage({ message: ctx.message, state: ctx.state, model, sdkSessionId }).catch(() => {});
|
|
1517
|
-
await publisher.publishPlanUsage({
|
|
1559
|
+
await publisher.publishPlanUsage({
|
|
1560
|
+
message: ctx.message,
|
|
1561
|
+
state: ctx.state,
|
|
1562
|
+
sdkSessionId,
|
|
1563
|
+
runId: ctx.runId || '',
|
|
1564
|
+
resumedSessionId: ctx.resumedSessionId || '',
|
|
1565
|
+
}).catch(() => {});
|
|
1518
1566
|
}
|
|
1519
1567
|
if (ctx.interrupted || aborted) {
|
|
1520
1568
|
await publisher.publishFinalStream(ctx.message, ctx.state.lastStreamedText);
|
|
@@ -1769,7 +1817,13 @@ export function createClaudeSessionRunner({
|
|
|
1769
1817
|
const agentLike = SUBAGENT_TASK_TYPES.has(String(task.taskType || ''));
|
|
1770
1818
|
const pinnedModel = (task.toolUseId && processRef.subagentSpawns.get(task.toolUseId)) || '';
|
|
1771
1819
|
const activity = (task.toolUseId && processRef.subagentActivity.get(task.toolUseId)) || {};
|
|
1772
|
-
const
|
|
1820
|
+
const knownModel = agentLike ? (activity.model || pinnedModel || processRef.initModel || null) : null;
|
|
1821
|
+
// A backend's CLI names a sub-agent's model in Claude's terms (the
|
|
1822
|
+
// spawn's "opus", a Claude id of a CLI without the alias pins): the
|
|
1823
|
+
// badge shows the backend's model that runs it.
|
|
1824
|
+
const model = knownModel && backend?.subagentModel
|
|
1825
|
+
? (backend.subagentModel(knownModel, processRef.initModel || processRef.model) || knownModel)
|
|
1826
|
+
: knownModel;
|
|
1773
1827
|
return {
|
|
1774
1828
|
taskId,
|
|
1775
1829
|
taskType: task.taskType,
|
|
@@ -2393,7 +2447,9 @@ export function createClaudeSessionRunner({
|
|
|
2393
2447
|
// init lands, and skipping that init settled the message with a pointer
|
|
2394
2448
|
// and dropped its real answer.
|
|
2395
2449
|
if (!firstInit && proc.pendingDelivered.length) turnStartedForWaitingDeliveries();
|
|
2396
|
-
persistNativeSessionId(sdkSessionId, sdkMessage.session_id
|
|
2450
|
+
persistNativeSessionId(sdkSessionId, sdkMessage.session_id, {
|
|
2451
|
+
cliStartedAt: backend && firstInit ? proc.spawnedAt : null,
|
|
2452
|
+
}).catch(() => {});
|
|
2397
2453
|
// Tracked on the process too (not only through the persist round-trip):
|
|
2398
2454
|
// the workflow poller needs the native id to find the session dir even
|
|
2399
2455
|
// when the persist call is still in flight or failed.
|
|
@@ -2402,7 +2458,9 @@ export function createClaudeSessionRunner({
|
|
|
2402
2458
|
// normalizer never sees it — capture the CLI's actual model here and
|
|
2403
2459
|
// seed it into every context at activation. Without this, a composer
|
|
2404
2460
|
// set to `auto` published responses with model: null.
|
|
2405
|
-
|
|
2461
|
+
// A backend's CLI names the model in its own terms ("deepseek-flash[1m]").
|
|
2462
|
+
const initModel = String(sdkMessage.model || '').trim();
|
|
2463
|
+
proc.initModel = (backend && initModel ? backend.relayModel(initModel) : initModel) || proc.initModel;
|
|
2406
2464
|
// A fresh init with no active turn AND nothing queued to deliver is the CLI
|
|
2407
2465
|
// opening a turn on its own — a background-task continuation ("you will be
|
|
2408
2466
|
// notified"). The real SDK does NOT emit a user-message replay for these
|
|
@@ -2541,7 +2599,8 @@ export function createClaudeSessionRunner({
|
|
|
2541
2599
|
// the composer pinned a model (adaptProcess repins via setModel). An
|
|
2542
2600
|
// `auto` composer keeps the CLI's own behavior — only the recorded
|
|
2543
2601
|
// model follows the switch.
|
|
2544
|
-
const
|
|
2602
|
+
const reportedFallback = String(sdkMessage.fallbackModel || '').trim();
|
|
2603
|
+
const fallbackModel = backend && reportedFallback ? backend.relayModel(reportedFallback) : reportedFallback;
|
|
2545
2604
|
if (fallbackModel) {
|
|
2546
2605
|
proc.initModel = fallbackModel;
|
|
2547
2606
|
if (String(sdkMessage.scope || '').trim().toLowerCase() === 'session') {
|
|
@@ -3522,10 +3581,7 @@ export function createClaudeSessionRunner({
|
|
|
3522
3581
|
}
|
|
3523
3582
|
continue;
|
|
3524
3583
|
}
|
|
3525
|
-
await finalizeContextOnStreamEnd(ctx, {
|
|
3526
|
-
aborted: processRef.aborted,
|
|
3527
|
-
model: processRef.model,
|
|
3528
|
-
}).catch(() => {});
|
|
3584
|
+
await finalizeContextOnStreamEnd(ctx, { aborted: processRef.aborted, model: processRef.model }).catch(() => {});
|
|
3529
3585
|
}
|
|
3530
3586
|
for (const entry of stoppedSteers) {
|
|
3531
3587
|
await settleFoldedSteeredEntry(entry).catch((error) => {
|
|
@@ -3546,10 +3602,12 @@ export function createClaudeSessionRunner({
|
|
|
3546
3602
|
if (missingFolder) throw missingFolder;
|
|
3547
3603
|
const relayMode = message.relayMode || 'agent';
|
|
3548
3604
|
const model = resolvePerTurnModel(message);
|
|
3549
|
-
|
|
3605
|
+
// A backend's effort is its own (DeepSeek: low/high/max), and so is its
|
|
3606
|
+
// thinking switch (DeepSeek: always on).
|
|
3607
|
+
const effort = backend ? backend.effortFor(message.reasoningEffort) : normalizeClaudeEffort(message.reasoningEffort);
|
|
3550
3608
|
const autoCompactWindow = normalizeAutoCompactWindow(getAutoCompactWindow());
|
|
3551
3609
|
const thinkingState = getThinking() || {};
|
|
3552
|
-
const thinkingEnabled = parseThinkingEnabled(thinkingState.enabled);
|
|
3610
|
+
const thinkingEnabled = backend ? backend.thinkingEnabled(effort) : parseThinkingEnabled(thinkingState.enabled);
|
|
3553
3611
|
const thinkingDisplay = parseThinkingDisplay(thinkingState.display);
|
|
3554
3612
|
const attribution = getAttribution() || null;
|
|
3555
3613
|
const backgroundCommandLimitMs = getBackgroundCommandLimitMs() ?? null;
|
|
@@ -3557,6 +3615,13 @@ export function createClaudeSessionRunner({
|
|
|
3557
3615
|
const processRef = {
|
|
3558
3616
|
turn: null,
|
|
3559
3617
|
abortController,
|
|
3618
|
+
// Names this CLI process in its usage reports: the relay tells a
|
|
3619
|
+
// report that fell behind one of the same process from a process
|
|
3620
|
+
// that started counting over.
|
|
3621
|
+
runId: randomUUID(),
|
|
3622
|
+
// When the CLI was spawned, reported with its first init (see
|
|
3623
|
+
// persistNativeSessionId).
|
|
3624
|
+
spawnedAt: Date.now(),
|
|
3560
3625
|
model,
|
|
3561
3626
|
relayMode,
|
|
3562
3627
|
effort,
|
|
@@ -3574,6 +3639,8 @@ export function createClaudeSessionRunner({
|
|
|
3574
3639
|
thinkingBudget: null,
|
|
3575
3640
|
appendClass: modeAppendClass(relayMode),
|
|
3576
3641
|
permissionMode: permissionModeForRelayMode(relayMode),
|
|
3642
|
+
// What only a fresh CLI of the backend takes (adaptProcess compares it).
|
|
3643
|
+
spawnKey: backend ? backend.spawnKey(message, { model }) : '',
|
|
3577
3644
|
liveTasks: new Map(),
|
|
3578
3645
|
knownTasks: new Set(),
|
|
3579
3646
|
// taskId → the row of a task that left the live set, until its
|
|
@@ -3602,6 +3669,8 @@ export function createClaudeSessionRunner({
|
|
|
3602
3669
|
// from `resume`, updated on init) — the workflow poller's key into
|
|
3603
3670
|
// `~/.claude/projects`.
|
|
3604
3671
|
nativeSessionId: '',
|
|
3672
|
+
// The session the process was spawned to resume ('' for a fresh one).
|
|
3673
|
+
resumedSessionId: '',
|
|
3605
3674
|
// tool_use id → explicitly pinned model of a subagent spawn block; the
|
|
3606
3675
|
// task panel's model column resolves through this at publish time.
|
|
3607
3676
|
subagentSpawns: new Map(),
|
|
@@ -3721,6 +3790,10 @@ export function createClaudeSessionRunner({
|
|
|
3721
3790
|
|
|
3722
3791
|
const resume = String(resumeOverride || message.claudeNativeSessionId || claudeNativeSessionId || '').trim();
|
|
3723
3792
|
processRef.nativeSessionId = resume;
|
|
3793
|
+
// The session this process continues, for its usage reports: the CLI's
|
|
3794
|
+
// first result then carries that session's earlier totals (see
|
|
3795
|
+
// claude-turn-publisher.mjs, runningCounts).
|
|
3796
|
+
processRef.resumedSessionId = resume;
|
|
3724
3797
|
// The CLI resolves `resume` inside the project directory for *this* CWD, so
|
|
3725
3798
|
// a session whose workspace root changed has to bring its transcript along
|
|
3726
3799
|
// or every turn from here on fails with "No conversation found".
|
|
@@ -3752,6 +3825,7 @@ export function createClaudeSessionRunner({
|
|
|
3752
3825
|
|| '',
|
|
3753
3826
|
),
|
|
3754
3827
|
remoteRelayTool: remoteRelayGate.isEnabled(),
|
|
3828
|
+
backend,
|
|
3755
3829
|
dbg,
|
|
3756
3830
|
});
|
|
3757
3831
|
processRef.lifecycleTimer = setInterval(() => evaluateLifecycle(), lifecyclePollMs);
|
|
@@ -3773,6 +3847,11 @@ export function createClaudeSessionRunner({
|
|
|
3773
3847
|
try {
|
|
3774
3848
|
processRef.turn.endInput?.();
|
|
3775
3849
|
} catch {}
|
|
3850
|
+
// The CLI refused a backend's live flag settings (its key and
|
|
3851
|
+
// endpoint pins, claude-sdk-adapter.mjs): nothing was sent, and the
|
|
3852
|
+
// SDK aborted the query, which reads like a stop. The open turns fail
|
|
3853
|
+
// with that note instead.
|
|
3854
|
+
if (!streamError && processRef.turn?.liveFlagSettingsError) streamError = processRef.turn.liveFlagSettingsError;
|
|
3776
3855
|
await cleanupProcess(processRef, streamError);
|
|
3777
3856
|
}
|
|
3778
3857
|
})();
|
|
@@ -3821,7 +3900,18 @@ export function createClaudeSessionRunner({
|
|
|
3821
3900
|
async function adaptProcess(message, delivery = {}) {
|
|
3822
3901
|
const relayMode = message.relayMode || 'agent';
|
|
3823
3902
|
const model = resolvePerTurnModel(message);
|
|
3824
|
-
const effort = normalizeClaudeEffort(message.reasoningEffort);
|
|
3903
|
+
const effort = backend ? backend.effortFor(message.reasoningEffort) : normalizeClaudeEffort(message.reasoningEffort);
|
|
3904
|
+
|
|
3905
|
+
// A backend's effort, and the model its CLI pins Claude Code's aliases
|
|
3906
|
+
// to, are read by the CLI at spawn only: a change takes a fresh process
|
|
3907
|
+
// on the same session, when nothing lives in the old one. Busy, the
|
|
3908
|
+
// delivery runs on the old process (setModel below) and the next
|
|
3909
|
+
// turn-opening one tries again.
|
|
3910
|
+
if (backend && backend.spawnKey(message, { model }) !== proc.spawnKey && canReleaseForRespawn()) {
|
|
3911
|
+
gracefulShutdown('backend-spawn');
|
|
3912
|
+
await drainClosingProcess(10_000);
|
|
3913
|
+
return null;
|
|
3914
|
+
}
|
|
3825
3915
|
|
|
3826
3916
|
// A mode change that would alter the spawn-time system prompt append gets
|
|
3827
3917
|
// a fresh process — but only when nothing lives in the old one.
|
|
@@ -3874,13 +3964,14 @@ export function createClaudeSessionRunner({
|
|
|
3874
3964
|
const repinAfterFallback = Boolean(proc.modelFallback) && Boolean(model);
|
|
3875
3965
|
if (model !== proc.model || repinAfterFallback) {
|
|
3876
3966
|
expectSettingsReinit();
|
|
3877
|
-
|
|
3967
|
+
const cliModel = backend && model ? backend.cliModel(model) : model;
|
|
3968
|
+
await Promise.resolve(proc.turn.setModel?.(cliModel || undefined)).catch((error) => {
|
|
3878
3969
|
dbg('setModel failed', error?.message || String(error));
|
|
3879
3970
|
});
|
|
3880
3971
|
proc.model = model;
|
|
3881
3972
|
if (model) proc.modelFallback = '';
|
|
3882
3973
|
}
|
|
3883
|
-
if (effort !== proc.effort) {
|
|
3974
|
+
if (!backend && effort !== proc.effort) {
|
|
3884
3975
|
// 'ultracode' is not an effortLevel the CLI accepts — it maps to the
|
|
3885
3976
|
// session-scoped ultracode/enableWorkflows flags (with the effort pinned
|
|
3886
3977
|
// to the xhigh the flag implies); every other change clears those flags
|
|
@@ -3922,7 +4013,8 @@ export function createClaudeSessionRunner({
|
|
|
3922
4013
|
const thinkingEnabled = parseThinkingEnabled(thinkingState.enabled);
|
|
3923
4014
|
const thinkingDisplay = parseThinkingDisplay(thinkingState.display);
|
|
3924
4015
|
let reassertDisplay = false;
|
|
3925
|
-
|
|
4016
|
+
// A backend's thinking is fixed at spawn (above).
|
|
4017
|
+
if (!backend && thinkingEnabled !== proc.thinkingEnabled) {
|
|
3926
4018
|
if (thinkingEnabled === true) {
|
|
3927
4019
|
// Enabling applies live. The display MUST be re-sent afterwards, in
|
|
3928
4020
|
// this order: a session spawned with thinking off carries no display
|
|
@@ -4175,14 +4267,20 @@ export function createClaudeSessionRunner({
|
|
|
4175
4267
|
// seconds (transcript load + first request); say so instead of
|
|
4176
4268
|
// showing bare dots. Posted once here — not in spawnProcess — so a
|
|
4177
4269
|
// push-race respawn cannot duplicate the line.
|
|
4270
|
+
// In a DeepSeek chat the CLI is Claude Code talking to DeepSeek; the
|
|
4271
|
+
// line names the backend so the wait is not read as a Claude turn.
|
|
4272
|
+
const startingWhat = backend ? `Claude Code for ${backend.label || 'DeepSeek'}` : 'the Claude CLI';
|
|
4178
4273
|
publisher.postActivity(message, (respawnResume || message.claudeNativeSessionId || claudeNativeSessionId)
|
|
4179
|
-
?
|
|
4180
|
-
:
|
|
4274
|
+
? `Starting ${startingWhat} (cold start — resuming the session transcript)…`
|
|
4275
|
+
: `Starting ${startingWhat} (cold start)…`);
|
|
4181
4276
|
}
|
|
4182
4277
|
// Released for the window, then joined a process another delivery
|
|
4183
4278
|
// spawned in the meantime.
|
|
4184
4279
|
noteWindowRespawn();
|
|
4185
|
-
|
|
4280
|
+
// A model that takes no image input gets every image by its path.
|
|
4281
|
+
const content = buildClaudeUserContent(message, {
|
|
4282
|
+
embedImages: backend ? backend.acceptsImages(resolvePerTurnModel(message)) : true,
|
|
4283
|
+
});
|
|
4186
4284
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
4187
4285
|
const procRef = proc;
|
|
4188
4286
|
const ctx = createDeliveredContext(message);
|
|
@@ -4209,9 +4307,9 @@ export function createClaudeSessionRunner({
|
|
|
4209
4307
|
// noted steer no longer starts with "/" and is pushed as noted. The
|
|
4210
4308
|
// replay signatures strip the guard too, so expectedText stays the
|
|
4211
4309
|
// user's own text here as well.
|
|
4212
|
-
// Nor into
|
|
4213
|
-
// user,
|
|
4214
|
-
const pushedContent = withSlashCommandGuardContent(steered && procRef.activeCtx?.kind !== 'continuation' && !
|
|
4310
|
+
// Nor into a message the relay wrote (an event, a scheduled prompt):
|
|
4311
|
+
// the note speaks as the user, that message must not (shared/relay-events.mjs).
|
|
4312
|
+
const pushedContent = withSlashCommandGuardContent(steered && procRef.activeCtx?.kind !== 'continuation' && !isRelayAutomatedDelivery(message)
|
|
4215
4313
|
? withSteerNoteContent(content)
|
|
4216
4314
|
: content);
|
|
4217
4315
|
procRef.pendingDelivered.push({ ctx, expectedText: contentText(content), pushedAt: Date.now(), steered });
|