@oar-sh/oar 0.9.1 → 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 (146) hide show
  1. package/.github/extensions/web-relay/extension.mjs +0 -3
  2. package/.github/extensions/web-relay/polling/heartbeat.mjs +2 -55
  3. package/.github/extensions/web-relay/polling/polling-loop.mjs +0 -1
  4. package/.github/extensions/web-relay/runtime/api-client.mjs +2 -34
  5. package/.github/extensions/web-relay/runtime/config-loader.mjs +2 -104
  6. package/.github/extensions/web-relay/runtime/send-and-wait-errors.mjs +2 -170
  7. package/.github/extensions/web-relay/runtime/session-io.mjs +1 -71
  8. package/.github/extensions/web-relay/runtime/worker-websocket-link.mjs +2 -325
  9. package/.github/extensions/web-relay/skills/prompt-context.mjs +2 -160
  10. package/CHANGELOG.md +269 -0
  11. package/README.md +407 -424
  12. package/bin/oar.js +142 -11
  13. package/package.json +8 -4
  14. package/server/README.md +59 -22
  15. package/server/claude-worker/claude-sdk-adapter.mjs +7 -2
  16. package/server/claude-worker/claude-session-process.mjs +1013 -45
  17. package/server/claude-worker/claude-session-worker.mjs +54 -9
  18. package/server/claude-worker/claude-turn-publisher.mjs +110 -19
  19. package/server/claude-worker/claude-worker-link-wiring.mjs +9 -0
  20. package/server/copilot-sdk-runtime.mjs +12 -3
  21. package/server/copilot-worker/copilot-ask-user-tool.mjs +89 -0
  22. package/server/copilot-worker/copilot-attachments.mjs +35 -2
  23. package/server/copilot-worker/copilot-continuation-signals.mjs +55 -0
  24. package/server/copilot-worker/copilot-model-switch.mjs +425 -0
  25. package/server/copilot-worker/copilot-prompt-context.mjs +20 -8
  26. package/server/copilot-worker/copilot-question-bridge.mjs +129 -16
  27. package/server/copilot-worker/copilot-sdk-adapter.mjs +59 -11
  28. package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +136 -12
  29. package/server/copilot-worker/copilot-sdk-session-process.mjs +2753 -527
  30. package/server/copilot-worker/copilot-sdk-session-worker.mjs +94 -25
  31. package/server/cursor-worker/cursor-session-worker.mjs +5 -5
  32. package/server/cursor-worker/cursor-turn-runner.mjs +50 -4
  33. package/server/db-schema.mjs +61 -1
  34. package/server/features.mjs +155 -41
  35. package/server/grok-worker/grok-session-worker.mjs +5 -5
  36. package/server/grok-worker/grok-turn-runner.mjs +45 -4
  37. package/server/migrations/0004-steer-settle-markers.mjs +119 -0
  38. package/server/public/app/annotate-core.mjs +181 -0
  39. package/server/public/app/annotate-editor.js +617 -0
  40. package/server/public/app/api-client.js +86 -5
  41. package/server/public/app/ask-user-view.js +102 -13
  42. package/server/public/app/attachments-view.js +172 -3
  43. package/server/public/app/background-task-timeout-options.mjs +9 -0
  44. package/server/public/app/background-tasks-view.mjs +34 -19
  45. package/server/public/app/bootstrap.js +153 -69
  46. package/server/public/app/composer-control-state.mjs +52 -34
  47. package/server/public/app/context-tier-options.mjs +24 -4
  48. package/server/public/app/conversation-draft-sync.mjs +187 -0
  49. package/server/public/app/conversation-list-filter.mjs +86 -0
  50. package/server/public/app/conversation-preferences.mjs +47 -10
  51. package/server/public/app/conversation-provider-indicator.mjs +8 -0
  52. package/server/public/app/conversation-view.js +1032 -218
  53. package/server/public/app/emoji-view.js +2 -0
  54. package/server/public/app/font-scaling.js +4 -37
  55. package/server/public/app/journal-view.js +190 -24
  56. package/server/public/app/model-selector-options.mjs +185 -1
  57. package/server/public/app/new-conversation-model-choice.mjs +29 -9
  58. package/server/public/app/pwa-install.js +64 -136
  59. package/server/public/app/question-multi-select.mjs +52 -0
  60. package/server/public/app/reasoning-effort-labels.mjs +13 -3
  61. package/server/public/app/relay-prompt-sanitizer.mjs +26 -3
  62. package/server/public/app/router.js +120 -11
  63. package/server/public/app/settings-modal.js +225 -20
  64. package/server/public/app/settings-tabs.js +1 -1
  65. package/server/public/app/socket-handlers.js +47 -4
  66. package/server/public/app/steered-turn-merge.mjs +100 -0
  67. package/server/public/app/store.js +159 -16
  68. package/server/public/app/sync-outbox.mjs +16 -8
  69. package/server/public/app/tool-activity-emoji.mjs +36 -0
  70. package/server/public/app/transcript-viewport-anchor.mjs +210 -0
  71. package/server/public/app/update-ui.js +194 -0
  72. package/server/public/index.html +483 -24
  73. package/server/public/sw.js +7 -0
  74. package/server/relay-tools.md +2 -0
  75. package/server/relay.mjs +23 -21
  76. package/server/repositories/message-repository.mjs +78 -21
  77. package/server/repositories/question-repository.mjs +19 -1
  78. package/server/repositories/session-repository.mjs +7 -31
  79. package/server/routes/ask-user-routes.mjs +10 -0
  80. package/server/routes/messages-routes.mjs +689 -159
  81. package/server/routes/sessions-routes.mjs +574 -80
  82. package/server/server-runtime.mjs +393 -925
  83. package/server/services/claude-auth-service.mjs +7 -1
  84. package/server/services/cli-install-service.mjs +8 -27
  85. package/server/services/cli-process-runner.mjs +30 -0
  86. package/server/services/copilot-model-discovery-service.mjs +202 -0
  87. package/server/services/data-dir-resolution.mjs +47 -0
  88. package/server/services/grok-auth-service.mjs +9 -1
  89. package/server/services/live-turn-picker.mjs +47 -0
  90. package/server/services/model-variant-catalog-service.mjs +1183 -0
  91. package/server/services/oar-cli-helpers.mjs +6 -2
  92. package/server/services/relay-prompt-sanitizer.mjs +30 -3
  93. package/server/services/relay-question-context.mjs +46 -0
  94. package/server/services/sdk-session-import-service.mjs +144 -16
  95. package/server/services/sdk-session-sync-service.mjs +84 -27
  96. package/server/services/session-worker-registry-service.mjs +116 -3
  97. package/server/services/session-worker-supervisor-service.mjs +7 -1
  98. package/server/services/session-worker-websocket-service.mjs +119 -24
  99. package/server/services/status-event-service.mjs +8 -2
  100. package/server/services/steer-resend.mjs +45 -0
  101. package/server/services/update-check-service.mjs +220 -0
  102. package/server/services/update-install-service.mjs +279 -0
  103. package/server/services/windows-autostart-service.mjs +42 -6
  104. package/server/services/windows-boot-autostart-service.mjs +487 -0
  105. package/shared/ask-user-bridge.mjs +37 -5
  106. package/shared/background-task-timeout.mjs +12 -4
  107. package/shared/claude-model-catalog.mjs +49 -0
  108. package/shared/context-window-fallbacks.mjs +1 -0
  109. package/shared/control-poller.mjs +4 -1
  110. package/shared/copilot-model-order.mjs +182 -0
  111. package/shared/media-embed-instructions.mjs +35 -0
  112. package/shared/model-descriptors.mjs +181 -30
  113. package/shared/model-id.mjs +6 -0
  114. package/shared/pwa-app-name.mjs +44 -0
  115. package/shared/question-multi-select.mjs +16 -0
  116. package/shared/relay-stop-failure.mjs +15 -0
  117. package/shared/slash-command-guard.mjs +76 -0
  118. package/shared/steer-note.mjs +66 -0
  119. package/shared/steer-settle-failure.mjs +89 -0
  120. package/shared/steer-settle-markers.mjs +18 -0
  121. package/shared/update-semver.mjs +50 -0
  122. package/shared/worker-crash-guard.mjs +40 -14
  123. package/shared/worker-runtime/api-client.mjs +34 -0
  124. package/shared/worker-runtime/config-loader.mjs +104 -0
  125. package/shared/worker-runtime/heartbeat.mjs +109 -0
  126. package/shared/worker-runtime/prompt-context.mjs +160 -0
  127. package/shared/worker-runtime/runner-link-wiring.mjs +44 -0
  128. package/shared/worker-runtime/send-and-wait-errors.mjs +170 -0
  129. package/shared/worker-runtime/worker-websocket-link.mjs +436 -0
  130. package/server/claude-worker/claude-session-test-harness.mjs +0 -236
  131. package/server/claude-worker/fixtures/workflow-run-record.json +0 -1
  132. package/server/copilot-worker/copilot-sdk-test-harness.mjs +0 -329
  133. package/server/copilot-worker/fixtures/abort-turn.json +0 -588
  134. package/server/copilot-worker/fixtures/ask-user-turn.json +0 -1154
  135. package/server/copilot-worker/fixtures/background-timer-continuation.json +0 -445
  136. package/server/copilot-worker/fixtures/background-timer-turn.json +0 -497
  137. package/server/copilot-worker/fixtures/happy-turn.json +0 -566
  138. package/server/copilot-worker/fixtures/quota-turn.json +0 -647
  139. package/server/copilot-worker/fixtures/reasoning-turn.json +0 -625
  140. package/server/copilot-worker/fixtures/resume-turn.json +0 -700
  141. package/server/copilot-worker/fixtures/subagent-turn.json +0 -1378
  142. package/server/copilot-worker/fixtures/tool-permission-turn.json +0 -1293
  143. package/server/routes/messages-routes-test-harness.mjs +0 -111
  144. package/server/services/fixtures/claude-auth-stub.sh +0 -62
  145. package/server/services/fixtures/cli-install-stub.sh +0 -75
  146. package/server/services/fixtures/grok-stub.sh +0 -86
