@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
@@ -1,325 +1,2 @@
1
- function normalizeText(value) {
2
- const text = String(value || "").trim();
3
- return text || null;
4
- }
5
-
6
- function normalizePositiveInt(value) {
7
- const numeric = Number(value);
8
- if (!Number.isFinite(numeric)) return null;
9
- const intValue = Math.trunc(numeric);
10
- return intValue > 0 ? intValue : null;
11
- }
12
-
13
- function resolveSocketStateValue(socket, stateName, fallback) {
14
- const candidates = [
15
- socket?.[stateName],
16
- socket?.constructor?.[stateName],
17
- globalThis?.WebSocket?.[stateName],
18
- ];
19
- for (const value of candidates) {
20
- const numeric = Number(value);
21
- if (Number.isFinite(numeric)) return numeric;
22
- }
23
- return fallback;
24
- }
25
-
26
- function toWebSocketUrl(serverUrl, token, getSessionId, getPid) {
27
- const source = new URL(String(serverUrl || "http://localhost:3333"));
28
- source.protocol = source.protocol === "https:" ? "wss:" : "ws:";
29
- source.pathname = "/api/session-worker/ws";
30
- source.search = "";
31
- if (token) source.searchParams.set("token", token);
32
- const sessionId = normalizeText(typeof getSessionId === "function" ? getSessionId() : null);
33
- if (sessionId) source.searchParams.set("sessionId", sessionId);
34
- const pid = normalizePositiveInt(typeof getPid === "function" ? getPid() : null);
35
- if (pid) source.searchParams.set("pid", String(pid));
36
- return source.toString();
37
- }
38
-
39
- export function createWorkerWebSocketLink({
40
- serverUrl,
41
- token,
42
- dbg = () => {},
43
- onDeliver = async () => {},
44
- // Fire-and-forget control pushes from the server (e.g. stop a background
45
- // task). Never blocks delivery; failures are the handler's to log.
46
- onControl = () => {},
47
- getSessionReady = () => false,
48
- getSessionId = () => null,
49
- getPid = () => null,
50
- minBackoffMs = 1000,
51
- maxBackoffMs = 8_000,
52
- jitterMs = 250,
53
- readyRefreshMs = 10_000,
54
- staleConnectionMs = 30_000,
55
- WebSocketImpl = globalThis.WebSocket,
56
- setTimeoutImpl = setTimeout,
57
- clearTimeoutImpl = clearTimeout,
58
- setIntervalImpl = setInterval,
59
- clearIntervalImpl = clearInterval,
60
- now = () => Date.now(),
61
- } = {}) {
62
- let ws = null;
63
- let stopped = false;
64
- let reconnectTimer = null;
65
- let readyRefreshTimer = null;
66
- let reconnectAttempt = 0;
67
- let reconnectDelayMs = 0;
68
- let deliveryInFlight = null;
69
- let lastOpenAt = null;
70
- let lastMessageAt = null;
71
- let lastHelloSentAt = null;
72
- let lastReadySentAt = null;
73
- let lastPingSentAt = null;
74
- let lastPongAt = null;
75
- let lastQueueChangedAt = null;
76
- let lastDeliveryAt = null;
77
-
78
- function getNowMs() {
79
- const value = Number(now());
80
- return Number.isFinite(value) ? value : Date.now();
81
- }
82
-
83
- function isSocketOpen() {
84
- return !!ws && ws.readyState === resolveSocketStateValue(ws, "OPEN", 1);
85
- }
86
-
87
- function send(payload) {
88
- if (!isSocketOpen()) return false;
89
- try {
90
- ws.send(JSON.stringify(payload));
91
- return true;
92
- } catch {
93
- return false;
94
- }
95
- }
96
-
97
- function notifyHello(reason = "worker-hello") {
98
- if (!getSessionReady()) return false;
99
- const sent = send({
100
- type: "worker.hello",
101
- reason,
102
- sessionId: normalizeText(getSessionId()),
103
- pid: normalizePositiveInt(getPid()),
104
- });
105
- if (sent) lastHelloSentAt = getNowMs();
106
- return sent;
107
- }
108
-
109
- async function notifyReady(reason = "worker-ready") {
110
- if (!getSessionReady() || deliveryInFlight) return false;
111
- const sent = send({
112
- type: "worker.ready",
113
- reason,
114
- sessionId: normalizeText(getSessionId()),
115
- pid: normalizePositiveInt(getPid()),
116
- });
117
- if (sent) lastReadySentAt = getNowMs();
118
- return sent;
119
- }
120
-
121
- function notifyPing(reason = "readiness-refresh") {
122
- if (!getSessionReady()) return false;
123
- const sent = send({
124
- type: "worker.ping",
125
- reason,
126
- sessionId: normalizeText(getSessionId()),
127
- pid: normalizePositiveInt(getPid()),
128
- });
129
- if (sent) lastPingSentAt = getNowMs();
130
- return sent;
131
- }
132
-
133
- async function deliverPending(pending, reason = "queue-deliver") {
134
- if (!getSessionReady()) return false;
135
- if (deliveryInFlight) return deliveryInFlight;
136
- deliveryInFlight = Promise.resolve()
137
- .then(() => onDeliver(pending, reason))
138
- .catch((error) => {
139
- dbg("worker ws delivery failed", reason, error?.message || String(error));
140
- return false;
141
- })
142
- .finally(() => {
143
- lastDeliveryAt = getNowMs();
144
- deliveryInFlight = null;
145
- });
146
- return deliveryInFlight;
147
- }
148
-
149
- function clearReconnectTimer() {
150
- if (!reconnectTimer) return;
151
- clearTimeoutImpl(reconnectTimer);
152
- reconnectTimer = null;
153
- }
154
-
155
- function clearReadyRefreshTimer() {
156
- if (!readyRefreshTimer) return;
157
- clearIntervalImpl(readyRefreshTimer);
158
- readyRefreshTimer = null;
159
- }
160
-
161
- function closeStaleSocket(reason) {
162
- if (!isSocketOpen()) return false;
163
- dbg("worker ws stale connection closing", reason);
164
- try { ws?.close?.(); } catch {}
165
- return true;
166
- }
167
-
168
- function refreshReadiness(reason = "readiness-refresh") {
169
- if (stopped || !getSessionReady() || !isSocketOpen()) return false;
170
- const nowMs = getNowMs();
171
- const staleMs = Math.max(0, Number(staleConnectionMs) || 0);
172
- if (staleMs > 0 && lastMessageAt && (nowMs - lastMessageAt) > staleMs) {
173
- return closeStaleSocket(`no-server-message:${reason}`);
174
- }
175
- notifyPing(reason);
176
- if (deliveryInFlight) return true;
177
- notifyHello(reason);
178
- void notifyReady(reason);
179
- return true;
180
- }
181
-
182
- function startReadyRefreshTimer() {
183
- clearReadyRefreshTimer();
184
- const intervalMs = Math.max(1000, Number(readyRefreshMs) || 10_000);
185
- readyRefreshTimer = setIntervalImpl(() => {
186
- refreshReadiness("readiness-refresh");
187
- }, intervalMs);
188
- if (typeof readyRefreshTimer?.unref === "function") readyRefreshTimer.unref();
189
- }
190
-
191
- function scheduleReconnect() {
192
- if (stopped) return;
193
- clearReconnectTimer();
194
- reconnectAttempt += 1;
195
- const backoff = Math.min(maxBackoffMs, minBackoffMs * (2 ** Math.max(0, reconnectAttempt - 1)));
196
- const jitter = Math.floor(Math.random() * Math.max(0, jitterMs));
197
- reconnectDelayMs = Math.min(maxBackoffMs, backoff + jitter);
198
- dbg("worker ws reconnect scheduled", `attempt=${reconnectAttempt}`, `delayMs=${reconnectDelayMs}`);
199
- reconnectTimer = setTimeoutImpl(() => {
200
- reconnectTimer = null;
201
- connect();
202
- }, reconnectDelayMs);
203
- }
204
-
205
- function connect() {
206
- if (stopped) return;
207
- if (ws) {
208
- const openState = resolveSocketStateValue(ws, "OPEN", 1);
209
- const connectingState = resolveSocketStateValue(ws, "CONNECTING", 0);
210
- if (ws.readyState === openState || ws.readyState === connectingState) return;
211
- }
212
- if (typeof WebSocketImpl !== "function") {
213
- dbg("worker ws unavailable: global WebSocket is missing");
214
- return;
215
- }
216
- const url = toWebSocketUrl(serverUrl, token, getSessionId, getPid);
217
- ws = new WebSocketImpl(url);
218
- ws.addEventListener("open", () => {
219
- const openedAt = getNowMs();
220
- reconnectAttempt = 0;
221
- reconnectDelayMs = 0;
222
- lastOpenAt = openedAt;
223
- lastMessageAt = openedAt;
224
- dbg("worker ws connected");
225
- notifyHello("ws-open");
226
- void notifyReady("ws-open");
227
- startReadyRefreshTimer();
228
- });
229
- ws.addEventListener("message", (event) => {
230
- lastMessageAt = getNowMs();
231
- let payload = null;
232
- try {
233
- payload = JSON.parse(String(event?.data || ""));
234
- } catch {
235
- return;
236
- }
237
- if (payload?.type === "queue.deliver") {
238
- void deliverPending(payload.pending || null, String(payload.reason || "queue-deliver"))
239
- .finally(() => notifyReady("delivery-complete"));
240
- return;
241
- }
242
- if (payload?.type === "server.hello") {
243
- void notifyReady(String(payload.reason || "server-hello"));
244
- return;
245
- }
246
- if (payload?.type === "server.pong") {
247
- lastPongAt = getNowMs();
248
- return;
249
- }
250
- if (payload?.type === "server.draining") {
251
- dbg("worker ws draining", payload.reason || "relay-shutdown");
252
- try { ws?.close?.(); } catch {}
253
- return;
254
- }
255
- if (payload?.type === "queue.changed") {
256
- lastQueueChangedAt = getNowMs();
257
- void notifyReady("queue-changed");
258
- return;
259
- }
260
- if (payload?.type === "worker.control") {
261
- try {
262
- void onControl(payload.control || null);
263
- } catch (error) {
264
- dbg("worker ws control handler failed", error?.message || String(error));
265
- }
266
- return;
267
- }
268
- if (payload?.type === "queue.blocked") {
269
- dbg("worker ws blocked", payload.reason || "blocked");
270
- }
271
- });
272
- ws.addEventListener("close", () => {
273
- clearReadyRefreshTimer();
274
- if (stopped) return;
275
- dbg("worker ws disconnected");
276
- scheduleReconnect();
277
- });
278
- ws.addEventListener("error", (error) => {
279
- dbg("worker ws error", error?.message || String(error));
280
- try { ws?.close?.(); } catch {}
281
- });
282
- }
283
-
284
- function start() {
285
- stopped = false;
286
- connect();
287
- }
288
-
289
- function stop() {
290
- stopped = true;
291
- clearReconnectTimer();
292
- clearReadyRefreshTimer();
293
- if (ws) {
294
- try { ws.close(); } catch {}
295
- ws = null;
296
- }
297
- }
298
-
299
- function status() {
300
- // Some WebSocket implementations expose OPEN only on the constructor —
301
- // the same reason every other check here goes through the resolver.
302
- const connected = !!ws && ws.readyState === resolveSocketStateValue(ws, "OPEN", 1);
303
- return {
304
- connected,
305
- delivering: !!deliveryInFlight,
306
- reconnectAttempt,
307
- reconnectDelayMs,
308
- lastOpenAt,
309
- lastMessageAt,
310
- lastHelloSentAt,
311
- lastReadySentAt,
312
- lastPingSentAt,
313
- lastPongAt,
314
- lastQueueChangedAt,
315
- lastDeliveryAt,
316
- };
317
- }
318
-
319
- return {
320
- notifyReady,
321
- start,
322
- stop,
323
- status,
324
- };
325
- }
1
+ // relocated for extension retirement; shim dies with the extension engine
2
+ export * from "../../../../shared/worker-runtime/worker-websocket-link.mjs";
@@ -1,160 +1,2 @@
1
- import {
2
- applyPreviewInstructions,
3
- createPreviewInstructionsProvider,
4
- } from "../../../../shared/preview-instructions.mjs";
5
-
6
- export { createPreviewInstructionsProvider };
7
-
8
- export function buildPrompt(message) {
9
- const text = String(message?.text || "").trim();
10
- const attachments = Array.isArray(message?.attachments) ? message.attachments : [];
11
- if (!attachments.length) return text;
12
-
13
- const attachmentNote = attachments.map((att) => {
14
- const name = att?.name ? String(att.name) : "attachment";
15
- const type = att?.type ? String(att.type) : "file";
16
- if (type.startsWith("image/")) return `${name} (${type}, inline image attachment)`;
17
- const reference = att?.path ? ` @${att.path}` : "";
18
- return `${name} (${type})${reference}`;
19
- }).join(", ");
20
- const suffix = `\n\n[Attached file${attachments.length === 1 ? "" : "s"}: ${attachmentNote}]`;
21
- return text ? `${text}${suffix}` : suffix.trimStart();
22
- }
23
-
24
- function normalizeRelayMode(mode) {
25
- const value = String(mode || "agent").trim().toLowerCase();
26
- if (value === "plan" || value === "ask" || value === "autopilot" || value === "agent") return value;
27
- return "agent";
28
- }
29
-
30
- function buildModeInstructionText(mode) {
31
- if (mode === "plan") {
32
- return [
33
- "Draft a concise plan only.",
34
- "Use read-only inspection tools (glob, rg, view) only when they materially improve plan quality; otherwise draft from provided context.",
35
- "Do not edit repository files or run mutating commands unless the user explicitly asks for implementation.",
36
- "If clarification is required, pause and ask through the web relay.",
37
- "These instructions remain in effect until relay mode changes.",
38
- ].join(" ");
39
- }
40
- if (mode === "ask") {
41
- return [
42
- "Prioritize clarification questions before doing any implementation work.",
43
- "If the request is ambiguous or underspecified, pause and ask through the web relay before making assumptions.",
44
- "Do not make broad assumptions when a question would materially change the result.",
45
- "These instructions remain in effect until relay mode changes.",
46
- ].join(" ");
47
- }
48
- if (mode === "autopilot") {
49
- return [
50
- "Act directly on the request and use tools when needed.",
51
- "Keep moving unless user input is truly blocking.",
52
- "These instructions remain in effect until relay mode changes.",
53
- ].join(" ");
54
- }
55
- return [
56
- "Proceed as an interactive coding agent and use tools as needed.",
57
- "If you need clarification, pause and ask through the web relay instead of stalling silently.",
58
- "These instructions remain in effect until relay mode changes.",
59
- ].join(" ");
60
- }
61
-
62
- export function buildModeMarker(mode) {
63
- return `[Relay mode: ${normalizeRelayMode(mode)}]`;
64
- }
65
-
66
- export function buildModePrompt(mode, toolInstructions = "", options = {}) {
67
- const normalizedMode = normalizeRelayMode(mode);
68
- const includeInstructions = options?.includeInstructions !== false;
69
- const parts = [buildModeMarker(normalizedMode)];
70
- if (includeInstructions) {
71
- parts.push(buildModeInstructionText(normalizedMode));
72
- if (toolInstructions) {
73
- parts.push(toolInstructions);
74
- }
75
- }
76
- return parts.join(" ");
77
- }
78
-
79
- export function buildPromptWithMode(message, toolInstructions = "", options = {}) {
80
- const modePrompt = buildModePrompt(message?.relayMode, toolInstructions, options);
81
- const body = buildPrompt(message);
82
- return [modePrompt, body].filter(Boolean).join(" ");
83
- }
84
-
85
- /**
86
- * The relay's prompt prefix builder: tool guidance rides along only when the
87
- * relay mode changed, so repeat turns in one mode stay cheap.
88
- */
89
- export function createRelayPromptBuilder({
90
- toolInstructions = "",
91
- getPreviewInstructions = null,
92
- } = {}) {
93
- let lastPromptedRelayMode = null;
94
- return async function buildPromptWithRelayContext(message) {
95
- const relayMode = normalizeRelayMode(message?.relayMode);
96
- const includeInstructions = lastPromptedRelayMode !== relayMode;
97
- let instructions = "";
98
- if (includeInstructions) {
99
- // Guidance is advisory: a preview lookup that fails must cost the block,
100
- // never the turn.
101
- const previewBlock = typeof getPreviewInstructions === "function"
102
- ? await Promise.resolve().then(getPreviewInstructions).catch(() => "")
103
- : "";
104
- instructions = applyPreviewInstructions(toolInstructions, previewBlock);
105
- }
106
- const prompt = buildPromptWithMode({ ...message, relayMode }, instructions, { includeInstructions });
107
- lastPromptedRelayMode = relayMode;
108
- return prompt;
109
- };
110
- }
111
-
112
- export function stripPromptPrefix(text, promptPrefix = "") {
113
- const value = String(text || "").trim();
114
- if (!value) return "";
115
- const prompt = String(promptPrefix || "").trim();
116
- if (!prompt) return value;
117
- if (!value.startsWith(prompt)) return value;
118
- const remainder = value.slice(prompt.length).trim();
119
- return remainder || "";
120
- }
121
-
122
- function escapeRegExp(value) {
123
- return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
124
- }
125
-
126
- function whitespaceFlexiblePattern(value) {
127
- return String(value || "")
128
- .trim()
129
- .split(/\s+/)
130
- .filter(Boolean)
131
- .map((part) => escapeRegExp(part))
132
- .join("\\s+");
133
- }
134
-
135
- function stripPromptPrefixFlexible(text, promptPrefix = "") {
136
- const value = String(text || "").trim();
137
- const prompt = String(promptPrefix || "").trim();
138
- if (!value || !prompt) return value;
139
- const pattern = new RegExp(`^${whitespaceFlexiblePattern(prompt)}\\s*`, "i");
140
- const stripped = value.replace(pattern, "").trim();
141
- return stripped || "";
142
- }
143
-
144
- export function stripPromptContextPrefix(text, message, toolInstructions = "", promptPrefix = "") {
145
- const value = String(text || "").trim();
146
- if (!value) return "";
147
- const candidates = [
148
- String(promptPrefix || "").trim(),
149
- buildPromptWithMode(message, toolInstructions),
150
- buildPromptWithMode(message, toolInstructions, { includeInstructions: false }),
151
- buildModeMarker(message?.relayMode),
152
- ].filter(Boolean);
153
- for (const candidate of candidates) {
154
- const stripped = stripPromptPrefix(value, candidate);
155
- if (stripped !== value) return stripped;
156
- const flexibleStripped = stripPromptPrefixFlexible(value, candidate);
157
- if (flexibleStripped !== value) return flexibleStripped;
158
- }
159
- return value;
160
- }
1
+ // relocated for extension retirement; shim dies with the extension engine
2
+ export * from "../../../../shared/worker-runtime/prompt-context.mjs";