@oar-sh/oar 0.9.20 → 0.9.22
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 +325 -0
- package/README.md +13 -13
- package/bin/oar.js +2 -2
- package/package.json +1 -1
- package/server/claude-cloud-worker/claude-cloud-session-process.mjs +231 -1
- package/server/claude-cloud-worker/claude-cloud-session-worker.mjs +31 -2
- package/server/claude-cloud-worker/claude-cloud-worker-steering.mjs +34 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +149 -4
- package/server/claude-worker/claude-session-process.mjs +16 -4
- package/server/claude-worker/claude-session-worker-main.mjs +18 -2
- package/server/copilot-worker/copilot-sdk-session-process.mjs +75 -1
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +18 -2
- package/server/cursor-worker/cursor-session-worker.mjs +21 -2
- package/server/cursor-worker/cursor-worker-steering.mjs +20 -0
- package/server/db-schema.mjs +19 -0
- package/server/grok-worker/grok-background-tasks.mjs +10 -0
- package/server/grok-worker/grok-session-worker.mjs +22 -2
- package/server/grok-worker/grok-turn-runner.mjs +4 -0
- package/server/grok-worker/grok-worker-steering.mjs +23 -0
- package/server/migrations/0037-relay-asks-queue-id.mjs +66 -0
- package/server/migrations/0038-turn-seq.mjs +92 -0
- package/server/public/app/about-ui.js +1 -1
- package/server/public/app/about-view.mjs +19 -0
- package/server/public/app/action-confirmations.js +1 -1
- package/server/public/app/activity-replay-state.mjs +160 -42
- package/server/public/app/api-client.js +11 -2
- package/server/public/app/ask-user-view.js +24 -9
- package/server/public/app/attachments-view.js +1 -1
- package/server/public/app/back-guard.mjs +31 -5
- package/server/public/app/bootstrap.js +618 -72
- package/server/public/app/chat-actions-menu-state.mjs +141 -4
- package/server/public/app/chat-header-meta.mjs +27 -0
- package/server/public/app/claude-billing-toggle-ui.js +2 -1
- package/server/public/app/claude-cloud-conversation-ui.js +2 -2
- package/server/public/app/composer-settings-label.mjs +90 -0
- package/server/public/app/composer-settings-ui.js +442 -0
- package/server/public/app/context-menu.mjs +36 -5
- package/server/public/app/context-usage-view.mjs +68 -0
- package/server/public/app/conversation-deep-link.mjs +15 -11
- package/server/public/app/conversation-group-drag.mjs +12 -2
- package/server/public/app/conversation-group-order.mjs +9 -2
- package/server/public/app/conversation-group-state.mjs +47 -10
- package/server/public/app/conversation-origin-ui.js +17 -1
- package/server/public/app/conversation-view.js +309 -49
- package/server/public/app/cwd-menu-placement.mjs +18 -6
- package/server/public/app/cwd-picker.js +36 -6
- package/server/public/app/emoji-view.js +1 -1
- package/server/public/app/font-scaling.js +26 -0
- package/server/public/app/header-priority-actions.mjs +238 -0
- package/server/public/app/held-details.mjs +202 -0
- package/server/public/app/hidden-messages-view.mjs +4 -0
- package/server/public/app/inbox-ui.js +88 -81
- package/server/public/app/inbox-view.mjs +35 -19
- package/server/public/app/journal-view.js +328 -58
- package/server/public/app/live-activity-cap.mjs +103 -0
- package/server/public/app/phone-landscape-ui.js +225 -0
- package/server/public/app/phone-landscape.mjs +99 -0
- package/server/public/app/phone-typing-state.mjs +104 -0
- package/server/public/app/pinned-messages-view.mjs +5 -0
- package/server/public/app/push-settings.js +2 -2
- package/server/public/app/pwa-install.js +24 -3
- package/server/public/app/questions-panel-ui.js +634 -0
- package/server/public/app/questions-stub-view.mjs +40 -0
- package/server/public/app/questions-view.mjs +89 -0
- package/server/public/app/relay-board-view.js +12 -2
- package/server/public/app/relay-event-subscriptions-ui.js +71 -22
- package/server/public/app/relay-event-view.mjs +38 -0
- package/server/public/app/relay-restart-ui.js +2 -2
- package/server/public/app/relay-schedule-view.mjs +32 -6
- package/server/public/app/relay-schedules-ui.js +69 -20
- package/server/public/app/relay-thoughts.mjs +15 -1
- package/server/public/app/remote-relay-origin-view.mjs +41 -23
- package/server/public/app/session-history-ui.js +251 -0
- package/server/public/app/session-history.mjs +599 -0
- package/server/public/app/settings-modal.js +67 -6
- package/server/public/app/settings-tabs.js +12 -0
- package/server/public/app/share-state.js +1 -1
- package/server/public/app/shared-conversations-settings.js +2 -2
- package/server/public/app/socket-handlers.js +47 -13
- package/server/public/app/status-view.mjs +18 -2
- package/server/public/app/store.js +87 -16
- package/server/public/app/stream-view-preference.mjs +60 -0
- package/server/public/app/subagent-fold.mjs +6 -3
- package/server/public/app/subagent-timeline-view.mjs +12 -5
- package/server/public/app/subagent-viewer.mjs +35 -8
- package/server/public/app/tab-strip-fade.mjs +101 -0
- package/server/public/app/tap-actions.js +66 -0
- package/server/public/app/thoughts-preference.mjs +79 -0
- package/server/public/app/turn-stream-view.mjs +124 -0
- package/server/public/app/usage-limit-ui.js +63 -2
- package/server/public/app/viewer-history.mjs +13 -2
- package/server/public/index.html +2603 -1767
- package/server/relay-tools.md +1 -0
- package/server/repositories/message-repository.mjs +33 -0
- package/server/repositories/question-repository.mjs +51 -7
- package/server/repositories/relay-turn-rows.mjs +56 -0
- package/server/repositories/session-repository.mjs +19 -0
- package/server/routes/ask-user-routes.mjs +34 -5
- package/server/routes/claude-cloud-routes.mjs +19 -0
- package/server/routes/messages-routes.mjs +92 -15
- package/server/routes/relay-ask-routes.mjs +15 -5
- package/server/routes/sessions-routes.mjs +117 -12
- package/server/server-runtime.mjs +367 -170
- package/server/services/about-service.mjs +22 -53
- package/server/services/claude-billing-swap-service.mjs +7 -20
- package/server/services/claude-cloud-session-service.mjs +47 -5
- package/server/services/cloudflared-tunnel-service.mjs +7 -0
- package/server/services/idle-timers.mjs +184 -0
- package/server/services/queue-wake-hook.mjs +145 -0
- package/server/services/queue-waker.mjs +127 -0
- package/server/services/relay-ask-service.mjs +64 -5
- package/server/services/relay-event-service.mjs +4 -1
- package/server/services/remote-relay-dispatcher.mjs +84 -18
- package/server/services/session-activity-tracker.mjs +1 -1
- package/server/services/session-history-refresh-service.mjs +4 -0
- package/server/services/session-launch-mutex-service.mjs +23 -0
- package/server/services/session-worker-registry-service.mjs +36 -0
- package/server/services/session-worker-supervisor-service.mjs +36 -0
- package/server/services/session-worker-websocket-service.mjs +183 -31
- package/server/services/ssh-tunnel-manager-service.mjs +7 -0
- package/server/services/worker-recycle-service.mjs +772 -0
- package/server/services/worker-replacement.mjs +197 -0
- package/shared/ask-user-bridge.mjs +9 -6
- package/shared/build-id.mjs +128 -0
- package/shared/relay-asks.mjs +10 -0
- package/shared/relay-events.mjs +1 -0
- package/shared/worker-runtime/heartbeat.mjs +31 -0
- package/shared/worker-runtime/simple-steering-state.mjs +38 -0
- package/shared/worker-runtime/worker-keepalive.mjs +108 -0
- package/shared/worker-runtime/worker-websocket-link.mjs +44 -10
- package/shared/worker-runtime/working-directory.mjs +1 -1
|
@@ -43,6 +43,7 @@ import { readStoredClaudeContextUsage, resolveAutoCompactWindowStatus } from '..
|
|
|
43
43
|
import { buildContextUsageView } from '../services/context-usage-view.mjs';
|
|
44
44
|
import { cleanupGeneratedImagesForConversation as cleanupGeneratedImagesForConversationDefault } from '../services/generated-image-cleanup-service.mjs';
|
|
45
45
|
import { stopSessionWorkerProcesses } from '../services/session-worker-stop-service.mjs';
|
|
46
|
+
import { isWorkerOnOtherBuild } from '../services/worker-recycle-service.mjs';
|
|
46
47
|
import { createClaudeAuthService } from '../services/claude-auth-service.mjs';
|
|
47
48
|
import { createCliInstallService } from '../services/cli-install-service.mjs';
|
|
48
49
|
import { createGrokAuthService } from '../services/grok-auth-service.mjs';
|
|
@@ -69,7 +70,7 @@ import { redactPreviewTokens } from '../services/preview-registry-service.mjs';
|
|
|
69
70
|
import { createSharePathRedactor } from '../services/share-path-redaction.mjs';
|
|
70
71
|
import { createSubagentTimelineService, TIMELINE_READ_DEFAULT_LIMIT } from '../services/subagent-timeline-service.mjs';
|
|
71
72
|
import { sanitizeSubagentRunId } from '../../shared/subagent-run-id.mjs';
|
|
72
|
-
import {
|
|
73
|
+
import { sessionLaunchMutex } from '../services/session-launch-mutex-service.mjs';
|
|
73
74
|
import { createFixedWindowRateLimiter } from '../services/rate-limit-service.mjs';
|
|
74
75
|
import { requestHostOf } from '../services/request-boundary.mjs';
|
|
75
76
|
import { parseRemoteRelayOriginJson } from '../repositories/remote-relay-repository.mjs';
|
|
@@ -77,6 +78,7 @@ import { createShareRepository } from '../repositories/share-repository.mjs';
|
|
|
77
78
|
import { createShareAccessLog, readShareRequestViewer } from '../services/share-access-log-service.mjs';
|
|
78
79
|
import { GROUP_NAME_MAX, resolveGroupDisplayName } from '../repositories/conversation-group-repository.mjs';
|
|
79
80
|
import { conversationGroupPosition, conversationGroupRole, groupActivityTimes, pageConversationRows } from '../public/app/conversation-group-order.mjs';
|
|
81
|
+
import { detailsVersion, parseHeldDetailsParam, thoughtFingerprint } from '../public/app/held-details.mjs';
|
|
80
82
|
import { admitRemoteRelayRequest, publicRemoteRelayOrigin, readRemoteRelayRequest } from '../services/remote-relay-inbound.mjs';
|
|
81
83
|
import {
|
|
82
84
|
RELAUNCH_COALESCE_WINDOW_MS,
|
|
@@ -1452,11 +1454,27 @@ export function buildSessionWorkerStatusPayload({
|
|
|
1452
1454
|
// What the last read of the process list did ({ at, durationMs, processes,
|
|
1453
1455
|
// error }); a slow box shows here before it shows as 502s.
|
|
1454
1456
|
processList = null,
|
|
1457
|
+
// The relay's own build id (runtimeState.buildId): each worker's is shown
|
|
1458
|
+
// next to it, so a worker still on the code before a deploy stands out.
|
|
1459
|
+
relayBuildId = null,
|
|
1460
|
+
// The worker recycle's status() (stale count, recycles in flight and done,
|
|
1461
|
+
// who waits on what), or null while the recycle is not wired.
|
|
1462
|
+
recycleStatus = null,
|
|
1455
1463
|
} = {}) {
|
|
1456
1464
|
const snapshot = supervisorSnapshot && typeof supervisorSnapshot === 'object'
|
|
1457
1465
|
? supervisorSnapshot
|
|
1458
1466
|
: {};
|
|
1459
|
-
const
|
|
1467
|
+
const relayBuild = typeof relayBuildId === 'string' && relayBuildId.trim() ? relayBuildId.trim() : null;
|
|
1468
|
+
const workers = (Array.isArray(snapshot.workers) ? snapshot.workers : []).map((worker) => (
|
|
1469
|
+
worker && typeof worker === 'object'
|
|
1470
|
+
? {
|
|
1471
|
+
...worker,
|
|
1472
|
+
buildId: typeof worker.buildId === 'string' && worker.buildId ? worker.buildId : null,
|
|
1473
|
+
startedAt: worker.startedAt || null,
|
|
1474
|
+
stale: isWorkerOnOtherBuild(worker, relayBuild),
|
|
1475
|
+
}
|
|
1476
|
+
: worker
|
|
1477
|
+
));
|
|
1460
1478
|
const onlineWorkerStatuses = new Set(SESSION_WORKER_ACTIVE_STATUSES);
|
|
1461
1479
|
const onlineCount = workers.reduce((count, worker) => {
|
|
1462
1480
|
const status = normalizeWorkerStatusText(worker?.status, 'new');
|
|
@@ -1561,6 +1579,8 @@ export function buildSessionWorkerStatusPayload({
|
|
|
1561
1579
|
onlineUnassignedProcessCount,
|
|
1562
1580
|
counts: snapshot.counts && typeof snapshot.counts === 'object' ? snapshot.counts : {},
|
|
1563
1581
|
workers,
|
|
1582
|
+
buildId: relayBuild,
|
|
1583
|
+
recycle: recycleStatus && typeof recycleStatus === 'object' ? recycleStatus : null,
|
|
1564
1584
|
pendingStarts: toSafeNonNegativeInt(snapshot.pendingStarts, 0),
|
|
1565
1585
|
lifecycle: Array.isArray(snapshot.lifecycle) ? snapshot.lifecycle : [],
|
|
1566
1586
|
integrity,
|
|
@@ -2608,9 +2628,10 @@ export function registerSessionsRoutes(app, deps) {
|
|
|
2608
2628
|
bucketTtlMs: 60_000,
|
|
2609
2629
|
maxBuckets: 4_096,
|
|
2610
2630
|
});
|
|
2611
|
-
//
|
|
2612
|
-
//
|
|
2613
|
-
|
|
2631
|
+
// Everything that reaches the session-worker launch path is serialized by
|
|
2632
|
+
// the relay-wide sessionLaunchMutex (services/session-launch-mutex-service.mjs),
|
|
2633
|
+
// keyed by sdkSessionId, which the worker recycle takes too; plus a
|
|
2634
|
+
// coalescer so a duplicated request replays one result.
|
|
2614
2635
|
const relaunchCoalescer = createRelaunchCoalescer({ windowMs: RELAUNCH_COALESCE_WINDOW_MS });
|
|
2615
2636
|
|
|
2616
2637
|
function getDeepSeekProviderSettings() {
|
|
@@ -4686,6 +4707,59 @@ export function registerSessionsRoutes(app, deps) {
|
|
|
4686
4707
|
});
|
|
4687
4708
|
|
|
4688
4709
|
// GET /api/conversation/:id — get paginated conversation history
|
|
4710
|
+
// The details version of each finished reply (held-details.mjs), one query
|
|
4711
|
+
// per table for the whole window (the ids go in as one JSON parameter).
|
|
4712
|
+
// Tool rows are only inserted: their count and highest row id. Thought rows
|
|
4713
|
+
// are updated in place as they stream and when done: their rows are read
|
|
4714
|
+
// (id, done, text) and fingerprinted, so any change shows.
|
|
4715
|
+
let detailsVersionStatements = null;
|
|
4716
|
+
function relayDetailsVersions(messageIds) {
|
|
4717
|
+
const ids = [...new Set(messageIds.filter(Boolean))];
|
|
4718
|
+
const versions = new Map();
|
|
4719
|
+
if (!ids.length || typeof db?.prepare !== 'function') return versions;
|
|
4720
|
+
try {
|
|
4721
|
+
detailsVersionStatements ||= {
|
|
4722
|
+
activity: db.prepare(`
|
|
4723
|
+
SELECT response_message_id AS id, COUNT(*) AS n, MAX(rowid) AS max_rowid
|
|
4724
|
+
FROM relay_activity
|
|
4725
|
+
WHERE response_message_id IN (SELECT value FROM json_each(?))
|
|
4726
|
+
GROUP BY response_message_id
|
|
4727
|
+
`),
|
|
4728
|
+
thought: db.prepare(`
|
|
4729
|
+
SELECT response_message_id AS response_id, rowid AS row_id, done, text
|
|
4730
|
+
FROM relay_thought
|
|
4731
|
+
WHERE response_message_id IN (SELECT value FROM json_each(?))
|
|
4732
|
+
`),
|
|
4733
|
+
};
|
|
4734
|
+
const idsJson = JSON.stringify(ids);
|
|
4735
|
+
const activity = new Map();
|
|
4736
|
+
for (const row of detailsVersionStatements.activity.all(idsJson) || []) {
|
|
4737
|
+
activity.set(String(row?.id || ''), { n: Number(row.n) || 0, max: Number(row.max_rowid) || 0 });
|
|
4738
|
+
}
|
|
4739
|
+
const thoughtRows = new Map();
|
|
4740
|
+
for (const row of detailsVersionStatements.thought.all(idsJson) || []) {
|
|
4741
|
+
const id = String(row?.response_id || '');
|
|
4742
|
+
if (!thoughtRows.has(id)) thoughtRows.set(id, []);
|
|
4743
|
+
thoughtRows.get(id).push({ rowid: row.row_id, done: row.done, text: row.text });
|
|
4744
|
+
}
|
|
4745
|
+
for (const id of ids) {
|
|
4746
|
+
const tools = activity.get(id) || { n: 0, max: 0 };
|
|
4747
|
+
const thoughts = thoughtRows.get(id) || [];
|
|
4748
|
+
versions.set(id, detailsVersion({
|
|
4749
|
+
activityCount: tools.n,
|
|
4750
|
+
activityMaxRowId: tools.max,
|
|
4751
|
+
thoughtCount: thoughts.length,
|
|
4752
|
+
thoughtMaxRowId: thoughts.reduce((max, row) => Math.max(max, Number(row.rowid) || 0), 0),
|
|
4753
|
+
thoughtHash: thoughtFingerprint(thoughts),
|
|
4754
|
+
}));
|
|
4755
|
+
}
|
|
4756
|
+
} catch {
|
|
4757
|
+
// No versions: nothing is held, everything goes out in full.
|
|
4758
|
+
return new Map();
|
|
4759
|
+
}
|
|
4760
|
+
return versions;
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4689
4763
|
app.get('/api/conversation/:id', auth, (req, res) => {
|
|
4690
4764
|
const requestedId = String(req.params.id || '').trim();
|
|
4691
4765
|
const limit = normalizeConversationHistoryLimit(req.query.limit, 20);
|
|
@@ -4709,7 +4783,9 @@ export function registerSessionsRoutes(app, deps) {
|
|
|
4709
4783
|
supportedRelayModes: SUPPORTED_RELAY_MODES,
|
|
4710
4784
|
defaultRelayMode: DEFAULT_RELAY_MODE,
|
|
4711
4785
|
});
|
|
4712
|
-
|
|
4786
|
+
// The running turn's tool lines: as many per thread as this page shows
|
|
4787
|
+
// (its "Tool lines shown" setting); none given, the relay's default.
|
|
4788
|
+
const inFlight = inFlightStateForConversation(resolvedConversationId, { activityCap: req.query.activityCap });
|
|
4713
4789
|
const sdkSessionId = String(conv.sdk_session_id || resolvedConversationId || '').trim();
|
|
4714
4790
|
const sessionUsageSummary = resolveSessionUsageSummaryForSdkSession({
|
|
4715
4791
|
sdkSessionId,
|
|
@@ -4748,14 +4824,23 @@ export function registerSessionsRoutes(app, deps) {
|
|
|
4748
4824
|
.map((row) => [String(row?.response_message_id || '').trim(), String(row?.id || '').trim()])
|
|
4749
4825
|
.filter(([responseMessageId, sourceMessageId]) => !!responseMessageId && !!sourceMessageId),
|
|
4750
4826
|
);
|
|
4827
|
+
// The live poll names the finished replies whose tool lines and thoughts
|
|
4828
|
+
// it holds (`heldDetails`, held-details.mjs); those whose details have
|
|
4829
|
+
// not changed since (the same `detailsVersion`) go out without them.
|
|
4830
|
+
const assistantIds = dbMessages.filter((m) => m.role === 'assistant').map((m) => String(m.id));
|
|
4831
|
+
const detailsVersions = relayDetailsVersions(assistantIds);
|
|
4832
|
+
const heldParam = parseHeldDetailsParam(req.query.heldDetails);
|
|
4833
|
+
const heldNow = new Set(heldParam
|
|
4834
|
+
? assistantIds.filter((id) => heldParam.get(id) === detailsVersions.get(id))
|
|
4835
|
+
: []);
|
|
4751
4836
|
const relayActivitiesByMessageId = new Map(
|
|
4752
4837
|
dbMessages
|
|
4753
|
-
.filter((m) => m.role === 'assistant')
|
|
4838
|
+
.filter((m) => m.role === 'assistant' && !heldNow.has(String(m.id)))
|
|
4754
4839
|
.map((m) => [m.id, relayActivityForResponse(m.id)]),
|
|
4755
4840
|
);
|
|
4756
4841
|
const relayThoughtsByMessageId = new Map(
|
|
4757
4842
|
dbMessages
|
|
4758
|
-
.filter((m) => m.role === 'assistant')
|
|
4843
|
+
.filter((m) => m.role === 'assistant' && !heldNow.has(String(m.id)))
|
|
4759
4844
|
.map((m) => [m.id, relayThoughtsForResponse ? relayThoughtsForResponse(m.id) : []]),
|
|
4760
4845
|
);
|
|
4761
4846
|
const subagentRunsByMessageId = new Map(
|
|
@@ -4796,8 +4881,14 @@ export function registerSessionsRoutes(app, deps) {
|
|
|
4796
4881
|
});
|
|
4797
4882
|
messages = messages.map((message) => {
|
|
4798
4883
|
if (message.role !== 'assistant') return message;
|
|
4799
|
-
const
|
|
4800
|
-
|
|
4884
|
+
const id = String(message.id || '').trim();
|
|
4885
|
+
const sourceMessageId = responseMessageToSourceId.get(id) || message.sourceMessageId || undefined;
|
|
4886
|
+
// Held: the page fills these from what it has. Otherwise the version
|
|
4887
|
+
// the page keeps with them.
|
|
4888
|
+
const details = heldNow.has(id)
|
|
4889
|
+
? { activities: [], thoughts: [], detailsHeld: true }
|
|
4890
|
+
: (detailsVersions.has(id) ? { detailsVersion: detailsVersions.get(id) } : {});
|
|
4891
|
+
return { ...message, ...(sourceMessageId ? { sourceMessageId } : {}), ...details };
|
|
4801
4892
|
});
|
|
4802
4893
|
const history = sqlPage
|
|
4803
4894
|
? { messages, pageInfo: sqlPage.pageInfo }
|
|
@@ -6099,7 +6190,7 @@ export function registerSessionsRoutes(app, deps) {
|
|
|
6099
6190
|
// Kill session stops the session's worker, and the delete counts no
|
|
6100
6191
|
// tasks for a conversation whose worker is gone.
|
|
6101
6192
|
message: activity.unstoppableTaskCount > 0
|
|
6102
|
-
? 'Background tasks are still running in this conversation, and some have no Stop button. Use Kill session in its
|
|
6193
|
+
? 'Background tasks are still running in this conversation, and some have no Stop button. Use Kill session in its ︙ menu, then delete it.'
|
|
6103
6194
|
: 'Background tasks are still running in this conversation. Stop them in the task panel, then delete it.',
|
|
6104
6195
|
},
|
|
6105
6196
|
};
|
|
@@ -6258,7 +6349,7 @@ export function registerSessionsRoutes(app, deps) {
|
|
|
6258
6349
|
error: 'conversation-active',
|
|
6259
6350
|
reason: 'background-tasks',
|
|
6260
6351
|
message: activity.unstoppableTaskCount > 0
|
|
6261
|
-
? 'Background tasks are still running in this conversation, and some have no Stop button. Use Kill session in its
|
|
6352
|
+
? 'Background tasks are still running in this conversation, and some have no Stop button. Use Kill session in its ︙ menu, then archive it.'
|
|
6262
6353
|
: 'Background tasks are still running in this conversation. Stop them in the task panel, then archive it.',
|
|
6263
6354
|
},
|
|
6264
6355
|
};
|
|
@@ -7129,6 +7220,18 @@ export function registerSessionsRoutes(app, deps) {
|
|
|
7129
7220
|
}
|
|
7130
7221
|
});
|
|
7131
7222
|
|
|
7223
|
+
// The worker recycle's status, read at request time: the recycle is set on
|
|
7224
|
+
// runtimeState after the routes are registered, and a status that throws
|
|
7225
|
+
// must not take /api/status down with it.
|
|
7226
|
+
function readWorkerRecycleStatus() {
|
|
7227
|
+
if (typeof runtimeState.workerRecycle !== 'function') return null;
|
|
7228
|
+
try {
|
|
7229
|
+
return runtimeState.workerRecycle() || null;
|
|
7230
|
+
} catch {
|
|
7231
|
+
return null;
|
|
7232
|
+
}
|
|
7233
|
+
}
|
|
7234
|
+
|
|
7132
7235
|
// GET /api/status — overall status
|
|
7133
7236
|
app.get('/api/status', auth, (req, res) => {
|
|
7134
7237
|
ensureSessionId(req, res);
|
|
@@ -7146,6 +7249,8 @@ export function registerSessionsRoutes(app, deps) {
|
|
|
7146
7249
|
supervisorSnapshot: sessionWorkerSupervisor?.snapshot?.({ pendingQuestionSessionIds }) || null,
|
|
7147
7250
|
queueRows: listSessionWorkerQueueRows.all(...SESSION_WORKER_STATUS_QUEUE_STATES),
|
|
7148
7251
|
processList: sessionWorkerProcessInspector?.getLastRead?.() || null,
|
|
7252
|
+
relayBuildId: runtimeState.buildId ?? null,
|
|
7253
|
+
recycleStatus: readWorkerRecycleStatus(),
|
|
7149
7254
|
});
|
|
7150
7255
|
const activeRuntimeSessionCount = featureFlags?.SESSION_WORKER_ROUTING_ENABLED === true
|
|
7151
7256
|
? Number(sessionWorkerStatus?.onlineBoundProcessCount || 0)
|