@oar-sh/oar 0.9.2 → 0.9.3

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.
Files changed (94) hide show
  1. package/CHANGELOG.md +269 -0
  2. package/README.md +407 -424
  3. package/bin/oar.js +25 -10
  4. package/package.json +7 -4
  5. package/server/README.md +1 -1
  6. package/server/claude-worker/claude-session-process.mjs +981 -44
  7. package/server/claude-worker/claude-session-worker.mjs +45 -4
  8. package/server/claude-worker/claude-turn-publisher.mjs +81 -20
  9. package/server/claude-worker/claude-worker-link-wiring.mjs +9 -0
  10. package/server/copilot-worker/copilot-ask-user-tool.mjs +89 -0
  11. package/server/copilot-worker/copilot-continuation-signals.mjs +55 -0
  12. package/server/copilot-worker/copilot-question-bridge.mjs +34 -12
  13. package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +101 -12
  14. package/server/copilot-worker/copilot-sdk-session-process.mjs +2269 -492
  15. package/server/copilot-worker/copilot-sdk-session-worker.mjs +82 -20
  16. package/server/db-schema.mjs +20 -0
  17. package/server/migrations/0004-steer-settle-markers.mjs +119 -0
  18. package/server/public/app/api-client.js +22 -2
  19. package/server/public/app/ask-user-view.js +102 -13
  20. package/server/public/app/attachments-view.js +37 -2
  21. package/server/public/app/background-task-timeout-options.mjs +9 -0
  22. package/server/public/app/background-tasks-view.mjs +33 -18
  23. package/server/public/app/bootstrap.js +34 -8
  24. package/server/public/app/composer-control-state.mjs +52 -34
  25. package/server/public/app/conversation-draft-sync.mjs +187 -0
  26. package/server/public/app/conversation-list-filter.mjs +86 -0
  27. package/server/public/app/conversation-provider-indicator.mjs +8 -0
  28. package/server/public/app/conversation-view.js +919 -194
  29. package/server/public/app/emoji-view.js +2 -0
  30. package/server/public/app/font-scaling.js +4 -37
  31. package/server/public/app/journal-view.js +145 -5
  32. package/server/public/app/model-selector-options.mjs +1 -1
  33. package/server/public/app/question-multi-select.mjs +52 -0
  34. package/server/public/app/relay-prompt-sanitizer.mjs +17 -2
  35. package/server/public/app/settings-modal.js +8 -6
  36. package/server/public/app/socket-handlers.js +19 -0
  37. package/server/public/app/steered-turn-merge.mjs +100 -0
  38. package/server/public/app/store.js +126 -15
  39. package/server/public/app/sync-outbox.mjs +16 -8
  40. package/server/public/app/tool-activity-emoji.mjs +36 -0
  41. package/server/public/app/transcript-viewport-anchor.mjs +210 -0
  42. package/server/public/index.html +206 -9
  43. package/server/public/sw.js +7 -0
  44. package/server/relay-tools.md +2 -0
  45. package/server/repositories/message-repository.mjs +61 -18
  46. package/server/repositories/question-repository.mjs +15 -0
  47. package/server/repositories/session-repository.mjs +7 -31
  48. package/server/routes/messages-routes.mjs +488 -46
  49. package/server/routes/sessions-routes.mjs +298 -55
  50. package/server/server-runtime.mjs +65 -25
  51. package/server/services/live-turn-picker.mjs +47 -0
  52. package/server/services/relay-prompt-sanitizer.mjs +17 -2
  53. package/server/services/relay-question-context.mjs +46 -0
  54. package/server/services/sdk-session-import-service.mjs +49 -7
  55. package/server/services/session-worker-registry-service.mjs +75 -0
  56. package/server/services/session-worker-supervisor-service.mjs +7 -1
  57. package/server/services/session-worker-websocket-service.mjs +23 -1
  58. package/server/services/steer-resend.mjs +45 -0
  59. package/shared/background-task-timeout.mjs +12 -4
  60. package/shared/control-poller.mjs +4 -1
  61. package/shared/question-multi-select.mjs +16 -0
  62. package/shared/relay-stop-failure.mjs +15 -0
  63. package/shared/slash-command-guard.mjs +76 -0
  64. package/shared/steer-note.mjs +66 -0
  65. package/shared/steer-settle-failure.mjs +89 -0
  66. package/shared/steer-settle-markers.mjs +18 -0
  67. package/shared/worker-crash-guard.mjs +40 -25
  68. package/shared/worker-runtime/heartbeat.mjs +55 -1
  69. package/shared/worker-runtime/runner-link-wiring.mjs +44 -0
  70. package/shared/worker-runtime/worker-websocket-link.mjs +119 -8
  71. package/server/claude-worker/claude-session-test-harness.mjs +0 -250
  72. package/server/claude-worker/fixtures/workflow-run-record.json +0 -1
  73. package/server/copilot-worker/copilot-sdk-test-harness.mjs +0 -382
  74. package/server/copilot-worker/fixtures/abort-turn.json +0 -588
  75. package/server/copilot-worker/fixtures/ask-user-turn.json +0 -1154
  76. package/server/copilot-worker/fixtures/background-timer-continuation.json +0 -445
  77. package/server/copilot-worker/fixtures/background-timer-turn.json +0 -497
  78. package/server/copilot-worker/fixtures/happy-turn.json +0 -566
  79. package/server/copilot-worker/fixtures/quota-turn.json +0 -647
  80. package/server/copilot-worker/fixtures/reasoning-turn.json +0 -625
  81. package/server/copilot-worker/fixtures/resume-turn.json +0 -700
  82. package/server/copilot-worker/fixtures/subagent-turn.json +0 -1378
  83. package/server/copilot-worker/fixtures/tool-permission-turn.json +0 -1293
  84. package/server/routes/messages-routes-test-harness.mjs +0 -111
  85. package/server/services/fixtures/claude-auth-stub.cmd +0 -1
  86. package/server/services/fixtures/claude-auth-stub.mjs +0 -76
  87. package/server/services/fixtures/claude-auth-stub.sh +0 -4
  88. package/server/services/fixtures/cli-install-stub.cmd +0 -1
  89. package/server/services/fixtures/cli-install-stub.mjs +0 -98
  90. package/server/services/fixtures/cli-install-stub.sh +0 -4
  91. package/server/services/fixtures/grok-stub.cmd +0 -1
  92. package/server/services/fixtures/grok-stub.mjs +0 -89
  93. package/server/services/fixtures/grok-stub.sh +0 -4
  94. package/shared/fixtures/copilot-catalog-raw-2026-09-07.json +0 -2363
