@oar-sh/oar 0.9.9 → 0.9.11
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 +561 -1
- package/README.md +162 -909
- package/bin/bat/oar.bat +46 -46
- package/package.json +14 -14
- package/server/README.md +369 -13
- package/server/claude-worker/claude-remote-relay-tool.mjs +10 -0
- package/server/claude-worker/claude-session-process.mjs +121 -9
- package/server/claude-worker/claude-session-worker.mjs +9 -1
- package/server/claude-worker/claude-turn-publisher.mjs +3 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +31 -4
- package/server/claude-worker/subagent-timeline-recorder.mjs +572 -0
- package/server/copilot-worker/copilot-sdk-session-process.mjs +79 -12
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +6 -0
- package/server/cursor-worker/cursor-sdk-adapter.mjs +56 -2
- package/server/db-schema.mjs +95 -2
- package/server/grok-worker/acp-client.mjs +2 -1
- package/server/grok-worker/acp-host-services.mjs +4 -3
- package/server/migrations/0009-subagent-timeline.mjs +135 -0
- package/server/migrations/0010-subagent-timeline-pruning.mjs +89 -0
- package/server/migrations/0011-relay-event-subscriptions.mjs +125 -0
- package/server/migrations/0012-cross-relay-event-subscriptions.mjs +116 -0
- package/server/migrations/0013-previews.mjs +130 -0
- package/server/migrations/0014-conversation-groups.mjs +89 -0
- package/server/migrations/0015-conversation-group-position.mjs +76 -0
- package/server/migrations/0016-share-access-log.mjs +110 -0
- package/server/public/app/about-ui.js +73 -0
- package/server/public/app/about-view.mjs +234 -0
- package/server/public/app/action-confirmations.js +31 -14
- package/server/public/app/agent-exchange-fold-view.mjs +187 -0
- package/server/public/app/api-client.js +312 -16
- package/server/public/app/archive-confirm.mjs +28 -0
- package/server/public/app/ask-user-view.js +17 -12
- package/server/public/app/attachments-view.js +516 -6
- package/server/public/app/background-tasks-view.mjs +18 -26
- package/server/public/app/bootstrap.js +336 -59
- package/server/public/app/chat-actions-menu-state.mjs +126 -0
- package/server/public/app/claude-cloud-conversation-ui.js +10 -22
- package/server/public/app/composer-control-state.mjs +16 -0
- package/server/public/app/composer-keyboard.mjs +54 -0
- package/server/public/app/context-menu.mjs +90 -6
- package/server/public/app/conversation-group-drag.mjs +191 -0
- package/server/public/app/conversation-group-order.mjs +265 -0
- package/server/public/app/conversation-group-state.mjs +100 -0
- package/server/public/app/conversation-list-drag.mjs +449 -0
- package/server/public/app/conversation-turn-state.mjs +81 -0
- package/server/public/app/conversation-view.js +436 -117
- package/server/public/app/cwd-picker.js +1 -1
- package/server/public/app/explorer-edit-state.mjs +68 -0
- package/server/public/app/hidden-messages-view.mjs +312 -0
- package/server/public/app/host-suspend-ui.js +13 -4
- package/server/public/app/journal-view.js +930 -45
- package/server/public/app/no-conversation-state.mjs +58 -0
- package/server/public/app/preview-cards.mjs +117 -31
- package/server/public/app/preview-command.mjs +10 -1
- package/server/public/app/provider-readiness.mjs +120 -0
- package/server/public/app/relay-board-view.js +19 -3
- package/server/public/app/relay-event-subscriptions-ui.js +184 -0
- package/server/public/app/relay-event-view.mjs +234 -0
- package/server/public/app/relay-restart-error.mjs +16 -0
- package/server/public/app/settings-modal.js +93 -9
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/share-format.mjs +49 -0
- package/server/public/app/share-preview-marks.mjs +167 -0
- package/server/public/app/share-state.js +109 -0
- package/server/public/app/shared-conversations-settings.js +480 -0
- package/server/public/app/socket-handlers.js +137 -40
- package/server/public/app/store.js +115 -32
- package/server/public/app/subagent-row.mjs +138 -0
- package/server/public/app/subagent-timeline-view.mjs +186 -0
- package/server/public/app/subagent-viewer-watch.mjs +50 -0
- package/server/public/app/subagent-viewer.mjs +558 -0
- package/server/public/app/task-row-format.mjs +29 -0
- package/server/public/app/transcript-jump-bottom-view.mjs +464 -0
- package/server/public/app/transcript-jump-bottom.mjs +210 -0
- package/server/public/app/transcript-reload-freshness.mjs +25 -0
- package/server/public/app/transcript-viewport-anchor.mjs +140 -0
- package/server/public/app/update-ui.js +1 -1
- package/server/public/app/viewer-history.mjs +5 -3
- package/server/public/index.html +774 -12
- package/server/relay-tools.md +2 -1
- package/server/relay.mjs +2 -1
- package/server/repositories/conversation-group-repository.mjs +444 -0
- package/server/repositories/message-repository.mjs +30 -3
- package/server/repositories/question-repository.mjs +9 -3
- package/server/repositories/remote-relay-repository.mjs +6 -2
- package/server/repositories/session-repository.mjs +12 -37
- package/server/repositories/share-repository.mjs +322 -0
- package/server/repositories/share-visibility-statements.mjs +10 -0
- package/server/repositories/subagent-timeline-statements.mjs +170 -0
- package/server/routes/about-routes.mjs +15 -0
- package/server/routes/ask-user-routes.mjs +3 -0
- package/server/routes/cache-routes.mjs +4 -0
- package/server/routes/conversation-group-routes.mjs +171 -0
- package/server/routes/messages-routes.mjs +698 -114
- package/server/routes/preview-routes.mjs +9 -1
- package/server/routes/relay-event-routes.mjs +157 -0
- package/server/routes/remote-relay-routes.mjs +15 -0
- package/server/routes/sessions-routes.mjs +843 -193
- package/server/server-runtime.mjs +559 -160
- package/server/services/about-service.mjs +461 -0
- package/server/services/background-task-store.mjs +128 -0
- package/server/services/background-work-view.mjs +228 -0
- package/server/services/cache-rebuild-service.mjs +30 -0
- package/server/services/delete-archive-service.mjs +4 -1
- package/server/services/detached-worker-watch-service.mjs +150 -0
- package/server/services/explorer-entry-name.mjs +26 -0
- package/server/services/explorer-mutations.mjs +122 -0
- package/server/services/hidden-message-list.mjs +21 -0
- package/server/services/host-suspend-command.mjs +2 -1
- package/server/services/message-pin-list.mjs +1 -1
- package/server/services/preview-health-probe.mjs +8 -1
- package/server/services/preview-proxy-server.mjs +49 -27
- package/server/services/preview-registry-service.mjs +676 -26
- package/server/services/preview-static-handler.mjs +40 -7
- package/server/services/preview-store.mjs +274 -0
- package/server/services/push-dispatch-service.mjs +18 -0
- package/server/services/rate-limit-service.mjs +12 -1
- package/server/services/relay-auth.mjs +183 -0
- package/server/services/relay-cli-launcher-service.mjs +3 -1
- package/server/services/relay-event-link-service.mjs +289 -0
- package/server/services/relay-event-service.mjs +1952 -0
- package/server/services/relay-shutdown-gate.mjs +31 -0
- package/server/services/remote-relay-caller-context.mjs +52 -7
- package/server/services/remote-relay-dispatcher.mjs +551 -7
- package/server/services/remote-relay-registry-service.mjs +11 -1
- package/server/services/request-boundary.mjs +183 -0
- package/server/services/session-activity-tracker.mjs +695 -0
- package/server/services/session-history-refresh-service.mjs +7 -0
- package/server/services/session-worker-launch-service.mjs +190 -6
- package/server/services/session-worker-process-service.mjs +10 -6
- package/server/services/share-access-log-service.mjs +123 -0
- package/server/services/share-path-redaction.mjs +424 -0
- package/server/services/stale-attempt-log-limiter.mjs +96 -0
- package/server/services/stale-recovery-clock-guard.mjs +57 -0
- package/server/services/stale-subagent-run-sweep.mjs +109 -0
- package/server/services/status-event-service.mjs +7 -3
- package/server/services/subagent-record-retention.mjs +164 -0
- package/server/services/subagent-timeline-service.mjs +328 -0
- package/server/services/subagent-viewer-rooms.mjs +119 -0
- package/server/services/tunnel-worker-path-guard.mjs +24 -41
- package/server/services/upgrade-socket-guard.mjs +16 -0
- package/server/services/windows-autostart-service.mjs +6 -1
- package/server/services/windows-boot-autostart-service.mjs +207 -23
- package/shared/preview-tool-core.mjs +15 -5
- package/shared/relay-events.mjs +371 -0
- package/shared/remote-relay-contract.mjs +160 -6
- package/shared/share-path-privacy.mjs +30 -0
- package/shared/subagent-record-retention.mjs +31 -0
- package/shared/subagent-timeline.mjs +79 -0
- package/shared/windows-system-tools.mjs +106 -0
- package/shared/worker-runtime/background-task-digest.mjs +30 -0
- package/shared/worker-runtime/heartbeat.mjs +24 -0
- package/shared/worker-runtime/kept-posts.mjs +83 -0
- package/shared/worker-runtime/worker-websocket-link.mjs +47 -15
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// A queued relay shutdown or restart waits for the turns that are processing,
|
|
4
|
+
// and only for those. While it waits, no pending row is handed to a worker:
|
|
5
|
+
// a new turn would postpone the restart again, and on a busy relay forever
|
|
6
|
+
// (live: a turn started after the restart was queued, each time). Pending and
|
|
7
|
+
// parked rows stay in the database and run after the restart.
|
|
8
|
+
|
|
9
|
+
export const RELAY_SHUTDOWN_QUEUED_REASON = 'relay_shutdown_queued';
|
|
10
|
+
|
|
11
|
+
/** Whether the runtime holds a shutdown or restart request that has not started yet. */
|
|
12
|
+
export function isRelayShutdownQueued(runtimeState) {
|
|
13
|
+
if (!runtimeState) return false;
|
|
14
|
+
if (runtimeState.relayShutdownQueued === true) return true;
|
|
15
|
+
return String(runtimeState.relayShutdown?.status || '') === 'queued';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The queued shutdown may go ahead: nothing is processing (pending and parked rows wait it out). */
|
|
19
|
+
export function queuedShutdownCanProceed(counts) {
|
|
20
|
+
return Number(counts?.processingCount || 0) <= 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** What a worker asking for work gets while a shutdown is queued: nothing, and why. */
|
|
24
|
+
export function relayShutdownGateReply(runtimeState) {
|
|
25
|
+
return {
|
|
26
|
+
message: null,
|
|
27
|
+
paused: true,
|
|
28
|
+
reason: RELAY_SHUTDOWN_QUEUED_REASON,
|
|
29
|
+
relayShutdown: runtimeState?.relayShutdown || null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -35,6 +35,7 @@ const LOG_PREFIX = '[remote-relays]';
|
|
|
35
35
|
const APPROVAL_HEADER = 'Remote relay';
|
|
36
36
|
// The once-per-conversation card for sessions an agent starts on this relay.
|
|
37
37
|
const LOCAL_SESSIONS_HEADER = 'Agent sessions';
|
|
38
|
+
const WATCH_HEADER = 'Relay events';
|
|
38
39
|
const APPROVAL_ALLOW = 'Allow';
|
|
39
40
|
const APPROVAL_DENY = 'Deny';
|
|
40
41
|
const EXCERPT_MAX = 200;
|
|
@@ -137,7 +138,42 @@ function describeLocalSessionTarget(args) {
|
|
|
137
138
|
* agent wants to start sessions on this relay itself. Allowing it covers the
|
|
138
139
|
* later ones of that conversation, so the card says what they cost.
|
|
139
140
|
*/
|
|
140
|
-
|
|
141
|
+
/**
|
|
142
|
+
* The card for relay event subscriptions (`watch`): the first subscribe of a
|
|
143
|
+
* conversation (`kind: 'subscribe'`, with the sessions or "all"), or a
|
|
144
|
+
* transfer of another conversation's subscriptions (`kind: 'transfer'`).
|
|
145
|
+
* "All sessions" is always spelled out.
|
|
146
|
+
*/
|
|
147
|
+
function formatWatchApprovalPrompt(watch) {
|
|
148
|
+
// A paired relay's sessions: named with it, so the user knows where.
|
|
149
|
+
const relayName = toText(watch.relayName);
|
|
150
|
+
const where = relayName ? `relay "${excerpt(relayName)}"` : 'this relay';
|
|
151
|
+
if (watch.kind === 'transfer') {
|
|
152
|
+
const from = watch.from || {};
|
|
153
|
+
const title = toText(from.title) ? `“${excerpt(from.title)}” ` : '';
|
|
154
|
+
const lines = [`This agent wants to take over ${watch.count} event subscription${watch.count === 1 ? '' : 's'} from session ${title}(${shortId(from.id)}) → this session. The old session stops getting their events.`];
|
|
155
|
+
const relays = (Array.isArray(watch.relays) ? watch.relays : []).map((name) => `"${excerpt(name)}"`);
|
|
156
|
+
if (relays.length) lines.push(`Among them: subscriptions on relay${relays.length === 1 ? '' : 's'} ${relays.join(', ')}.`);
|
|
157
|
+
if (watch.all) lines.push(`Among them: ALL sessions of ${relayName ? where : 'a relay'}, including ones created later.`);
|
|
158
|
+
return lines.join('\n\n');
|
|
159
|
+
}
|
|
160
|
+
const lines = [];
|
|
161
|
+
if (watch.all) {
|
|
162
|
+
lines.push(`This agent wants to watch ALL sessions of ${where}, including ones created later, and be woken by their events.`);
|
|
163
|
+
} else {
|
|
164
|
+
const sessions = (Array.isArray(watch.sessions) ? watch.sessions : []).slice(0, 10)
|
|
165
|
+
.map((session) => (toText(session?.title) ? `“${excerpt(session.title)}” (${shortId(session.id)})` : shortId(session?.id)));
|
|
166
|
+
const more = (watch.sessions?.length || 0) - sessions.length;
|
|
167
|
+
lines.push(`This agent wants to watch ${sessions.join(', ')}${more > 0 ? ` and ${more} more` : ''}${relayName ? ` on ${where}` : ''} and be woken by their events.`);
|
|
168
|
+
}
|
|
169
|
+
const events = (Array.isArray(watch.events) ? watch.events : []).join(', ');
|
|
170
|
+
const extras = [watch.once ? 'once' : '', Number(watch.heartbeatMinutes) > 0 ? `heartbeat every ${watch.heartbeatMinutes} min` : ''].filter(Boolean);
|
|
171
|
+
if (events) lines.push(`Events: ${events}${extras.length ? ` · ${extras.join(' · ')}` : ''}`);
|
|
172
|
+
return lines.join('\n\n');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function formatRemoteRelayApprovalPrompt({ relay, action, args = {}, caller = null, agentPrompts = 0, localSessions = false, watch = null } = {}) {
|
|
176
|
+
if (watch && typeof watch === 'object') return formatWatchApprovalPrompt(watch);
|
|
141
177
|
const local = relay?.local === true;
|
|
142
178
|
const name = toText(relay?.name) || 'a remote relay';
|
|
143
179
|
const where = local ? 'this relay' : `relay "${name}"`;
|
|
@@ -317,23 +353,25 @@ export function createRemoteRelayCallerContext({
|
|
|
317
353
|
* `localSessions` makes it the card for starting sessions on this relay
|
|
318
354
|
* itself, asked once per conversation (the dispatcher remembers the Allow).
|
|
319
355
|
*/
|
|
320
|
-
async function requestApproval({ conversationId, callerContext, relay, action, args = {}, signal, agentPrompts = 0, localSessions = false } = {}) {
|
|
356
|
+
async function requestApproval({ conversationId, callerContext, relay, action, args = {}, signal, agentPrompts = 0, localSessions = false, watch = null } = {}) {
|
|
321
357
|
const rowId = toText(callerContext?.processingRowId);
|
|
322
358
|
const row = rowId ? getQueueRow.get(rowId) : null;
|
|
323
359
|
if (!row || row.status !== 'processing') {
|
|
324
360
|
return {
|
|
325
361
|
approved: false,
|
|
326
362
|
code: REMOTE_RELAY_ERROR_CODES.noTurn,
|
|
327
|
-
error:
|
|
328
|
-
? '
|
|
329
|
-
:
|
|
363
|
+
error: watch
|
|
364
|
+
? 'Watching sessions needs the user\'s approval, but the conversation has no running turn to ask on.'
|
|
365
|
+
: (localSessions
|
|
366
|
+
? 'Starting sessions on this relay needs the user\'s approval once per conversation, but the conversation has no running turn to ask on.'
|
|
367
|
+
: 'This write action needs the user\'s approval, but the conversation has no running turn to ask on.'),
|
|
330
368
|
};
|
|
331
369
|
}
|
|
332
370
|
|
|
333
371
|
const createdAt = now().toISOString();
|
|
334
372
|
const questionId = String(uuid());
|
|
335
373
|
const relayMode = normalizeMode(row.relay_mode);
|
|
336
|
-
const prompt = formatRemoteRelayApprovalPrompt({ relay, action, args, caller: callerContext, agentPrompts, localSessions });
|
|
374
|
+
const prompt = formatRemoteRelayApprovalPrompt({ relay, action, args, caller: callerContext, agentPrompts, localSessions, watch });
|
|
337
375
|
const local = relay?.local === true;
|
|
338
376
|
let rationale = `Write actions on ${local ? 'other sessions of this relay' : 'other relays'} need approval in ${relayMode} mode.`;
|
|
339
377
|
if (agentPrompts > 0) {
|
|
@@ -342,6 +380,13 @@ export function createRemoteRelayCallerContext({
|
|
|
342
380
|
: 'Agents on different relays that keep prompting each other need your go from time to time.';
|
|
343
381
|
} else if (localSessions) {
|
|
344
382
|
rationale = 'Allow covers every session this conversation\'s agent starts on this relay; Deny refuses this one.';
|
|
383
|
+
} else if (watch?.kind === 'transfer') {
|
|
384
|
+
rationale = 'The subscriptions keep their settings; event messages are the relay\'s, never yours, and approve nothing.';
|
|
385
|
+
} else if (watch) {
|
|
386
|
+
const there = toText(watch.relayName) ? ` on relay "${excerpt(watch.relayName)}"` : '';
|
|
387
|
+
rationale = watch.all
|
|
388
|
+
? `Allow covers this conversation's later subscriptions${there} too. Event messages are the relay's, never yours, and approve nothing.`
|
|
389
|
+
: `Allow covers this conversation's later subscriptions${there}, except one to all sessions, which is asked again. Event messages are the relay's, never yours, and approve nothing.`;
|
|
345
390
|
}
|
|
346
391
|
// The same source for every card of this tool: it is what keeps agents
|
|
347
392
|
// (other relays' and this relay's own) from seeing or answering one.
|
|
@@ -351,7 +396,7 @@ export function createRemoteRelayCallerContext({
|
|
|
351
396
|
queueMessageId: row.id,
|
|
352
397
|
conversationId: toText(conversationId) || row.conversation_id,
|
|
353
398
|
relayMode,
|
|
354
|
-
header: localSessions ? LOCAL_SESSIONS_HEADER : APPROVAL_HEADER,
|
|
399
|
+
header: watch ? WATCH_HEADER : (localSessions ? LOCAL_SESSIONS_HEADER : APPROVAL_HEADER),
|
|
355
400
|
}, { normalizeRelayMode: normalizeMode, defaultRelayMode });
|
|
356
401
|
questionStatements.insertQuestion.run(
|
|
357
402
|
questionId,
|