@oar-sh/oar 0.9.8 → 0.9.10
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 +478 -0
- package/README.md +129 -39
- package/bin/bat/oar.bat +46 -46
- package/bin/oar.js +814 -228
- package/package.json +5 -6
- package/server/README.md +306 -4
- package/server/claude-worker/claude-remote-relay-tool.mjs +10 -0
- package/server/claude-worker/claude-session-process.mjs +35 -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 +3 -1
- package/server/cursor-worker/cursor-sdk-adapter.mjs +56 -2
- package/server/db-schema.mjs +44 -0
- package/server/migrations/0008-message-pins.mjs +85 -0
- 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/public/app/agent-exchange-fold-view.mjs +248 -0
- package/server/public/app/api-client.js +91 -0
- package/server/public/app/archive-confirm.mjs +28 -0
- package/server/public/app/ask-user-view.js +3 -0
- package/server/public/app/attachments-view.js +1024 -131
- package/server/public/app/back-guard.mjs +58 -0
- package/server/public/app/background-tasks-view.mjs +18 -26
- package/server/public/app/bootstrap.js +230 -35
- package/server/public/app/chat-actions-menu-state.mjs +116 -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/conversation-view.js +471 -110
- package/server/public/app/explorer-edit-state.mjs +68 -0
- package/server/public/app/external-link-policy.mjs +4 -1
- package/server/public/app/file-version.mjs +58 -0
- package/server/public/app/host-suspend-ui.js +6 -3
- package/server/public/app/journal-view.js +161 -27
- package/server/public/app/no-conversation-state.mjs +58 -0
- package/server/public/app/pinned-messages-view.mjs +253 -0
- package/server/public/app/provider-readiness.mjs +120 -0
- package/server/public/app/pwa-install.js +1 -1
- package/server/public/app/relay-board-view.js +3 -0
- package/server/public/app/relay-dot-state.mjs +8 -1
- 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/router.js +8 -2
- package/server/public/app/settings-modal.js +53 -1
- package/server/public/app/socket-handlers.js +65 -28
- package/server/public/app/store.js +25 -2
- 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 +534 -0
- package/server/public/app/task-row-format.mjs +29 -0
- package/server/public/app/thinking-follow.js +4 -0
- package/server/public/app/transcript-jump-bottom-view.mjs +290 -0
- package/server/public/app/transcript-jump-bottom.mjs +99 -0
- package/server/public/app/transcript-reload-freshness.mjs +25 -0
- package/server/public/app/update-ui.js +1 -1
- package/server/public/app/viewer-chat-gallery.mjs +59 -0
- package/server/public/app/viewer-controls-state.mjs +59 -0
- package/server/public/app/viewer-gallery.mjs +46 -0
- package/server/public/app/viewer-gestures.mjs +105 -0
- package/server/public/app/viewer-history.mjs +45 -0
- package/server/public/app/viewer-tap.mjs +112 -0
- package/server/public/index.html +700 -59
- package/server/repositories/message-pin-statements.mjs +39 -0
- package/server/repositories/message-repository.mjs +34 -3
- package/server/repositories/question-repository.mjs +9 -3
- package/server/repositories/remote-relay-repository.mjs +6 -2
- package/server/repositories/subagent-timeline-statements.mjs +170 -0
- package/server/routes/ask-user-routes.mjs +3 -0
- package/server/routes/messages-routes.mjs +548 -111
- package/server/routes/relay-event-routes.mjs +157 -0
- package/server/routes/remote-relay-routes.mjs +15 -0
- package/server/routes/sessions-routes.mjs +164 -24
- package/server/server-runtime.mjs +274 -62
- package/server/services/cloudflared-tunnel-service.mjs +139 -41
- package/server/services/explorer-entry-name.mjs +26 -0
- package/server/services/explorer-mutations.mjs +122 -0
- package/server/services/file-serving.mjs +155 -0
- package/server/services/message-pin-list.mjs +52 -0
- package/server/services/oar-cli-helpers.mjs +139 -4
- package/server/services/oar-cli-menu.mjs +240 -0
- package/server/services/oar-cli-wsl.mjs +129 -0
- package/server/services/preview-proxy-server.mjs +41 -23
- package/server/services/push-dispatch-service.mjs +18 -0
- package/server/services/relay-event-link-service.mjs +289 -0
- package/server/services/relay-event-service.mjs +1935 -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 +494 -2
- package/server/services/remote-relay-registry-service.mjs +11 -1
- package/server/services/safe-served-content.mjs +61 -11
- package/server/services/session-activity-tracker.mjs +487 -0
- package/server/services/session-history-refresh-service.mjs +31 -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/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/upgrade-socket-guard.mjs +16 -0
- package/shared/relay-events.mjs +371 -0
- package/shared/remote-relay-contract.mjs +157 -5
- package/shared/subagent-record-retention.mjs +31 -0
- package/shared/subagent-timeline.mjs +79 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// In the installed app, the system back button with nothing left to go back
|
|
2
|
+
// to closes the app. There is no way for a page to send itself to the
|
|
3
|
+
// background instead, but it can make sure that moment never comes: one
|
|
4
|
+
// entry marked as the floor is kept beneath the app's own entry, and when
|
|
5
|
+
// back lands on the floor the app's entry is pushed again at once. Back
|
|
6
|
+
// still closes the file viewer (which pushes its own entry above); only the
|
|
7
|
+
// last step out is swallowed. A browser tab keeps its back button: this is
|
|
8
|
+
// for the installed app alone.
|
|
9
|
+
|
|
10
|
+
export const BACK_GUARD_FLOOR = 'oarBackGuardFloor';
|
|
11
|
+
export const BACK_GUARD_APP = 'oarApp';
|
|
12
|
+
const FIRST_INTERACTION_EVENTS = ['pointerdown', 'keydown', 'touchstart'];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Lay the floor at the first touch or key, not at start-up: an entry a page
|
|
16
|
+
* leaves through pushState before the user has done anything is one the
|
|
17
|
+
* browser skips on back (its guard against pages that hijack the button),
|
|
18
|
+
* so a floor laid at start-up would be stepped over. Before the first
|
|
19
|
+
* interaction nothing a page does keeps back from leaving; from then on
|
|
20
|
+
* the floor holds.
|
|
21
|
+
*/
|
|
22
|
+
export function armBackGuardOnFirstInteraction({ history, enabled = true, target, onFloor = null } = {}) {
|
|
23
|
+
if (!enabled || !history || !target) return null;
|
|
24
|
+
const state = { guard: null };
|
|
25
|
+
const arm = () => {
|
|
26
|
+
for (const eventName of FIRST_INTERACTION_EVENTS) target.removeEventListener(eventName, arm, true);
|
|
27
|
+
if (state.guard) return;
|
|
28
|
+
state.guard = installBackGuard({ history, enabled: true, onFloor });
|
|
29
|
+
target.addEventListener('popstate', () => state.guard.onPopState());
|
|
30
|
+
};
|
|
31
|
+
for (const eventName of FIRST_INTERACTION_EVENTS) target.addEventListener(eventName, arm, true);
|
|
32
|
+
return { get armed() { return Boolean(state.guard); } };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `onFloor` is told each time back lands on the floor and is swallowed.
|
|
37
|
+
* The entry pushed again then is pushed without a touch, so the browser
|
|
38
|
+
* lets the next back leave unless the user touches the app in between:
|
|
39
|
+
* one swallowed press, then the app closes — the pattern phones use, and
|
|
40
|
+
* the moment to say "press back again to close".
|
|
41
|
+
*/
|
|
42
|
+
export function installBackGuard({ history, enabled = true, onFloor = null } = {}) {
|
|
43
|
+
if (!enabled || !history) return null;
|
|
44
|
+
const current = history.state && typeof history.state === 'object' ? history.state : {};
|
|
45
|
+
// After a reload on the app's entry the floor already lies beneath it.
|
|
46
|
+
if (!current[BACK_GUARD_FLOOR] && !current[BACK_GUARD_APP]) {
|
|
47
|
+
history.replaceState({ ...current, [BACK_GUARD_FLOOR]: true }, '');
|
|
48
|
+
history.pushState({ [BACK_GUARD_APP]: true }, '');
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
/** Feed every popstate here. */
|
|
52
|
+
onPopState() {
|
|
53
|
+
if (!history.state?.[BACK_GUARD_FLOOR]) return;
|
|
54
|
+
history.pushState({ [BACK_GUARD_APP]: true }, '');
|
|
55
|
+
onFloor?.();
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
// `backgroundTasks` on reload; hidden whenever the set is empty.
|
|
6
6
|
|
|
7
7
|
import { apiFetch } from './api-client.js';
|
|
8
|
-
import {
|
|
8
|
+
import { formatTaskModelLabel, formatTaskTokens } from './task-row-format.mjs';
|
|
9
9
|
import { prefixToolActivityEmoji } from './tool-activity-emoji.mjs';
|
|
10
|
+
|
|
11
|
+
// Shared with the sub-agent rows (subagent-row.mjs); re-exported for the
|
|
12
|
+
// transcript's finished-workflow card and the existing importers.
|
|
13
|
+
export { formatTaskModelLabel, formatTaskTokens };
|
|
10
14
|
import {
|
|
11
15
|
getPreviewsForConversation,
|
|
12
16
|
renderPreviewRowsInto,
|
|
@@ -71,16 +75,6 @@ function taskKindLabel(task) {
|
|
|
71
75
|
return label === 'Subagent' && subagentType ? subagentType : label;
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
// "claude-opus-5-5" → "Opus 5.5". A bare alias ("opus") is what a spawn pin
|
|
75
|
-
// carries until the subagent's first answer names the real model.
|
|
76
|
-
export function formatTaskModelLabel(model) {
|
|
77
|
-
const value = String(model || '').trim();
|
|
78
|
-
if (!value) return '';
|
|
79
|
-
if (/^claude-/i.test(value)) return humanizeModelLabel(value);
|
|
80
|
-
if (/^[a-z]+(\[[^\]]+\])?$/i.test(value)) return value.charAt(0).toUpperCase() + value.slice(1);
|
|
81
|
-
return humanizeModelLabel(value);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
78
|
// Detail line: the live tool call when the worker saw one ("🪓 Tool
|
|
85
79
|
// (PowerShell): cd …", cropped to the row by CSS), else the SDK's progress
|
|
86
80
|
// summary, else the bare tool name.
|
|
@@ -93,20 +87,6 @@ export function taskActivityText(task) {
|
|
|
93
87
|
return { text: lastToolName ? `using ${lastToolName}` : '', toolCall: false };
|
|
94
88
|
}
|
|
95
89
|
|
|
96
|
-
// Compact token counts: "812 tok", "62.2k tok", "1.2M tok". Exported for the
|
|
97
|
-
// transcript's finished-workflow card, which shares the panel's formatting.
|
|
98
|
-
export function formatTaskTokens(totalTokens) {
|
|
99
|
-
const tokens = Number(totalTokens);
|
|
100
|
-
if (!Number.isFinite(tokens) || tokens <= 0) return '';
|
|
101
|
-
if (tokens < 1000) return `${tokens} tok`;
|
|
102
|
-
if (tokens < 1_000_000) {
|
|
103
|
-
const thousands = tokens / 1000;
|
|
104
|
-
return `${thousands >= 100 ? Math.round(thousands) : thousands.toFixed(1)}k tok`;
|
|
105
|
-
}
|
|
106
|
-
const millions = tokens / 1_000_000;
|
|
107
|
-
return `${millions >= 100 ? Math.round(millions) : millions.toFixed(1)}M tok`;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
90
|
function escHtml(value) {
|
|
111
91
|
return String(value ?? '').replace(/[&<>"']/g, (ch) => ({
|
|
112
92
|
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
@@ -377,6 +357,16 @@ async function stopBackgroundTask(conversationId, taskId) {
|
|
|
377
357
|
}
|
|
378
358
|
}
|
|
379
359
|
|
|
360
|
+
// A sub-agent task opens its record in the viewer: the task's spawning
|
|
361
|
+
// tool_use id is the run's id. Bash tasks, monitors and workflows have no
|
|
362
|
+
// record, and Copilot's tasks carry no run id.
|
|
363
|
+
export function subagentRunIdForTask(task) {
|
|
364
|
+
const runId = String(task?.toolUseId || '').trim();
|
|
365
|
+
if (!runId) return '';
|
|
366
|
+
const type = String(task?.taskType || '').trim();
|
|
367
|
+
return type === 'local_agent' || type === 'agent' || type === 'subagent' ? runId : '';
|
|
368
|
+
}
|
|
369
|
+
|
|
380
370
|
function renderTaskRow(conversationId, task) {
|
|
381
371
|
const key = `${conversationId}:${task.taskId}`;
|
|
382
372
|
const stopping = stopsInFlight.has(key);
|
|
@@ -403,7 +393,9 @@ function renderTaskRow(conversationId, task) {
|
|
|
403
393
|
<div class="bg-task-row" data-task-id="${escHtml(task.taskId)}">
|
|
404
394
|
${foldButton}<span class="bg-task-icon" title="${escHtml(task.taskType || 'task')}">${taskIcon(task.taskType)}</span>
|
|
405
395
|
<span class="bg-task-main">
|
|
406
|
-
|
|
396
|
+
${subagentRunIdForTask(task)
|
|
397
|
+
? `<button type="button" class="subagent-open bg-task-desc" data-subagent-open="${escHtml(subagentRunIdForTask(task))}" title="Open this sub-agent's thoughts, tool calls and reply">${escHtml(task.description || task.taskId)}</button>`
|
|
398
|
+
: `<span class="bg-task-desc">${escHtml(task.description || task.taskId)}</span>`}
|
|
407
399
|
${activity.text ? `<span class="bg-task-detail${activity.toolCall ? ' bg-task-toolcall' : ''}" title="${escHtml(activity.text)}">${escHtml(activity.text)}</span>` : ''}
|
|
408
400
|
</span>
|
|
409
401
|
<span class="bg-task-side">
|