package/bin/oar.js CHANGED
@@ -17,7 +17,7 @@ function resolvePackageRoot(metaUrl = import.meta.url) {
17
17
  return path.resolve(path.dirname(fileURLToPath(metaUrl)), '..');
18
18
  }
19
19
 
20
- function parsePort(argv = []) {
20
+ function parsePort(argv = [], fallback = 3333) {
21
21
  const args = Array.isArray(argv) ? argv : [];
22
22
  for (let index = 0; index < args.length; index += 1) {
23
23
  const arg = String(args[index] || '');
@@ -30,7 +30,12 @@ function parsePort(argv = []) {
30
30
  if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) return parsed;
31
31
  }
32
32
  }
33
- return 3333;
33
+ return fallback;
34
+ }
35
+
36
+ function normalizePort(value) {
37
+ const parsed = Number.parseInt(String(value ?? ''), 10);
38
+ return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : null;
34
39
  }
35
40
 
36
41
  function readRelayLock(lockPath) {
@@ -184,7 +189,7 @@ function createDefaultConfig({ port = 3333, token = '' } = {}) {
184
189
  };
185
190
  }
186
191
 
187
- function resolveLauncherConfig({ packageRoot, env = process.env, relayToken = '', relayPort = 3333, layout = null } = {}) {
192
+ function resolveConfigPaths({ packageRoot, env = process.env, layout = null } = {}) {
188
193
  const envConfigPath = String(env.COPILOT_WEB_RELAY_CONFIG || '').trim();
189
194
  const repoConfigPath = path.join(packageRoot, 'server', 'config.json');
190
195
  const oarConfigPath = layout && !layout.checkout ? path.join(layout.configDir, 'config.json') : null;
@@ -195,7 +200,11 @@ function resolveLauncherConfig({ packageRoot, env = process.env, relayToken = ''
195
200
  || null
196
201
  );
197
202
  const managedPath = oarConfigPath || path.join(getLegacyManagedConfigDir(env), 'config.json');
198
- const configPath = seedPath || managedPath;
203
+ return { seedPath, configPath: seedPath || managedPath };
204
+ }
205
+
206
+ function resolveLauncherConfig({ packageRoot, env = process.env, relayToken = '', relayPort = 3333, portOverride = null, layout = null } = {}) {
207
+ const { seedPath, configPath } = resolveConfigPaths({ packageRoot, env, layout });
199
208
  const seedConfig = readJsonFile(seedPath) || {};
200
209
  const runtimeConfig = {
201
210
  ...createDefaultConfig({ port: relayPort }),
@@ -203,10 +212,9 @@ function resolveLauncherConfig({ packageRoot, env = process.env, relayToken = ''
203
212
  };
204
213
  if (relayToken) runtimeConfig.authToken = relayToken;
205
214
  if (!String(runtimeConfig.authToken || '').trim()) runtimeConfig.authToken = randomUUID();
206
- const parsedPort = Number.parseInt(String(runtimeConfig.port || relayPort || 3333), 10);
207
- runtimeConfig.port = Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535
208
- ? parsedPort
209
- : 3333;
215
+ // An explicit --port wins over the config for this run: the server gets it
216
+ // on argv, and only a config this command creates saves it.
217
+ runtimeConfig.port = normalizePort(portOverride) ?? normalizePort(runtimeConfig.port) ?? normalizePort(relayPort) ?? 3333;
210
218
  return {
211
219
  configPath,
212
220
  managed: !seedPath,
@@ -262,7 +270,7 @@ export async function launchRelay({
262
270
  const forwardedArgs = separatorIdx === -1 ? [] : args.slice(separatorIdx + 1);
263
271
  if (args.includes('--help') || args.includes('-h')) {
264
272
  logger.log([
265
- 'Usage: oar [--port <port>] [--token <token>] [--migrate-from <old-checkout>] [--install-extension] [--no-install-extension] [-- [gh copilot args...]]',
273
+ 'Usage: oar [--port <port>] [--migrate-from <old-checkout>] [--install-extension] [--no-install-extension] [-- [gh copilot args...]]',
266
274
  '',
267
275
  'Starts the web relay server if needed, then launches gh copilot in the current shell.',
268
276
  '--install-extension installs/updates a user-global web-relay wrapper extension and exits.',
@@ -284,7 +292,7 @@ export async function launchRelay({
284
292
  return { code: 0, reason: 'extension-installed' };
285
293
  }
286
294
 
287
- const port = parsePort(launcherArgs);
295
+ const cliPort = parsePort(launcherArgs, null);
288
296
  const serverDir = path.join(packageRoot, 'server');
289
297
  const layout = resolveStateLayout({ packageRoot, env });
290
298
 
@@ -311,6 +319,12 @@ export async function launchRelay({
311
319
  const lockPath = layout.checkout
312
320
  ? path.join(serverDir, 'data', 'relay-server.lock')
313
321
  : path.join(layout.dataDir, 'relay-server.lock');
322
+ // One port finds, launches and probes the relay: an explicit --port for this
323
+ // run, else the config's, else 3333. The probe used to take --port (or 3333)
324
+ // while the server started on the config's port, so any mismatch waited out
325
+ // the readiness deadline and killed the relay it had just started.
326
+ const { seedPath } = resolveConfigPaths({ packageRoot, env, layout });
327
+ const port = cliPort ?? normalizePort(readJsonFile(seedPath)?.port) ?? 3333;
314
328
  const statusUrl = `http://localhost:${port}/api/status`;
315
329
  const running = await detectRunningRelay({
316
330
  lockPath,
@@ -323,6 +337,7 @@ export async function launchRelay({
323
337
  env,
324
338
  relayToken: running?.lock?.token || '',
325
339
  relayPort: port,
340
+ portOverride: cliPort,
326
341
  layout,
327
342
  });
328
343
  if (runtimeConfig.managed || !fs.existsSync(runtimeConfig.configPath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oar-sh/oar",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "type": "module",
5
5
  "description": "OAR — Open Agent Relay: drive your local coding agents from any browser",
6
6
  "license": "MIT",
@@ -20,6 +20,8 @@
20
20
  "bin/",
21
21
  "server/",
22
22
  "!server/**/*.test.mjs",
23
+ "!server/**/*-test-harness.mjs",
24
+ "!server/**/fixtures/",
23
25
  "!server/data/",
24
26
  "!server/logs/",
25
27
  "!server/config.json",
@@ -28,6 +30,7 @@
28
30
  "server/uploads/image_input.md",
29
31
  "shared/",
30
32
  "!shared/**/*.test.mjs",
33
+ "!shared/**/fixtures/",
31
34
  ".github/extensions/web-relay/",
32
35
  "!.github/extensions/web-relay/**/*.test.mjs",
33
36
  "README.md",
@@ -43,9 +46,9 @@
43
46
  "test:e2e:raw": "node ./node_modules/@playwright/test/cli.js test --config tests/playwright.config.mjs"
44
47
  },
45
48
  "dependencies": {
46
- "@anthropic-ai/claude-agent-sdk": "^0.3.261",
47
- "@cursor/sdk": "^1.0.27",
48
- "@github/copilot-sdk": "^1.0.13",
49
+ "@anthropic-ai/claude-agent-sdk": "^0.3.283",
50
+ "@cursor/sdk": "^1.0.32",
51
+ "@github/copilot-sdk": "^1.0.14",
49
52
  "better-sqlite3": "^12.10.0",
50
53
  "express": "^4.18.2",
51
54
  "qrcode-terminal": "^0.12.0",
package/server/README.md CHANGED
@@ -773,7 +773,7 @@ Queue metrics include `parkedCount` for turns deferred behind restart/rebind gat
773
773
  | GET | `/api/search/messages` | Search message text across all conversations (`q`, `limit`, `offset`) |
774
774
  | PATCH | `/api/conversation/:id` | Update a conversation title |
775
775
  | POST | `/api/conversation/:id/compact` | Compact a conversation into a new one with carry-over summary seed |
776
- | DELETE | `/api/conversation/:id` | Delete a conversation |
776
+ | DELETE | `/api/conversation/:id` | Delete a conversation: stops its session worker, deletes its CLI session, removes its rows. A Copilot session the relay only imported and never ran is hidden and tombstoned instead: nothing is stopped and its CLI session is kept. `409 conversation-active` (`reason: turn-running` / `background-tasks`) while it is still working; `409 worker-stop-failed` when its worker survives the stop (nothing deleted) |
777
777
  | GET | `/api/sdk-session-delete/pending` | (CLI relay) Fetch next pending SDK session delete request |
778
778
  | POST | `/api/sdk-session-delete/result` | (CLI relay) Report SDK session delete result |
779
779
  | POST | `/api/session-sync` | (CLI relay) Sync conversation↔SDK binding and optionally confirm orchestrator rebind completion |