@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
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Claude Cloud session worker: a per-conversation Node process that speaks
|
|
3
|
+
// the same relay contracts as the Claude/Cursor/Grok workers, but runs no
|
|
4
|
+
// agent itself. The agent and its clone live in a Claude Code cloud session;
|
|
5
|
+
// this process sends the user's messages there and follows the session's
|
|
6
|
+
// event stream (claude-cloud-session-process.mjs).
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
loadTokenFromConfig,
|
|
12
|
+
resolveRelayServerUrl,
|
|
13
|
+
} from '../../shared/worker-runtime/config-loader.mjs';
|
|
14
|
+
import { createApiClient } from '../../shared/worker-runtime/api-client.mjs';
|
|
15
|
+
import { createWorkerWebSocketLink } from '../../shared/worker-runtime/worker-websocket-link.mjs';
|
|
16
|
+
import { createHeartbeatController } from '../../shared/worker-runtime/heartbeat.mjs';
|
|
17
|
+
import { installWorkerLogFile } from '../../shared/worker-runtime/worker-log-file.mjs';
|
|
18
|
+
import { createControlPoller } from '../../shared/control-poller.mjs';
|
|
19
|
+
import { installWorkerCrashGuard } from '../../shared/worker-crash-guard.mjs';
|
|
20
|
+
import { createClaudeCloudCredentials } from '../../shared/claude-cloud/credentials.mjs';
|
|
21
|
+
import { createClaudeCloudClient } from '../../shared/claude-cloud/api-client.mjs';
|
|
22
|
+
import { resolveClaudeCloudBaseUrl } from '../../shared/claude-cloud/base-url.mjs';
|
|
23
|
+
import { createClaudeCloudSessionRunner } from './claude-cloud-session-process.mjs';
|
|
24
|
+
|
|
25
|
+
// Windows console launch: the launcher names the worker log, this copies
|
|
26
|
+
// stdout/stderr into it. A no-op wherever the launcher redirects instead.
|
|
27
|
+
installWorkerLogFile();
|
|
28
|
+
|
|
29
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const HEARTBEAT_MS = 10_000;
|
|
31
|
+
|
|
32
|
+
function parseSessionIdArg(argv = process.argv) {
|
|
33
|
+
const index = argv.indexOf('--session-id');
|
|
34
|
+
if (index !== -1 && argv[index + 1]) return String(argv[index + 1]).trim();
|
|
35
|
+
const inline = argv.find((arg) => String(arg || '').startsWith('--session-id='));
|
|
36
|
+
if (inline) return String(inline.split('=')[1] || '').trim();
|
|
37
|
+
return String(process.env.SESSION_ID || '').trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function dbg(...parts) {
|
|
41
|
+
const timestamp = new Date().toISOString();
|
|
42
|
+
console.log(`[claude-cloud-worker ${timestamp}]`, ...parts);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function main() {
|
|
46
|
+
const sdkSessionId = parseSessionIdArg();
|
|
47
|
+
if (!sdkSessionId) {
|
|
48
|
+
console.error('claude-cloud-session-worker: missing --session-id');
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const configPath = String(process.env.COPILOT_WEB_RELAY_CONFIG || '').trim()
|
|
53
|
+
|| path.resolve(__dirname, '..', 'config.json');
|
|
54
|
+
const serverUrl = resolveRelayServerUrl({ configPath });
|
|
55
|
+
const token = loadTokenFromConfig(configPath);
|
|
56
|
+
const defaultModel = String(process.env.CLAUDE_CLOUD_RELAY_MODEL || '').trim();
|
|
57
|
+
|
|
58
|
+
const api = createApiClient({
|
|
59
|
+
serverUrl,
|
|
60
|
+
token,
|
|
61
|
+
getHeaders: () => ({
|
|
62
|
+
'X-Relay-Process-Pid': String(process.pid),
|
|
63
|
+
'X-Relay-Parent-Pid': String(process.ppid),
|
|
64
|
+
'X-Relay-Session-Id': sdkSessionId,
|
|
65
|
+
'X-Relay-Conversation-Id': sdkSessionId,
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// The Claude CLI's login, read by the client's own module (the credentials
|
|
70
|
+
// file, or CLAUDE_CODE_OAUTH_TOKEN where the relay runs on one): this
|
|
71
|
+
// process never logs it. A login that has run out is nudged through the
|
|
72
|
+
// relay, which can run the CLI (`claude auth status` refreshes a login
|
|
73
|
+
// whose refresh token is still good); the file is read again afterwards.
|
|
74
|
+
// The base URL is the Anthropic API unless a test names a fake one on this
|
|
75
|
+
// machine (base-url.mjs takes nothing else).
|
|
76
|
+
const cloud = createClaudeCloudClient({
|
|
77
|
+
credentials: createClaudeCloudCredentials({
|
|
78
|
+
nudge: async () => {
|
|
79
|
+
const answer = await api('POST', '/api/claude-cloud/login-nudge', {});
|
|
80
|
+
dbg('login nudge', answer?.nudged ? 'ran' : 'skipped', answer?.expired ? 'still expired' : `expires ${answer?.expiresAt || '?'}`);
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
baseUrl: resolveClaudeCloudBaseUrl(process.env),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const controlPoller = createControlPoller({
|
|
87
|
+
api,
|
|
88
|
+
sdkSessionId,
|
|
89
|
+
abortAckNote: 'cloud turn interrupted',
|
|
90
|
+
dbg,
|
|
91
|
+
});
|
|
92
|
+
const runner = createClaudeCloudSessionRunner({
|
|
93
|
+
api,
|
|
94
|
+
cloud,
|
|
95
|
+
sdkSessionId,
|
|
96
|
+
defaultModel,
|
|
97
|
+
controlPoller,
|
|
98
|
+
dbg,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
let heartbeatTimer = null;
|
|
102
|
+
const heartbeat = createHeartbeatController({
|
|
103
|
+
api,
|
|
104
|
+
pollMs: HEARTBEAT_MS,
|
|
105
|
+
getSessionReady: () => true,
|
|
106
|
+
getHeartbeatTimer: () => heartbeatTimer,
|
|
107
|
+
setHeartbeatTimer: (timer) => { heartbeatTimer = timer; },
|
|
108
|
+
getActiveQueueMessageId: () => runner.getActiveQueueMessageId(),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const wsLink = createWorkerWebSocketLink({
|
|
112
|
+
serverUrl,
|
|
113
|
+
token,
|
|
114
|
+
dbg,
|
|
115
|
+
getSessionReady: () => true,
|
|
116
|
+
getSessionId: () => sdkSessionId,
|
|
117
|
+
getPid: () => process.pid,
|
|
118
|
+
onDeliver: async (pending, reason) => {
|
|
119
|
+
dbg('queue.deliver received', `reason=${reason}`, `msgId=${pending?.message?.id || 'none'}`);
|
|
120
|
+
try {
|
|
121
|
+
return await runner.handlePendingPayload(pending);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
dbg('turn handling failed', error?.message || String(error));
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const shutdown = async (signal) => {
|
|
130
|
+
dbg(`shutting down (${signal})`);
|
|
131
|
+
try { wsLink.stop(); } catch {}
|
|
132
|
+
try { heartbeat.stopHeartbeat(); } catch {}
|
|
133
|
+
try { controlPoller.stop(); } catch {}
|
|
134
|
+
// The cloud session is left as it is: a turn in flight keeps running
|
|
135
|
+
// there, and the next worker finds it through the stored sequence number.
|
|
136
|
+
try { await runner.dispose(); } catch {}
|
|
137
|
+
process.exit(0);
|
|
138
|
+
};
|
|
139
|
+
process.on('SIGTERM', () => { shutdown('SIGTERM'); });
|
|
140
|
+
process.on('SIGINT', () => { shutdown('SIGINT'); });
|
|
141
|
+
installWorkerCrashGuard({
|
|
142
|
+
api,
|
|
143
|
+
workerName: 'claude-cloud-session-worker',
|
|
144
|
+
getActiveQueueMessageIds: () => [runner.getActiveQueueAttempt()],
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
dbg(`starting session=${sdkSessionId.slice(0, 8)} server=${serverUrl} model=${defaultModel || 'default'}`);
|
|
148
|
+
heartbeat.startHeartbeat();
|
|
149
|
+
wsLink.start();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
main().catch((error) => {
|
|
153
|
+
console.error('claude-cloud-session-worker fatal:', error?.stack || error?.message || error);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
});
|
|
@@ -62,6 +62,8 @@ export const REMOTE_RELAY_TOOL_ZOD_SHAPE = {
|
|
|
62
62
|
provider: choice('provider', REMOTE_RELAY_PROVIDERS),
|
|
63
63
|
model: text('model'),
|
|
64
64
|
cwd: text('cwd'),
|
|
65
|
+
repo: text('repo'),
|
|
66
|
+
branch: text('branch'),
|
|
65
67
|
mode: choice('mode', REMOTE_RELAY_MODES),
|
|
66
68
|
effort: text('effort'),
|
|
67
69
|
title: text('title'),
|
|
@@ -55,17 +55,6 @@ export function claudeUltracodeFlagSettings(effort) {
|
|
|
55
55
|
return { ultracode: null, enableWorkflows: null, effortLevel: effort || null };
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/**
|
|
59
|
-
* The flag-settings payload that moves a live session onto another auto-compact
|
|
60
|
-
* window. `null` clears the flag layer back to the CLI's model-tuned default,
|
|
61
|
-
* which is exactly what "Auto" means — leaving the previous window pinned would
|
|
62
|
-
* make the setting one-way.
|
|
63
|
-
*/
|
|
64
|
-
export function claudeAutoCompactFlagSettings(autoCompactWindow) {
|
|
65
|
-
const window = normalizeAutoCompactWindow(autoCompactWindow);
|
|
66
|
-
return { autoCompactWindow: window };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
58
|
/**
|
|
70
59
|
* The flag-settings payload that pins (or clears) `alwaysThinkingEnabled` on a
|
|
71
60
|
* live session.
|
|
@@ -111,7 +100,7 @@ export function normalizeAutoCompactWindow(value) {
|
|
|
111
100
|
* spreads of `settings` in the options literal would silently clobber each
|
|
112
101
|
* other. Returns null when nothing needs to be set.
|
|
113
102
|
*/
|
|
114
|
-
export function claudeSpawnSettings({ ultracode = false, autoCompactWindow = null, thinkingEnabled = null } = {}) {
|
|
103
|
+
export function claudeSpawnSettings({ ultracode = false, autoCompactWindow = null, thinkingEnabled = null, attribution = null } = {}) {
|
|
115
104
|
const window = normalizeAutoCompactWindow(autoCompactWindow);
|
|
116
105
|
const thinking = strictThinkingEnabled(thinkingEnabled);
|
|
117
106
|
const settings = {
|
|
@@ -122,10 +111,25 @@ export function claudeSpawnSettings({ ultracode = false, autoCompactWindow = nul
|
|
|
122
111
|
// present; the omit path exists for direct callers that mean to say
|
|
123
112
|
// nothing.
|
|
124
113
|
...(thinking !== null ? { alwaysThinkingEnabled: thinking } : {}),
|
|
114
|
+
// What commits and PRs say (shared/claude-attribution.mjs). null is
|
|
115
|
+
// vanilla: the key is left out and the CLI uses its own.
|
|
116
|
+
...(attribution && typeof attribution === 'object' ? { attribution } : {}),
|
|
125
117
|
};
|
|
126
118
|
return Object.keys(settings).length ? settings : null;
|
|
127
119
|
}
|
|
128
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Live twin of the attribution spawn setting, for a value (OAR or off). A
|
|
123
|
+
* running CLI takes a new text through the flag layer (it arrives as a fresh
|
|
124
|
+
* system-reminder on the next user turn) but cannot UNSET a value it was
|
|
125
|
+
* spawned with — `{}`, `null` and `true` all leave the spawn-time text in
|
|
126
|
+
* force (wire-logged probe, 2026-10-01). Back to vanilla therefore means a
|
|
127
|
+
* fresh process without the key; the session runner does that.
|
|
128
|
+
*/
|
|
129
|
+
export function claudeAttributionFlagSettings(attribution) {
|
|
130
|
+
return { attribution: attribution && typeof attribution === 'object' ? attribution : { commit: '', pr: '', sessionUrl: false } };
|
|
131
|
+
}
|
|
132
|
+
|
|
129
133
|
export function permissionModeForRelayMode(relayMode) {
|
|
130
134
|
const mode = String(relayMode || 'agent').trim().toLowerCase();
|
|
131
135
|
if (mode === 'plan') return 'plan';
|
|
@@ -228,6 +232,7 @@ export function startClaudeSession({
|
|
|
228
232
|
reasoningEffort = '',
|
|
229
233
|
autoCompactWindow = null,
|
|
230
234
|
thinkingEnabled = null,
|
|
235
|
+
attribution = null,
|
|
231
236
|
thinkingDisplay = '',
|
|
232
237
|
abortController,
|
|
233
238
|
canUseTool,
|
|
@@ -248,7 +253,7 @@ export function startClaudeSession({
|
|
|
248
253
|
// only way to hand a fresh CLI the session-scoped ultracode flag, the
|
|
249
254
|
// auto-compact window and the thinking pin, and all three share one
|
|
250
255
|
// `settings` object.
|
|
251
|
-
const spawnSettings = claudeSpawnSettings({ ultracode, autoCompactWindow, thinkingEnabled });
|
|
256
|
+
const spawnSettings = claudeSpawnSettings({ ultracode, autoCompactWindow, thinkingEnabled, attribution });
|
|
252
257
|
// Relay tools need the worker's authenticated API helper; a caller that
|
|
253
258
|
// passes none (tests, probes) gets a session without them.
|
|
254
259
|
const relayMcpServer = typeof api === 'function'
|