@@ -423,7 +423,6 @@ async function ensureSessionForConversation(conversationId, reason = "dequeue")
423
423
 
424
424
  const sessionIo = createSessionIoHelpers({
425
425
  getSession: () => session,
426
- sleep,
427
426
  dbg,
428
427
  });
429
428
 
@@ -510,7 +509,6 @@ async function startPolling() {
510
509
  setModelForMessage,
511
510
  buildPromptWithRelayContext,
512
511
  sendAndWaitWithHardTimeout,
513
- sendWithBestEffortStreaming,
514
512
  extractFinalText,
515
513
  extractGeneratedImages,
516
514
  getLastActivityText: () => getScopedLastActivityText(),
@@ -969,4 +967,3 @@ async function ensureRelayActive(reason) {
969
967
  const extractFinalText = sessionIo.extractFinalText;
970
968
  const extractGeneratedImages = sessionIo.extractGeneratedImages;
971
969
  const sendAndWaitWithHardTimeout = sessionIo.sendAndWaitWithHardTimeout;
972
- const sendWithBestEffortStreaming = sessionIo.sendWithBestEffortStreaming;
@@ -1,55 +1,2 @@
1
- export function createHeartbeatController({
2
- api,
3
- pollMs,
4
- getSessionReady,
5
- getHeartbeatTimer,
6
- setHeartbeatTimer,
7
- getActiveQueueMessageId,
8
- // Optional: every queue message the worker currently owns work for (a
9
- // persistent-process worker can hold a running turn, a delivered message
10
- // queued behind it, and a background continuation at once). Each reported
11
- // id gets its owner lease refreshed; unreported owned rows are recovered.
12
- getActiveQueueMessageIds,
13
- }) {
14
- async function pulseHeartbeat() {
15
- if (!getSessionReady()) return false;
16
- try {
17
- const activeQueueMessageId = typeof getActiveQueueMessageId === "function"
18
- ? String(getActiveQueueMessageId() || "").trim()
19
- : "";
20
- const activeQueueMessageIds = typeof getActiveQueueMessageIds === "function"
21
- ? (getActiveQueueMessageIds() || []).map((id) => String(id || "").trim()).filter(Boolean)
22
- : [];
23
- const body = {
24
- ...(activeQueueMessageId ? { activeQueueMessageId } : {}),
25
- ...(activeQueueMessageIds.length ? { activeQueueMessageIds } : {}),
26
- };
27
- await api("POST", "/api/heartbeat", body);
28
- return true;
29
- } catch {
30
- return false;
31
- }
32
- }
33
-
34
- function startHeartbeat() {
35
- if (getHeartbeatTimer()) return;
36
- void pulseHeartbeat();
37
- const timer = setInterval(() => {
38
- void pulseHeartbeat();
39
- }, pollMs);
40
- setHeartbeatTimer(timer);
41
- }
42
-
43
- function stopHeartbeat() {
44
- const timer = getHeartbeatTimer();
45
- if (!timer) return;
46
- clearInterval(timer);
47
- setHeartbeatTimer(null);
48
- }
49
-
50
- return {
51
- pulseHeartbeat,
52
- startHeartbeat,
53
- stopHeartbeat,
54
- };
55
- }
1
+ // relocated for extension retirement; shim dies with the extension engine
2
+ export * from "../../../../shared/worker-runtime/heartbeat.mjs";
@@ -428,7 +428,6 @@ export function createPollingLoop({
428
428
  setModelForMessage,
429
429
  buildPromptWithRelayContext,
430
430
  sendAndWaitWithHardTimeout,
431
- sendWithBestEffortStreaming,
432
431
  extractFinalText,
433
432
  extractGeneratedImages = () => [],
434
433
  getLastActivityText,
@@ -1,34 +1,2 @@
1
- export function createApiClient({ serverUrl, token, getHeaders }) {
2
- return async function api(method, routePath, body) {
3
- const url = `${serverUrl}${routePath}`;
4
- const extraHeaders = typeof getHeaders === "function" ? (getHeaders() || {}) : {};
5
- const opts = {
6
- method,
7
- headers: {
8
- "Content-Type": "application/json",
9
- Authorization: `Bearer ${token}`,
10
- ...extraHeaders,
11
- },
12
- ...(method !== "GET" ? { body: JSON.stringify(body || {}) } : {}),
13
- };
14
-
15
- const res = await fetch(url, opts);
16
- if (!res.ok) {
17
- const rawText = String(await res.text().catch(() => "")).trim();
18
- let detail = rawText;
19
- if (rawText) {
20
- try {
21
- const payload = JSON.parse(rawText);
22
- detail = String(payload?.error || payload?.message || rawText).trim();
23
- } catch {
24
- detail = rawText;
25
- }
26
- }
27
- const error = new Error(`HTTP ${res.status} ${routePath}${detail ? `: ${detail}` : ""}`);
28
- error.status = res.status;
29
- error.detail = detail;
30
- throw error;
31
- }
32
- return res.json();
33
- };
34
- }
1
+ // relocated for extension retirement; shim dies with the extension engine
2
+ export * from "../../../../shared/worker-runtime/api-client.mjs";
@@ -1,104 +1,2 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { fileURLToPath } from "url";
4
-
5
- function toAbsolutePath(value) {
6
- if (!value) return "";
7
- return path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
8
- }
9
-
10
- function looksLikeServerDir(serverDir) {
11
- if (!serverDir) return false;
12
- return fs.existsSync(path.join(serverDir, "server.js")) && fs.existsSync(path.join(serverDir, "config.json"));
13
- }
14
-
15
- export function resolveRelayPaths(importMetaUrl) {
16
- const __dirname = path.dirname(fileURLToPath(importMetaUrl));
17
- const projectServerDir = path.resolve(__dirname, "../../../server");
18
- const cwdServerDir = path.resolve(process.cwd(), "server");
19
- const envRoot = toAbsolutePath(process.env.COPILOT_WEB_RELAY_ROOT || "");
20
- const envServerDir = toAbsolutePath(process.env.COPILOT_WEB_RELAY_SERVER_DIR || "");
21
- const envConfigPath = toAbsolutePath(process.env.COPILOT_WEB_RELAY_CONFIG || "");
22
- const envToolsPath = toAbsolutePath(process.env.COPILOT_WEB_RELAY_TOOLS || "");
23
- const envLogDir = toAbsolutePath(process.env.COPILOT_WEB_RELAY_LOG_DIR || "");
24
-
25
- const resolvedServerDir =
26
- (looksLikeServerDir(envServerDir) && envServerDir) ||
27
- (looksLikeServerDir(path.join(envRoot, "server")) && path.join(envRoot, "server")) ||
28
- (looksLikeServerDir(projectServerDir) && projectServerDir) ||
29
- (looksLikeServerDir(cwdServerDir) && cwdServerDir) ||
30
- projectServerDir;
31
-
32
- const SERVER_DIR = resolvedServerDir;
33
- const CONFIG_PATH = envConfigPath || path.resolve(SERVER_DIR, "config.json");
34
- const RELAY_TOOLS_PATH = envToolsPath || path.resolve(SERVER_DIR, "relay-tools.md");
35
- const LOG_DIR = envLogDir || path.resolve(SERVER_DIR, "logs");
36
- const SERVER_LOG_PATH = path.resolve(LOG_DIR, "server.log");
37
- const SERVER_ERR_PATH = path.resolve(LOG_DIR, "server-err.log");
38
-
39
- return {
40
- __dirname,
41
- CONFIG_PATH,
42
- RELAY_TOOLS_PATH,
43
- SERVER_DIR,
44
- LOG_DIR,
45
- SERVER_LOG_PATH,
46
- SERVER_ERR_PATH,
47
- };
48
- }
49
-
50
- export function loadTokenFromConfig(configPath) {
51
- try {
52
- const cfg = loadRelayConfig(configPath);
53
- return String(cfg.authToken || "").trim();
54
- } catch {
55
- return "";
56
- }
57
- }
58
-
59
- export function loadRelayConfig(configPath) {
60
- try {
61
- const parsed = JSON.parse(fs.readFileSync(configPath, "utf8"));
62
- return parsed && typeof parsed === "object" ? parsed : {};
63
- } catch {
64
- return {};
65
- }
66
- }
67
-
68
- function normalizeLoopbackHost(value) {
69
- const host = String(value || "").trim();
70
- if (!host || host === "0.0.0.0" || host === "::") return "127.0.0.1";
71
- if (host === "localhost") return "127.0.0.1";
72
- return host;
73
- }
74
-
75
- export function resolveRelayServerUrl({ configPath, env = process.env } = {}) {
76
- const explicitUrl = String(env.COPILOT_WEB_RELAY_SERVER_URL || "").trim();
77
- if (explicitUrl) {
78
- const url = new URL(explicitUrl);
79
- if (url.protocol !== "http:" && url.protocol !== "https:") {
80
- throw new Error(`invalid-relay-server-url:${url.protocol}`);
81
- }
82
- url.pathname = "";
83
- url.search = "";
84
- url.hash = "";
85
- return url.toString().replace(/\/$/, "");
86
- }
87
-
88
- const config = loadRelayConfig(configPath);
89
- const configuredPort = Number.parseInt(String(config.port || ""), 10);
90
- const port = Number.isInteger(configuredPort) && configuredPort > 0 && configuredPort <= 65535
91
- ? configuredPort
92
- : 3333;
93
- const host = normalizeLoopbackHost(config.host);
94
- const formattedHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
95
- return `http://${formattedHost}:${port}`;
96
- }
97
-
98
- export function loadRelayInstructionsFromFile(relayToolsPath) {
99
- try {
100
- return String(fs.readFileSync(relayToolsPath, "utf8")).trim();
101
- } catch {
102
- return "";
103
- }
104
- }
1
+ // relocated for extension retirement; shim dies with the extension engine
2
+ export * from "../../../../shared/worker-runtime/config-loader.mjs";
@@ -1,170 +1,2 @@
1
- function readErrorText(error) {
2
- return String(error?.message || error || "").trim();
3
- }
4
-
5
- function extractFirstMatch(text, pattern) {
6
- const match = text.match(pattern);
7
- return String(match?.[1] || "").trim() || null;
8
- }
9
-
10
- export function toKebabToken(value) {
11
- return String(value || "")
12
- .trim()
13
- .toLowerCase()
14
- .replace(/[^a-z0-9]+/g, "-")
15
- .replace(/^-+|-+$/g, "") || null;
16
- }
17
-
18
- // The runtime's own vocabulary for "the billing window is spent". The Copilot
19
- // SDK's `ErrorData.errorCode` documents `quota_exceeded` and
20
- // `session_quota_exceeded`; `errorType` documents the coarser `quota`
21
- // category; HTTP 402 is the transport-level form of the same thing.
22
- const QUOTA_ERROR_CODES = new Set([
23
- "quota_exceeded",
24
- "session_quota_exceeded",
25
- "quota-exceeded",
26
- "session-quota-exceeded",
27
- "quota",
28
- ]);
29
-
30
- /**
31
- * True when a runtime flagged quota exhaustion STRUCTURALLY, independent of
32
- * how the prose was worded. This exists because the prose is the one part
33
- * GitHub rewords freely ("premium requests" → "AI credits" → "monthly quota"),
34
- * and a quota failure misfiled as retryable burns the retry budget against a
35
- * window that cannot reset until the next billing period.
36
- *
37
- * Accepts the loose shapes callers actually hold: an `Error` with `code` /
38
- * `statusCode`, or a runtime error payload with `errorCode` / `errorType` /
39
- * `statusCode`.
40
- */
41
- export function isStructuredQuotaError(source) {
42
- if (!source || typeof source !== "object") return false;
43
- for (const field of ["code", "errorCode", "errorType"]) {
44
- const token = String(source[field] || "").trim().toLowerCase();
45
- if (token && QUOTA_ERROR_CODES.has(token)) return true;
46
- }
47
- const status = Number(source.statusCode ?? source.status);
48
- return status === 402;
49
- }
50
-
51
- const QUOTA_EXHAUSTED = {
52
- code: "quota-exhausted",
53
- message: "GitHub Copilot has no AI credits left for this billing window, so the request was rejected.",
54
- guidance: "Open Check Usage for the reset time and retry after the reset, or switch this conversation to another provider.",
55
- };
56
-
57
- /**
58
- * `hint` carries structured signals that outrank the prose. Only quota is
59
- * modelled today; the prose branches below are unchanged and remain the sole
60
- * classifier when no hint is supplied, which is the extension's path.
61
- */
62
- function classifyTerminalError(text, hint = {}) {
63
- // Checked before the prose so a structurally-flagged quota error lands in
64
- // the non-retryable branch however mildly it was worded.
65
- if (hint.quota === true) return QUOTA_EXHAUSTED;
66
-
67
- const lower = text.toLowerCase();
68
- if (!lower) return null;
69
-
70
- if (lower.includes("no tool output found for function call")) {
71
- return {
72
- code: "missing-tool-output",
73
- message: "No tool output was returned for a required function call.",
74
- guidance: "Retry the message. If this keeps happening, restart the relay and include the error code.",
75
- };
76
- }
77
- if (lower.includes("tool call") && (lower.includes("not found") || lower.includes("missing"))) {
78
- return {
79
- code: "tool-call-missing",
80
- message: "A required tool call was missing in the runtime response.",
81
- guidance: "Retry the message. If it repeats, restart the relay and include the error code.",
82
- };
83
- }
84
- if (lower.includes("tool output") && lower.includes("invalid")) {
85
- return {
86
- code: "invalid-tool-output",
87
- message: "Tool output returned from the runtime was invalid.",
88
- guidance: "Retry the message. If it repeats, restart the relay and include the error code.",
89
- };
90
- }
91
- // Monthly/plan quota exhaustion is not transient: retrying before the
92
- // billing window resets can never succeed. Distinct from "rate limit"
93
- // (per-minute throttling), which stays retryable below.
94
- if ((lower.includes("quota") && (lower.includes("exceeded") || lower.includes("exhaust")))
95
- || lower.includes("out of ai credits")
96
- || lower.includes("premium request allowance")) {
97
- return QUOTA_EXHAUSTED;
98
- }
99
- if ((lower.includes("capierror") || lower.includes("http 400") || lower.includes("status 400") || /^400\b/.test(lower))
100
- && !lower.includes("timeout")
101
- && !lower.includes("temporar")
102
- && !lower.includes("rate limit")) {
103
- return {
104
- code: "request-invalid",
105
- message: "The runtime rejected the request as invalid and non-retryable.",
106
- guidance: "Retry after adjusting the request. If it persists, restart the relay and include the error code.",
107
- };
108
- }
109
-
110
- return null;
111
- }
112
-
113
- /**
114
- * `options` lets a caller that holds structured error fields (an SDK error
115
- * payload rather than a thrown `Error`) pass them in explicitly:
116
- * `{ quota: true }`, or `{ errorCode, errorType, statusCode }` to be judged
117
- * here. Structured fields carried on the error object itself (`error.code`,
118
- * `error.statusCode`) are read as well, so plain HTTP-ish errors classify
119
- * without the caller doing anything.
120
- */
121
- export function normalizeTerminalSendAndWaitError(error, options = {}) {
122
- const detail = readErrorText(error);
123
- const quota = options.quota === true
124
- || isStructuredQuotaError(options)
125
- || isStructuredQuotaError(error);
126
- const base = classifyTerminalError(detail, { quota });
127
- if (!base) return null;
128
- const functionCallId = extractFirstMatch(detail, /function call\s+([a-z0-9_-]+)/i)
129
- || extractFirstMatch(detail, /\b(call_[a-z0-9_-]+)/i);
130
- // GitHub request ids are colon-separated hex segments (AFAC:3C07CD:…).
131
- const requestId = extractFirstMatch(detail, /\brequest(?:\s+id|_id)?[:=]\s*([a-z0-9_-]+(?::[a-z0-9_-]+)*)/i)
132
- || extractFirstMatch(detail, /\b(req_[a-z0-9_-]+)/i);
133
- return {
134
- terminal: true,
135
- code: base.code,
136
- stableCode: `relay.${base.code}`,
137
- message: base.message,
138
- guidance: base.guidance,
139
- detail: detail || "unknown error",
140
- functionCallId,
141
- requestId,
142
- classificationHint: toKebabToken(error?.code || error?.name || null),
143
- };
144
- }
145
-
146
- export function isTerminalSendAndWaitError(error, options = {}) {
147
- return !!normalizeTerminalSendAndWaitError(error, options);
148
- }
149
-
150
- export function buildTerminalFailureText(error, options = {}) {
151
- const normalized = normalizeTerminalSendAndWaitError(error, options) || {
152
- stableCode: "relay.unknown-terminal",
153
- message: "The relay runtime hit a terminal error and could not complete this turn.",
154
- guidance: "Retry the message.",
155
- detail: readErrorText(error) || "unknown error",
156
- functionCallId: null,
157
- requestId: null,
158
- };
159
- const ids = [
160
- normalized.functionCallId ? `functionCallId=${normalized.functionCallId}` : null,
161
- normalized.requestId ? `requestId=${normalized.requestId}` : null,
162
- ].filter(Boolean);
163
- return [
164
- normalized.message,
165
- `Error code: ${normalized.stableCode}.`,
166
- ids.length ? `IDs: ${ids.join(", ")}.` : null,
167
- normalized.guidance,
168
- `Details: ${normalized.detail}`,
169
- ].filter(Boolean).join(" ");
170
- }
1
+ // relocated for extension retirement; shim dies with the extension engine
2
+ export * from "../../../../shared/worker-runtime/send-and-wait-errors.mjs";
@@ -223,17 +223,7 @@ function extractGeneratedImages(finalEvent) {
223
223
  return deduped.slice(0, 8);
224
224
  }
225
225
 
226
- export function createSessionIoHelpers({ getSession, sleep, dbg = () => {} }) {
227
- const STREAM_WAIT_POLL_MS = 5_000;
228
- const WAIT_TICK = Symbol("relay-stream-wait-tick");
229
-
230
- function buildRelayStreamTimeoutError(timeoutMs) {
231
- const error = new Error(`Hard timeout after ${timeoutMs}ms while streaming session.send`);
232
- error.code = "RELAY_STREAM_TIMEOUT";
233
- error.stableCode = "relay.stream-timeout";
234
- return error;
235
- }
236
-
226
+ export function createSessionIoHelpers({ getSession, dbg = () => {} }) {
237
227
  async function sendAndWaitWithHardTimeout(payload, timeoutMs) {
238
228
  // The loser timer is cancelled when the race settles: a fast turn must
239
229
  // not leave a five-minute timer armed to fire a rejection into a settled
@@ -254,65 +244,6 @@ export function createSessionIoHelpers({ getSession, sleep, dbg = () => {} }) {
254
244
  }
255
245
  }
256
246
 
257
- async function sendWithBestEffortStreaming(payload, timeoutMs, onEvent, options = {}) {
258
- const session = getSession();
259
- if (!session) throw new Error("No active Copilot session");
260
- if (typeof session.send !== "function") {
261
- return sendAndWaitWithHardTimeout(payload, timeoutMs);
262
- }
263
-
264
- const streamOrResult = await session.send(payload);
265
- if (streamOrResult && typeof streamOrResult[Symbol.asyncIterator] === "function") {
266
- const deadline = Date.now() + timeoutMs + 5_000;
267
- let finalEvent = null;
268
- const iterator = streamOrResult[Symbol.asyncIterator]();
269
- const onWaiting = typeof options?.onWaiting === "function" ? options.onWaiting : null;
270
- const waitPollMs = Math.max(250, Math.min(Number(options?.waitPollMs) || STREAM_WAIT_POLL_MS, 30_000));
271
-
272
- try {
273
- while (true) {
274
- const remainingMs = deadline - Date.now();
275
- if (remainingMs <= 0) {
276
- throw buildRelayStreamTimeoutError(timeoutMs);
277
- }
278
-
279
- const nextOrTick = await Promise.race([
280
- iterator.next(),
281
- sleep(Math.min(waitPollMs, remainingMs)).then(() => WAIT_TICK),
282
- ]);
283
-
284
- if (nextOrTick === WAIT_TICK) {
285
- if (onWaiting) {
286
- await onWaiting({
287
- timeoutMs,
288
- deadline,
289
- remainingMs: Math.max(0, deadline - Date.now()),
290
- });
291
- }
292
- continue;
293
- }
294
-
295
- if (nextOrTick?.done) break;
296
- const event = nextOrTick?.value;
297
- finalEvent = event;
298
- if (typeof onEvent === "function") {
299
- try { await onEvent(event); } catch {}
300
- }
301
- }
302
- } finally {
303
- if (typeof iterator?.return === "function") {
304
- try { await iterator.return(); } catch {}
305
- }
306
- }
307
- return finalEvent || {};
308
- }
309
-
310
- if (typeof onEvent === "function") {
311
- try { await onEvent(streamOrResult); } catch {}
312
- }
313
- return streamOrResult;
314
- }
315
-
316
247
  function extractFinalTextWithLogging(finalEvent) {
317
248
  const result = extractFinalText(finalEvent, dbg);
318
249
  // Always log envelope structure so we can diagnose extraction misses
@@ -344,6 +275,5 @@ export function createSessionIoHelpers({ getSession, sleep, dbg = () => {} }) {
344
275
  extractFinalText: extractFinalTextWithLogging,
345
276
  extractGeneratedImages: extractGeneratedImagesWithLogging,
346
277
  sendAndWaitWithHardTimeout,
347
- sendWithBestEffortStreaming,
348
278
  };
349
279
  }