@openclaw/copilot 2026.5.28

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.
@@ -0,0 +1,1667 @@
1
+ import { n as createInfiniteSessionConfig, r as resolveCopilotAuth } from "./harness-Blgz_qk3.js";
2
+ import { createHash } from "node:crypto";
3
+ import path from "node:path";
4
+ import fsp from "node:fs/promises";
5
+ import { acquireSessionWriteLock, appendSessionTranscriptMessage, applyEmbeddedAttemptToolsAllow, buildEmbeddedAttemptToolRunContext, detectAndLoadAgentHarnessPromptImages, emitSessionTranscriptUpdate, getPluginToolMeta, isSubagentSessionKey, resolveAttemptFsWorkspaceOnly, resolveAttemptSpawnWorkspaceDir, resolveBootstrapContextForRun, resolveEmbeddedAttemptToolConstructionPlan, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, resolveSessionWriteLockAcquireTimeoutMs, resolveUserPath, runAgentHarnessBeforeMessageWriteHook } from "openclaw/plugin-sdk/agent-harness-runtime";
6
+ //#region extensions/copilot/src/dual-write-transcripts.ts
7
+ /**
8
+ * Mirrors the AgentMessages produced by the copilot agent runtime into the
9
+ * OpenClaw audit transcript that sits next to (but is distinct from) the
10
+ * SDK's own session storage.
11
+ *
12
+ * The OpenClaw shell (src/agents/command/attempt-execution.ts) already
13
+ * writes the user prompt and the terminal assistant text into the
14
+ * transcript at the end of each attempt. That is the bare minimum to
15
+ * keep `/history` working. It does NOT capture tool calls, tool
16
+ * results, or intermediate assistant turns — those live only in the
17
+ * SDK's own session file.
18
+ *
19
+ * For audit/compliance and for the codex-parity guarantees we promised
20
+ * in the proposal, we mirror the full `messagesSnapshot` (user +
21
+ * assistant + toolResult) into the OpenClaw transcript via the same
22
+ * plugin-sdk primitives that the codex extension uses
23
+ * (extensions/codex/src/app-server/transcript-mirror.ts). Both writers
24
+ * cooperate via idempotency-key dedupe: each mirrored entry carries a
25
+ * stable `${idempotencyScope}:${identity}` key, and we skip any key
26
+ * already present in the transcript on disk before appending. Both
27
+ * attempt-execution's untagged entries (no idempotencyKey) and our
28
+ * tagged mirror entries can coexist; attempt-execution dedupes its own
29
+ * final-assistant append via `embeddedAssistantGapFill` content match.
30
+ *
31
+ * Failures (lock contention, fs errors, etc.) are swallowed by the
32
+ * caller-side `dualWriteCopilotTranscriptBestEffort` wrapper used
33
+ * in attempt.ts so they cannot break the attempt; this module itself
34
+ * throws on infrastructure failure so callers can choose policy.
35
+ */
36
+ const MIRROR_IDENTITY_META_KEY = "mirrorIdentity";
37
+ /**
38
+ * Tag a message with a stable logical identity for mirror dedupe.
39
+ * Callers should use a value that is invariant for the same logical
40
+ * message across re-emits (e.g. `${sdkSessionId}:assistant:${turnIndex}`)
41
+ * but distinct for genuinely-distinct messages. When present this
42
+ * identity replaces the role/content fingerprint in the idempotency
43
+ * key, so the dedupe survives caller-scope rotation without collapsing
44
+ * distinct same-content turns. Symmetric to
45
+ * `attachCodexMirrorIdentity` in the codex extension.
46
+ */
47
+ function attachCopilotMirrorIdentity(message, identity) {
48
+ const record = message;
49
+ const existing = record["__openclaw"];
50
+ const baseMeta = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
51
+ return {
52
+ ...record,
53
+ __openclaw: {
54
+ ...baseMeta,
55
+ [MIRROR_IDENTITY_META_KEY]: identity
56
+ }
57
+ };
58
+ }
59
+ function readMirrorIdentity(message) {
60
+ const meta = message["__openclaw"];
61
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return;
62
+ const id = meta[MIRROR_IDENTITY_META_KEY];
63
+ return typeof id === "string" && id.length > 0 ? id : void 0;
64
+ }
65
+ function fingerprintMirrorMessageContent(message) {
66
+ const payload = JSON.stringify({
67
+ role: message.role,
68
+ content: message.content
69
+ });
70
+ return createHash("sha256").update(payload).digest("hex").slice(0, 16);
71
+ }
72
+ function buildMirrorDedupeIdentity(message) {
73
+ const explicit = readMirrorIdentity(message);
74
+ if (explicit) return explicit;
75
+ return `${message.role}:${fingerprintMirrorMessageContent(message)}`;
76
+ }
77
+ async function mirrorCopilotTranscript(params) {
78
+ const messages = params.messages.filter((message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult");
79
+ if (messages.length === 0) return;
80
+ const lock = await acquireSessionWriteLock({
81
+ sessionFile: params.sessionFile,
82
+ timeoutMs: resolveSessionWriteLockAcquireTimeoutMs(params.config)
83
+ });
84
+ try {
85
+ const existingIdempotencyKeys = await readTranscriptIdempotencyKeys(params.sessionFile);
86
+ for (const message of messages) {
87
+ const dedupeIdentity = buildMirrorDedupeIdentity(message);
88
+ const idempotencyKey = params.idempotencyScope ? `${params.idempotencyScope}:${dedupeIdentity}` : void 0;
89
+ if (idempotencyKey && existingIdempotencyKeys.has(idempotencyKey)) continue;
90
+ const nextMessage = runAgentHarnessBeforeMessageWriteHook({
91
+ message: {
92
+ ...message,
93
+ ...idempotencyKey ? { idempotencyKey } : {}
94
+ },
95
+ agentId: params.agentId,
96
+ sessionKey: params.sessionKey
97
+ });
98
+ if (!nextMessage) continue;
99
+ const messageToAppend = idempotencyKey ? {
100
+ ...nextMessage,
101
+ idempotencyKey
102
+ } : nextMessage;
103
+ await appendSessionTranscriptMessage({
104
+ transcriptPath: params.sessionFile,
105
+ message: messageToAppend,
106
+ config: params.config
107
+ });
108
+ if (idempotencyKey) existingIdempotencyKeys.add(idempotencyKey);
109
+ }
110
+ } finally {
111
+ await lock.release();
112
+ }
113
+ if (params.sessionKey) emitSessionTranscriptUpdate({
114
+ sessionFile: params.sessionFile,
115
+ sessionKey: params.sessionKey
116
+ });
117
+ else emitSessionTranscriptUpdate(params.sessionFile);
118
+ }
119
+ async function readTranscriptIdempotencyKeys(sessionFile) {
120
+ const keys = /* @__PURE__ */ new Set();
121
+ let raw;
122
+ try {
123
+ raw = await fsp.readFile(sessionFile, "utf8");
124
+ } catch (error) {
125
+ if (error.code !== "ENOENT") throw error;
126
+ return keys;
127
+ }
128
+ for (const line of raw.split(/\r?\n/)) {
129
+ if (!line.trim()) continue;
130
+ try {
131
+ const parsed = JSON.parse(line);
132
+ if (typeof parsed.message?.idempotencyKey === "string") keys.add(parsed.message.idempotencyKey);
133
+ } catch {
134
+ continue;
135
+ }
136
+ }
137
+ return keys;
138
+ }
139
+ /**
140
+ * Caller-side wrapper that swallows mirror failures. attempt.ts uses
141
+ * this so that a transient transcript-mirror failure (lock contention,
142
+ * disk full, etc.) never breaks an otherwise-successful attempt. The
143
+ * SDK's own session file remains the source of truth in that case;
144
+ * the OpenClaw audit trail just misses the intermediate messages for
145
+ * this turn.
146
+ */
147
+ async function dualWriteCopilotTranscriptBestEffort(params) {
148
+ try {
149
+ await mirrorCopilotTranscript(params);
150
+ } catch (error) {
151
+ console.warn("[copilot-attempt] dual-write transcript mirror failed", error);
152
+ }
153
+ }
154
+ //#endregion
155
+ //#region extensions/copilot/src/usage-bridge.ts
156
+ function isCopilotUsageSource(data) {
157
+ return typeof data === "object" && data !== null;
158
+ }
159
+ function buildZeroCost() {
160
+ return {
161
+ cacheRead: 0,
162
+ cacheWrite: 0,
163
+ input: 0,
164
+ output: 0,
165
+ total: 0
166
+ };
167
+ }
168
+ function coerceTokenCount(value) {
169
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : void 0;
170
+ }
171
+ function normalizeCopilotUsage(data) {
172
+ if (!isCopilotUsageSource(data)) return;
173
+ const input = coerceTokenCount(data.inputTokens);
174
+ const output = coerceTokenCount(data.outputTokens);
175
+ const cacheRead = coerceTokenCount(data.cacheReadTokens);
176
+ const cacheWrite = coerceTokenCount(data.cacheWriteTokens);
177
+ return {
178
+ cacheRead,
179
+ cacheWrite,
180
+ input,
181
+ output,
182
+ total: (input ?? 0) + (output ?? 0) + (cacheRead ?? 0) + (cacheWrite ?? 0)
183
+ };
184
+ }
185
+ function buildCopilotAssistantUsage(params) {
186
+ const usage = params.usage ?? normalizeCopilotUsage({ outputTokens: params.fallbackOutputTokens });
187
+ return {
188
+ cacheRead: usage?.cacheRead ?? 0,
189
+ cacheWrite: usage?.cacheWrite ?? 0,
190
+ cost: buildZeroCost(),
191
+ input: usage?.input ?? 0,
192
+ output: usage?.output ?? 0,
193
+ totalTokens: usage?.total ?? 0
194
+ };
195
+ }
196
+ //#endregion
197
+ //#region extensions/copilot/src/event-bridge.ts
198
+ function attachEventBridge(session, options) {
199
+ const messageOrder = [];
200
+ const messagesById = /* @__PURE__ */ new Map();
201
+ const reasoningOrder = [];
202
+ const reasoningById = /* @__PURE__ */ new Map();
203
+ let lastAssistantEvent;
204
+ let usage;
205
+ let streamError;
206
+ const toolMetas = [];
207
+ const toolNamesByCallId = /* @__PURE__ */ new Map();
208
+ let startedCount = 0;
209
+ let completedCount = 0;
210
+ let deltaQueue = Promise.resolve();
211
+ let deltaChain = Promise.resolve();
212
+ let firstDeltaError;
213
+ let detached = false;
214
+ const unsubscribeFns = [];
215
+ registerListener(session, unsubscribeFns, "assistant.message_delta", (event) => {
216
+ const messageId = readString$2(event.data.messageId) ?? "assistant-message";
217
+ const delta = event.data.deltaContent;
218
+ if (!delta) return;
219
+ const entry = ensureMessageAccumulator(messagesById, messageOrder, messageId);
220
+ entry.text += delta;
221
+ const onAssistantDelta = options.onAssistantDelta;
222
+ if (!onAssistantDelta) return;
223
+ const payload = {
224
+ delta,
225
+ sessionId: options.getSdkSessionId(),
226
+ text: entry.text,
227
+ usage
228
+ };
229
+ deltaQueue = deltaQueue.then(() => onAssistantDelta(payload), () => onAssistantDelta(payload)).catch((error) => {
230
+ firstDeltaError ??= error;
231
+ });
232
+ deltaChain = deltaQueue.then(() => {
233
+ if (firstDeltaError !== void 0) throw firstDeltaError;
234
+ });
235
+ deltaChain.catch(() => void 0);
236
+ });
237
+ registerListener(session, unsubscribeFns, "assistant.reasoning_delta", (event) => {
238
+ const reasoningId = readString$2(event.data.reasoningId) ?? "assistant-reasoning";
239
+ const delta = event.data.deltaContent;
240
+ if (!delta) return;
241
+ if (!reasoningById.has(reasoningId)) {
242
+ reasoningById.set(reasoningId, "");
243
+ reasoningOrder.push(reasoningId);
244
+ }
245
+ reasoningById.set(reasoningId, `${reasoningById.get(reasoningId) ?? ""}${delta}`);
246
+ });
247
+ registerListener(session, unsubscribeFns, "assistant.message", (event) => {
248
+ lastAssistantEvent = event;
249
+ const entry = ensureMessageAccumulator(messagesById, messageOrder, event.data.messageId);
250
+ if (typeof event.data.content === "string" && event.data.content.length >= entry.text.length) entry.text = event.data.content;
251
+ });
252
+ registerListener(session, unsubscribeFns, "assistant.usage", (event) => {
253
+ usage = normalizeCopilotUsage(event.data);
254
+ });
255
+ registerListener(session, unsubscribeFns, "tool.execution_start", (event) => {
256
+ startedCount += 1;
257
+ toolNamesByCallId.set(event.data.toolCallId, event.data.toolName);
258
+ toolMetas.push({ toolName: event.data.toolName });
259
+ });
260
+ registerListener(session, unsubscribeFns, "tool.execution_complete", (event) => {
261
+ completedCount += 1;
262
+ const toolName = toolNamesByCallId.get(event.data.toolCallId);
263
+ const meta = event.data.success ? event.data.result?.detailedContent ?? event.data.result?.content : event.data.error?.message;
264
+ if (toolName) toolMetas.push({
265
+ meta,
266
+ toolName
267
+ });
268
+ });
269
+ registerListener(session, unsubscribeFns, "session.error", (event) => {
270
+ if (!options.isAborted()) streamError = createPromptError$1(event.data.errorCode ?? event.data.errorType, event.data.message);
271
+ });
272
+ registerListener(session, unsubscribeFns, "abort", (event) => {
273
+ if (!options.isAborted()) streamError = createPromptError$1("session_aborted", `[copilot-attempt] session aborted: ${event.data.reason}`);
274
+ });
275
+ return {
276
+ recordSendResult(result) {
277
+ if (!isAssistantMessageEvent(result)) return false;
278
+ lastAssistantEvent = result;
279
+ return true;
280
+ },
281
+ awaitDeltaChain() {
282
+ return deltaChain;
283
+ },
284
+ snapshot() {
285
+ return {
286
+ assistantTexts: finalizeAssistantTexts(messageOrder, messagesById, lastAssistantEvent),
287
+ completedCount,
288
+ lastAssistantEvent,
289
+ startedCount,
290
+ streamError,
291
+ toolMetas: toolMetas.map((toolMeta) => Object.assign({}, toolMeta)),
292
+ usage: usage ? { ...usage } : void 0
293
+ };
294
+ },
295
+ buildAssistantMessage(args) {
296
+ return buildAssistantMessage({
297
+ event: lastAssistantEvent,
298
+ modelRef: args.modelRef,
299
+ now: args.now,
300
+ reasoningById,
301
+ reasoningOrder,
302
+ usage,
303
+ assistantTexts: finalizeAssistantTexts(messageOrder, messagesById, lastAssistantEvent)
304
+ });
305
+ },
306
+ finalizeAssistantTexts() {
307
+ return finalizeAssistantTexts(messageOrder, messagesById, lastAssistantEvent);
308
+ },
309
+ detach() {
310
+ if (detached) return;
311
+ detached = true;
312
+ for (const unsubscribe of [...unsubscribeFns].toReversed()) try {
313
+ unsubscribe();
314
+ } catch {}
315
+ unsubscribeFns.length = 0;
316
+ }
317
+ };
318
+ }
319
+ function buildAssistantMessage(params) {
320
+ const event = params.event;
321
+ const text = event ? event.data.content || params.assistantTexts[params.assistantTexts.length - 1] || "" : "";
322
+ const reasoningText = event?.data.reasoningText ?? joinReasoning(params.reasoningOrder, params.reasoningById);
323
+ const toolRequests = event?.data.toolRequests ?? [];
324
+ if (!text && !reasoningText && toolRequests.length === 0) return;
325
+ const content = [];
326
+ if (reasoningText) content.push({
327
+ thinking: reasoningText,
328
+ type: "thinking"
329
+ });
330
+ if (text) content.push({
331
+ text,
332
+ type: "text"
333
+ });
334
+ for (const request of toolRequests) content.push({
335
+ arguments: request.arguments ?? {},
336
+ id: request.toolCallId,
337
+ name: request.name,
338
+ type: "toolCall"
339
+ });
340
+ return {
341
+ api: params.modelRef.api ?? "openai-responses",
342
+ content,
343
+ model: event?.data.model ?? params.modelRef.id,
344
+ provider: params.modelRef.provider,
345
+ role: "assistant",
346
+ stopReason: toolRequests.length > 0 ? "toolUse" : "stop",
347
+ timestamp: params.now(),
348
+ usage: buildCopilotAssistantUsage({
349
+ fallbackOutputTokens: event?.data.outputTokens,
350
+ usage: params.usage
351
+ })
352
+ };
353
+ }
354
+ function createPromptError$1(code, message, cause) {
355
+ const error = new Error(message);
356
+ error.code = code;
357
+ if (cause !== void 0) error.cause = cause;
358
+ return error;
359
+ }
360
+ function ensureMessageAccumulator(messagesById, messageOrder, messageId) {
361
+ let entry = messagesById.get(messageId);
362
+ if (!entry) {
363
+ entry = {
364
+ messageId,
365
+ text: ""
366
+ };
367
+ messagesById.set(messageId, entry);
368
+ messageOrder.push(messageId);
369
+ }
370
+ return entry;
371
+ }
372
+ function finalizeAssistantTexts(messageOrder, messagesById, event) {
373
+ const texts = messageOrder.map((messageId) => messagesById.get(messageId)?.text ?? "").filter((text) => text.length > 0);
374
+ if (texts.length > 0) return texts;
375
+ if (event?.data.content) return [event.data.content];
376
+ return [];
377
+ }
378
+ function isAssistantMessageEvent(event) {
379
+ return event?.type === "assistant.message";
380
+ }
381
+ function joinReasoning(order, reasoningById) {
382
+ return order.map((reasoningId) => reasoningById.get(reasoningId) ?? "").join("");
383
+ }
384
+ function readString$2(value) {
385
+ return typeof value === "string" && value.length > 0 ? value : void 0;
386
+ }
387
+ function registerListener(session, unsubscribeFns, eventType, handler) {
388
+ const maybeUnsubscribe = session.on(eventType, handler);
389
+ if (typeof maybeUnsubscribe === "function") {
390
+ unsubscribeFns.push(maybeUnsubscribe);
391
+ return;
392
+ }
393
+ unsubscribeFns.push(() => {
394
+ session.off?.(eventType, handler);
395
+ });
396
+ }
397
+ //#endregion
398
+ //#region extensions/copilot/src/hooks-bridge.ts
399
+ const DEFAULT_HOOK_ERROR_HANDLER = ({ hookName, error }) => {
400
+ console.warn(`[copilot hooks-bridge] ${hookName} handler threw:`, error);
401
+ };
402
+ /**
403
+ * Wrap a host handler in an error-isolating envelope so it cannot
404
+ * throw out into the SDK. Returns `undefined` (no opinion) when the
405
+ * host handler throws, so the SDK falls back to its default behaviour
406
+ * for that hook.
407
+ */
408
+ function isolate(hookName, handler, onError) {
409
+ if (!handler) return;
410
+ return async (...args) => {
411
+ try {
412
+ return await handler(...args);
413
+ } catch (error) {
414
+ try {
415
+ onError({
416
+ hookName,
417
+ error
418
+ });
419
+ } catch {}
420
+ return;
421
+ }
422
+ };
423
+ }
424
+ /**
425
+ * Build an SDK-shaped `SessionHooks` object from a host-supplied
426
+ * `CopilotHooksConfig`. Returns `undefined` when no handlers were
427
+ * supplied so the SDK skips the hook subsystem entirely.
428
+ */
429
+ function createHooksBridge(config) {
430
+ if (!config) return;
431
+ const onError = config.onHookError ?? DEFAULT_HOOK_ERROR_HANDLER;
432
+ const hooks = {};
433
+ const pre = isolate("onPreToolUse", config.onPreToolUse, onError);
434
+ const post = isolate("onPostToolUse", config.onPostToolUse, onError);
435
+ const userPrompt = isolate("onUserPromptSubmitted", config.onUserPromptSubmitted, onError);
436
+ const sessionStart = isolate("onSessionStart", config.onSessionStart, onError);
437
+ const sessionEnd = isolate("onSessionEnd", config.onSessionEnd, onError);
438
+ const errorOccurred = isolate("onErrorOccurred", config.onErrorOccurred, onError);
439
+ if (pre) hooks.onPreToolUse = pre;
440
+ if (post) hooks.onPostToolUse = post;
441
+ if (userPrompt) hooks.onUserPromptSubmitted = userPrompt;
442
+ if (sessionStart) hooks.onSessionStart = sessionStart;
443
+ if (sessionEnd) hooks.onSessionEnd = sessionEnd;
444
+ if (errorOccurred) hooks.onErrorOccurred = errorOccurred;
445
+ if (Object.keys(hooks).length === 0) return;
446
+ return hooks;
447
+ }
448
+ //#endregion
449
+ //#region extensions/copilot/src/permission-bridge.ts
450
+ /** Built-in fail-closed default. Mirrors the pre-bridge attempt.ts stub. */
451
+ const REJECT_ALL_FEEDBACK = "copilot agent runtime: no permission policy installed (fail-closed default)";
452
+ const rejectAllPolicy = () => ({
453
+ kind: "reject",
454
+ feedback: REJECT_ALL_FEEDBACK
455
+ });
456
+ /**
457
+ * Adapt a `CopilotPermissionPolicy` to the SDK's
458
+ * `PermissionHandler` shape. The returned handler always resolves
459
+ * (never rejects), defaulting to fail-closed when the policy returns
460
+ * undefined or throws.
461
+ */
462
+ function createPermissionBridge(policy = rejectAllPolicy) {
463
+ return async (request, invocation) => {
464
+ const ctx = {
465
+ request,
466
+ sessionId: invocation.sessionId
467
+ };
468
+ try {
469
+ const result = await policy(ctx);
470
+ if (result !== void 0) return result;
471
+ } catch (error) {
472
+ return {
473
+ kind: "reject",
474
+ feedback: `copilot permission policy threw: ${formatError(error)}`
475
+ };
476
+ }
477
+ return {
478
+ kind: "reject",
479
+ feedback: REJECT_ALL_FEEDBACK
480
+ };
481
+ };
482
+ }
483
+ function formatError(error) {
484
+ if (error instanceof Error) return error.message;
485
+ try {
486
+ return JSON.stringify(error);
487
+ } catch {
488
+ return String(error);
489
+ }
490
+ }
491
+ //#endregion
492
+ //#region extensions/copilot/src/replay-shim.ts
493
+ function normalizeSdkSessionId(value) {
494
+ if (typeof value !== "string") return;
495
+ const trimmed = value.trim();
496
+ return trimmed.length > 0 ? trimmed : void 0;
497
+ }
498
+ /**
499
+ * Pure pre-call decision: should attempt.ts call resumeSession or
500
+ * createSession?
501
+ *
502
+ * Rules:
503
+ * - No input → create (no-replay-state)
504
+ * - No (trimmed) sdkSessionId → create (no-sdk-session-id)
505
+ * - sdkSessionId + replayInvalid=true → create (replay-invalid),
506
+ * downgradedFromResume=true
507
+ * - sdkSessionId + replayInvalid=false → resume
508
+ */
509
+ function decideReplayAction(input) {
510
+ if (!input) return {
511
+ action: "create",
512
+ downgradedFromResume: false,
513
+ downgradeReason: "no-replay-state"
514
+ };
515
+ const sdkSessionId = normalizeSdkSessionId(input.sdkSessionId);
516
+ if (!sdkSessionId) return {
517
+ action: "create",
518
+ downgradedFromResume: false,
519
+ downgradeReason: "no-sdk-session-id"
520
+ };
521
+ if (input.replayInvalid === true) return {
522
+ action: "create",
523
+ downgradedFromResume: true,
524
+ downgradeReason: "replay-invalid"
525
+ };
526
+ return {
527
+ action: "resume",
528
+ sdkSessionId,
529
+ downgradedFromResume: false
530
+ };
531
+ }
532
+ const MISSING_SESSION_CODES = new Set([
533
+ "SESSION_NOT_FOUND",
534
+ "session_not_found",
535
+ "NotFound",
536
+ "ENOENT"
537
+ ]);
538
+ const MISSING_SESSION_MESSAGE_PATTERNS = [
539
+ /\bsession not found\b/i,
540
+ /\bsession .* not found\b/i,
541
+ /\bunknown session id\b/i,
542
+ /\bsession id .* (does not exist|not found)\b/i,
543
+ /\bsession .* does not exist\b/i,
544
+ /\bno such session\b/i
545
+ ];
546
+ function readErrorField(error, key) {
547
+ if (!error || typeof error !== "object") return;
548
+ return error[key];
549
+ }
550
+ /**
551
+ * Post-call: classify a resumeSession() failure so attempt.ts can
552
+ * decide whether to downgrade silently to createSession.
553
+ *
554
+ * Conservative: only treats clearly session-gone signals as recoverable.
555
+ * Structured signals (status === 404, recognised code strings) are
556
+ * checked first; message matching is a fallback because SDK error
557
+ * messages are not part of the typed contract.
558
+ *
559
+ * Everything else (transport errors, auth failures, generic Error) is
560
+ * unrecoverable and should surface to the outer attempt.ts try/catch
561
+ * which converts it to a prompt error.
562
+ */
563
+ function classifyResumeFailure(error) {
564
+ if (error === void 0 || error === null) return {
565
+ recoverable: false,
566
+ kind: "unknown"
567
+ };
568
+ if (readErrorField(error, "status") === 404) return {
569
+ recoverable: true,
570
+ kind: "missing"
571
+ };
572
+ if (readErrorField(error, "statusCode") === 404) return {
573
+ recoverable: true,
574
+ kind: "missing"
575
+ };
576
+ const code = readErrorField(error, "code");
577
+ if (typeof code === "string" && MISSING_SESSION_CODES.has(code)) return {
578
+ recoverable: true,
579
+ kind: "missing"
580
+ };
581
+ const message = error instanceof Error ? error.message : typeof error === "object" ? typeof error.message === "string" ? error.message : void 0 : void 0;
582
+ if (typeof message === "string") {
583
+ for (const pattern of MISSING_SESSION_MESSAGE_PATTERNS) if (pattern.test(message)) return {
584
+ recoverable: true,
585
+ kind: "missing"
586
+ };
587
+ }
588
+ return {
589
+ recoverable: false,
590
+ kind: "unknown"
591
+ };
592
+ }
593
+ /**
594
+ * Compute the `EmbeddedRunReplayMetadata` to attach to the attempt
595
+ * result. Worst-case-wins:
596
+ *
597
+ * hadPotentialSideEffects = priorHadPotentialSideEffects OR timedOut
598
+ * OR thisAttemptHadPotentialSideEffects
599
+ * (timeout means we cannot prove the prompt was not partially
600
+ * committed server-side; treat as side-effecting so the
601
+ * orchestrator will not blindly re-issue the same prompt).
602
+ *
603
+ * replaySafe = NOT (
604
+ * priorReplayInvalid
605
+ * OR thisAttemptDowngradedFromResume
606
+ * OR thisAttemptResumeFailureRecovered
607
+ * OR hadPotentialSideEffects
608
+ * )
609
+ *
610
+ * Matches the parity rule in
611
+ * `src/agents/pi-embedded-runner/replay-state.ts#replayMetadataFromState`.
612
+ */
613
+ function computeReplayMetadata(input) {
614
+ const priorReplayInvalid = input.priorReplayInvalid === true;
615
+ const priorHadPotentialSideEffects = input.priorHadPotentialSideEffects === true;
616
+ const timedOut = input.thisAttemptTimedOut === true;
617
+ const thisAttemptHadPotentialSideEffects = input.thisAttemptHadPotentialSideEffects === true;
618
+ const downgraded = input.thisAttemptDowngradedFromResume === true;
619
+ const recovered = input.thisAttemptResumeFailureRecovered === true;
620
+ const hadPotentialSideEffects = priorHadPotentialSideEffects || timedOut || thisAttemptHadPotentialSideEffects;
621
+ return {
622
+ hadPotentialSideEffects,
623
+ replaySafe: !(priorReplayInvalid || downgraded || recovered || hadPotentialSideEffects)
624
+ };
625
+ }
626
+ const COPILOT_REPLAY_SAFE_READ_ONLY_TOOL_NAMES = new Set([
627
+ "get",
628
+ "file_read",
629
+ "glob",
630
+ "grep",
631
+ "inspect",
632
+ "list",
633
+ "ls",
634
+ "memory_get",
635
+ "memory_search",
636
+ "probe",
637
+ "query",
638
+ "read",
639
+ "search",
640
+ "sessions_history",
641
+ "sessions_list",
642
+ "status",
643
+ "tool_search",
644
+ "update_plan",
645
+ "view",
646
+ "web_fetch",
647
+ "web_search"
648
+ ]);
649
+ function copilotToolMetasHavePotentialSideEffects(toolMetas) {
650
+ return (toolMetas ?? []).some((entry) => entry.asyncStarted === true || !isReplaySafeReadOnlyToolName(entry.toolName));
651
+ }
652
+ function isReplaySafeReadOnlyToolName(toolName) {
653
+ const normalized = toolName.trim().toLowerCase();
654
+ return COPILOT_REPLAY_SAFE_READ_ONLY_TOOL_NAMES.has(normalized);
655
+ }
656
+ //#endregion
657
+ //#region extensions/copilot/src/tool-bridge.ts
658
+ const SUPPORTED_TOOL_PROVIDERS = new Set(["github-copilot"]);
659
+ const BASE_COPILOT_CODING_TOOL_NAMES = new Set([
660
+ "edit",
661
+ "read",
662
+ "write"
663
+ ]);
664
+ const SHELL_COPILOT_CODING_TOOL_NAMES = new Set([
665
+ "apply_patch",
666
+ "exec",
667
+ "process"
668
+ ]);
669
+ function supportsModelTools(modelProvider) {
670
+ return SUPPORTED_TOOL_PROVIDERS.has(modelProvider);
671
+ }
672
+ async function createCopilotToolBridge(input) {
673
+ if (!supportsModelTools(input.modelProvider)) return {
674
+ sdkTools: [],
675
+ sourceTools: []
676
+ };
677
+ const attemptParams = input.attemptParams ?? {};
678
+ const toolPlan = resolveEmbeddedAttemptToolConstructionPlan({
679
+ disableTools: attemptParams.disableTools,
680
+ forceMessageTool: shouldForceCopilotMessageTool(attemptParams),
681
+ isRawModelRun: isCopilotRawModelRun(attemptParams),
682
+ toolsAllow: attemptParams.toolsAllow
683
+ });
684
+ const effectiveToolPlan = hasNonWildcardGlobAllowlist(toolPlan.runtimeToolAllowlist) ? {
685
+ ...toolPlan,
686
+ codingToolConstructionPlan: {
687
+ includeBaseCodingTools: true,
688
+ includeChannelTools: true,
689
+ includeOpenClawTools: true,
690
+ includePluginTools: true,
691
+ includeShellTools: true
692
+ },
693
+ constructTools: true,
694
+ includeCoreTools: true
695
+ } : toolPlan;
696
+ if (!effectiveToolPlan.constructTools) return {
697
+ sdkTools: [],
698
+ sourceTools: []
699
+ };
700
+ const createOpenClawCodingTools = input.createOpenClawCodingTools ?? (await import("openclaw/plugin-sdk/agent-harness")).createOpenClawCodingTools;
701
+ const toolOptions = buildOpenClawCodingToolsOptions(input, effectiveToolPlan);
702
+ let sourceTools;
703
+ try {
704
+ sourceTools = await createOpenClawCodingTools(toolOptions);
705
+ } catch (error) {
706
+ throw createError(`[copilot-tool-bridge] createOpenClawCodingTools failed: ${toError$1(error).message}`, error);
707
+ }
708
+ if (!Array.isArray(sourceTools)) throw new Error("[copilot-tool-bridge] createOpenClawCodingTools must return an array of tools");
709
+ const filteredTools = filterCopilotToolsForAllowlist(filterCopilotToolsForConstructionPlan(sourceTools, effectiveToolPlan.codingToolConstructionPlan), effectiveToolPlan.runtimeToolAllowlist);
710
+ const duplicateNames = findDuplicateToolNames(filteredTools);
711
+ if (duplicateNames.length > 0) throw new Error(`[copilot-tool-bridge] duplicate tool names: ${duplicateNames.join(", ")}`);
712
+ return {
713
+ sdkTools: filteredTools.map((sourceTool) => convertOpenClawToolToSdkTool(sourceTool, {
714
+ abortSignal: input.abortSignal,
715
+ beforeExecute: input.beforeExecute
716
+ })),
717
+ sourceTools: filteredTools
718
+ };
719
+ }
720
+ /**
721
+ * Builds the full `createOpenClawCodingTools` options bag mirroring the
722
+ * PI in-tree call at `src/agents/pi-embedded-runner/run/attempt.ts:1029-1117`.
723
+ *
724
+ * Why PI parity matters: bridged OpenClaw tools register with the SDK
725
+ * as `overridesBuiltInTool: true, skipPermission: true` (see
726
+ * `convertOpenClawToolToSdkTool` below). That means the wrapped-tool
727
+ * enforcement layer
728
+ * (`src/agents/pi-tools.before-tool-call.ts → wrapToolWithBeforeToolCallHook`)
729
+ * is the single gate for permission, owner-only allowlists, loop
730
+ * detection, trusted-plugin policies, and two-phase plugin approvals.
731
+ * That layer reads its context from the fields forwarded here; missing
732
+ * fields silently degrade policy decisions. See docs/plugins/copilot.md.
733
+ *
734
+ * The shared embedded-runner tool plan is forwarded so the bridge does
735
+ * not construct broad tool families only to filter them later. That
736
+ * preserves PI allowlist semantics such as `write` not materializing
737
+ * `apply_patch`.
738
+ * Sandbox is forwarded via the explicit `sandbox` field on
739
+ * {@link CopilotToolBridgeInput}; callers resolve it via
740
+ * `resolveSandboxContext` before constructing the bridge.
741
+ */
742
+ function buildOpenClawCodingToolsOptions(input, toolPlan) {
743
+ const a = input.attemptParams ?? {};
744
+ const sandboxSessionKey = a.sandboxSessionKey?.trim() || a.sessionKey?.trim() || input.sessionKey || input.sessionId;
745
+ const liveSessionKey = a.sessionKey ?? input.sessionKey;
746
+ const runSessionKey = liveSessionKey && liveSessionKey !== sandboxSessionKey ? liveSessionKey : void 0;
747
+ const workspaceDir = input.workspaceDir ?? a.workspaceDir;
748
+ const cwd = input.cwd ?? a.cwd;
749
+ const agentDir = input.agentDir ?? a.agentDir;
750
+ const sandbox = input.sandbox ?? void 0;
751
+ const spawnWorkspaceDir = input.spawnWorkspaceDir ?? (workspaceDir ? resolveAttemptSpawnWorkspaceDir({
752
+ sandbox,
753
+ resolvedWorkspace: workspaceDir
754
+ }) : void 0);
755
+ const model = a.model;
756
+ const modelHasVision = Array.isArray(model?.input) && model.input.includes("image");
757
+ const modelCompat = model && typeof model === "object" && "compat" in model && model.compat && typeof model.compat === "object" ? model.compat : void 0;
758
+ return {
759
+ agentId: input.agentId,
760
+ ...buildEmbeddedAttemptToolRunContext({
761
+ trigger: a.trigger,
762
+ jobId: a.jobId,
763
+ memoryFlushWritePath: a.memoryFlushWritePath,
764
+ toolsAllow: a.toolsAllow
765
+ }),
766
+ exec: {
767
+ ...a.execOverrides,
768
+ elevated: a.bashElevated
769
+ },
770
+ messageProvider: a.messageProvider ?? a.messageChannel,
771
+ agentAccountId: a.agentAccountId,
772
+ messageTo: a.messageTo,
773
+ messageThreadId: a.messageThreadId,
774
+ groupId: a.groupId,
775
+ groupChannel: a.groupChannel,
776
+ groupSpace: a.groupSpace,
777
+ memberRoleIds: a.memberRoleIds,
778
+ spawnedBy: a.spawnedBy,
779
+ senderId: a.senderId,
780
+ senderName: a.senderName,
781
+ senderUsername: a.senderUsername,
782
+ senderE164: a.senderE164,
783
+ senderIsOwner: a.senderIsOwner,
784
+ allowGatewaySubagentBinding: a.allowGatewaySubagentBinding,
785
+ sessionKey: sandboxSessionKey,
786
+ runSessionKey,
787
+ sessionId: input.sessionId,
788
+ runId: a.runId,
789
+ agentDir,
790
+ workspaceDir,
791
+ cwd,
792
+ sandbox,
793
+ spawnWorkspaceDir,
794
+ config: a.config,
795
+ abortSignal: input.abortSignal,
796
+ modelProvider: input.modelProvider,
797
+ modelId: input.modelId,
798
+ includeCoreTools: toolPlan.includeCoreTools,
799
+ runtimeToolAllowlist: toolPlan.runtimeToolAllowlist,
800
+ toolConstructionPlan: toolPlan.codingToolConstructionPlan,
801
+ modelCompat,
802
+ modelApi: model?.api,
803
+ modelContextWindowTokens: model?.contextWindow,
804
+ modelAuthMode: resolveModelAuthMode(input.modelProvider, a.config, void 0, { workspaceDir }),
805
+ currentChannelId: a.currentChannelId,
806
+ currentThreadTs: a.currentThreadTs,
807
+ currentMessageId: a.currentMessageId,
808
+ replyToMode: a.replyToMode,
809
+ hasRepliedRef: a.hasRepliedRef,
810
+ modelHasVision,
811
+ requireExplicitMessageTarget: a.requireExplicitMessageTarget ?? isSubagentSessionKey(liveSessionKey),
812
+ sourceReplyDeliveryMode: a.sourceReplyDeliveryMode,
813
+ disableMessageTool: a.disableMessageTool,
814
+ forceMessageTool: a.forceMessageTool,
815
+ enableHeartbeatTool: a.enableHeartbeatTool,
816
+ forceHeartbeatTool: a.forceHeartbeatTool,
817
+ authProfileStore: a.toolAuthProfileStore ?? a.authProfileStore,
818
+ onToolOutcome: a.onToolOutcome,
819
+ onYield: (message) => {
820
+ try {
821
+ input.onYieldDetected?.(message);
822
+ } catch (error) {
823
+ console.warn("[copilot-tool-bridge] onYieldDetected handler threw; continuing", error);
824
+ }
825
+ (input.sessionRef?.current)?.abort?.();
826
+ }
827
+ };
828
+ }
829
+ function convertOpenClawToolToSdkTool(sourceTool, ctx) {
830
+ if (typeof sourceTool.name !== "string" || sourceTool.name.trim().length === 0) throw new Error("[copilot-tool-bridge] tool name must be a non-empty string");
831
+ if (typeof sourceTool.execute !== "function") throw new Error(`[copilot-tool-bridge] tool '${sourceTool.name}' must define an execute function`);
832
+ let sequentialLock = Promise.resolve();
833
+ const executeOnce = async (args, invocation) => {
834
+ if (ctx.abortSignal?.aborted) {
835
+ const error = /* @__PURE__ */ new Error("[copilot-tool-bridge] aborted before execution");
836
+ return createFailureResult(error.message, error);
837
+ }
838
+ try {
839
+ await ctx.beforeExecute?.({
840
+ args,
841
+ invocation,
842
+ sourceTool,
843
+ toolCallId: invocation.toolCallId,
844
+ toolName: sourceTool.name
845
+ });
846
+ } catch (error) {
847
+ return createFailureResult(`[copilot-tool-bridge] beforeExecute failed for tool '${sourceTool.name}': ${toError$1(error).message}`, error);
848
+ }
849
+ let preparedArgs = args;
850
+ try {
851
+ preparedArgs = sourceTool.prepareArguments ? sourceTool.prepareArguments(args) : args;
852
+ } catch (error) {
853
+ return createFailureResult(`[copilot-tool-bridge] prepareArguments failed for tool '${sourceTool.name}': ${toError$1(error).message}`, error);
854
+ }
855
+ let result;
856
+ try {
857
+ result = await sourceTool.execute(invocation.toolCallId, preparedArgs, ctx.abortSignal, void 0);
858
+ } catch (error) {
859
+ return createFailureResult(`[copilot-tool-bridge] tool '${sourceTool.name}' failed: ${toError$1(error).message}`, error);
860
+ }
861
+ return agentToolResultToSdk(result);
862
+ };
863
+ const handler = sourceTool.executionMode === "sequential" ? (args, invocation) => {
864
+ const run = sequentialLock.then(() => executeOnce(args, invocation), () => executeOnce(args, invocation));
865
+ sequentialLock = run.then(() => void 0, () => void 0);
866
+ return run;
867
+ } : executeOnce;
868
+ return {
869
+ description: sourceTool.description,
870
+ handler,
871
+ name: sourceTool.name,
872
+ overridesBuiltInTool: true,
873
+ parameters: sourceTool.parameters,
874
+ skipPermission: true
875
+ };
876
+ }
877
+ function agentToolResultToSdk(result) {
878
+ const content = result?.content;
879
+ if (content == null) return createSuccessResult("");
880
+ if (!Array.isArray(content)) return createUnsupportedContentFailure(typeof content);
881
+ const textParts = [];
882
+ const binaryResults = [];
883
+ for (const block of content) {
884
+ if (!block || typeof block !== "object") return createUnsupportedContentFailure(typeof block);
885
+ const kind = readString$1(block.type);
886
+ if (kind === "text") {
887
+ const text = readString$1(block.text, { allowEmpty: true });
888
+ if (text === void 0) return createUnsupportedContentFailure(kind);
889
+ textParts.push(text);
890
+ continue;
891
+ }
892
+ if (kind === "image") {
893
+ const base64Data = readString$1(block.data);
894
+ const mimeType = readString$1(block.mimeType);
895
+ if (!base64Data || !mimeType) return createUnsupportedContentFailure(kind);
896
+ binaryResults.push({
897
+ base64Data,
898
+ data: base64Data,
899
+ mimeType,
900
+ type: "image"
901
+ });
902
+ continue;
903
+ }
904
+ return createUnsupportedContentFailure(kind ?? typeof block);
905
+ }
906
+ return {
907
+ ...binaryResults.length > 0 ? { binaryResultsForLlm: binaryResults } : {},
908
+ resultType: "success",
909
+ textResultForLlm: textParts.join("\n")
910
+ };
911
+ }
912
+ function createUnsupportedContentFailure(kind) {
913
+ const message = `[copilot-tool-bridge] unsupported AgentToolResult content shape: ${kind}`;
914
+ return createFailureResult(message, new Error(message));
915
+ }
916
+ function createSuccessResult(textResultForLlm) {
917
+ return {
918
+ resultType: "success",
919
+ textResultForLlm
920
+ };
921
+ }
922
+ function createFailureResult(message, error) {
923
+ return {
924
+ error: toError$1(error).message,
925
+ resultType: "failure",
926
+ textResultForLlm: message
927
+ };
928
+ }
929
+ function createError(message, cause) {
930
+ const error = new Error(message);
931
+ error.cause = cause;
932
+ return error;
933
+ }
934
+ /**
935
+ * Returns true when the attempt was launched as a raw-model run, which
936
+ * suppresses tool construction in PI
937
+ * (`src/agents/pi-embedded-runner/run/attempt.ts:1305-1310` and
938
+ * `attempt-tool-construction-plan.ts:165-184`). A run is raw when the
939
+ * caller explicitly sets `modelRun: true` or asks for no system prompt
940
+ * via `promptMode: "none"`.
941
+ */
942
+ function isCopilotRawModelRun(params) {
943
+ return params.modelRun === true || params.promptMode === "none";
944
+ }
945
+ /**
946
+ * Mirrors PI's `shouldForceMessageTool` semantics: a message tool is
947
+ * forced when the caller asked for it explicitly or when the source
948
+ * reply delivery mode is `message_tool_only`, but never when
949
+ * `disableMessageTool` is set (the suppress flag always wins). Compare
950
+ * `src/agents/pi-embedded-runner/run/attempt.ts:1361-1366` and the
951
+ * codex equivalent at
952
+ * `extensions/codex/src/app-server/run-attempt.ts:4253-4258`.
953
+ */
954
+ function shouldForceCopilotMessageTool(params) {
955
+ if (params.disableMessageTool === true) return false;
956
+ return params.forceMessageTool === true || params.sourceReplyDeliveryMode === "message_tool_only";
957
+ }
958
+ /**
959
+ * Mirrors PI's `applyEmbeddedAttemptToolsAllow`
960
+ * (`src/agents/embedded-agent-runner/run/attempt-tool-construction-plan.ts`)
961
+ * so final filtering keeps aliases, groups, plugin policies, and glob
962
+ * semantics identical to the in-tree embedded runner.
963
+ */
964
+ function filterCopilotToolsForAllowlist(tools, toolsAllow) {
965
+ return applyEmbeddedAttemptToolsAllow(tools, toolsAllow, { toolMeta: (tool) => getPluginToolMeta(tool) ?? readInlinePluginToolMeta(tool) });
966
+ }
967
+ function filterCopilotToolsForConstructionPlan(tools, plan) {
968
+ if (plan.includeBaseCodingTools && plan.includeShellTools) return tools;
969
+ return tools.filter((tool) => {
970
+ if (!plan.includeBaseCodingTools && BASE_COPILOT_CODING_TOOL_NAMES.has(tool.name)) return false;
971
+ if (!plan.includeShellTools && SHELL_COPILOT_CODING_TOOL_NAMES.has(tool.name)) return false;
972
+ return true;
973
+ });
974
+ }
975
+ function hasNonWildcardGlobAllowlist(toolsAllow) {
976
+ return (toolsAllow ?? []).some((entry) => {
977
+ const trimmed = entry.trim();
978
+ return trimmed !== "*" && trimmed.includes("*");
979
+ });
980
+ }
981
+ function readInlinePluginToolMeta(tool) {
982
+ const pluginId = tool.pluginId;
983
+ return typeof pluginId === "string" && pluginId.trim() ? { pluginId } : void 0;
984
+ }
985
+ function findDuplicateToolNames(sourceTools) {
986
+ const counts = /* @__PURE__ */ new Map();
987
+ for (const sourceTool of sourceTools) {
988
+ if (typeof sourceTool.name !== "string" || sourceTool.name.length === 0) continue;
989
+ counts.set(sourceTool.name, (counts.get(sourceTool.name) ?? 0) + 1);
990
+ }
991
+ return [...counts.entries()].filter(([, count]) => count > 1).map(([name]) => name).toSorted();
992
+ }
993
+ function readString$1(value, options = {}) {
994
+ if (typeof value !== "string") return;
995
+ if (options.allowEmpty || value.length > 0) return value;
996
+ }
997
+ function toError$1(error) {
998
+ return error instanceof Error ? error : new Error(String(error));
999
+ }
1000
+ //#endregion
1001
+ //#region extensions/copilot/src/workspace-bootstrap.ts
1002
+ const COPILOT_NATIVE_PROJECT_DOC_BASENAMES = new Set(["agents.md"]);
1003
+ const COPILOT_BOOTSTRAP_CONTEXT_ORDER = new Map([
1004
+ ["soul.md", 10],
1005
+ ["identity.md", 20],
1006
+ ["heartbeat.md", 30],
1007
+ ["bootstrap.md", 40],
1008
+ ["tools.md", 50],
1009
+ ["user.md", 60],
1010
+ ["memory.md", 70]
1011
+ ]);
1012
+ /**
1013
+ * Loads OpenClaw workspace bootstrap files (IDENTITY.md, SOUL.md,
1014
+ * HEARTBEAT.md, USER.md, TOOLS.md, BOOTSTRAP.md, MEMORY.md, ...) using
1015
+ * the shared core helper PI and codex both use, then renders them as a
1016
+ * single string suitable for `SessionConfig.systemMessage.content` on
1017
+ * the Copilot SDK.
1018
+ *
1019
+ * Returns `instructions: undefined` when there are no relevant files
1020
+ * (after filtering out SDK-native docs) so the caller can omit the
1021
+ * `systemMessage` field entirely rather than passing an empty string.
1022
+ *
1023
+ * Mirrors codex's `buildCodexWorkspaceBootstrapContext` /
1024
+ * `renderCodexWorkspaceBootstrapInstructions` pair
1025
+ * (`extensions/codex/src/app-server/run-attempt.ts:2877,3047`). The
1026
+ * shape divergence — codex returns instructions inside the same object
1027
+ * as bootstrapFiles+contextFiles for its developerInstructions field;
1028
+ * copilot exposes the rendered string for SDK `systemMessage` — is the
1029
+ * intended difference between the two runtimes' system-prompt
1030
+ * surfaces.
1031
+ */
1032
+ async function resolveCopilotWorkspaceBootstrapContext(params) {
1033
+ const { attempt } = params;
1034
+ const workspaceDir = readResolvedWorkspacePath(attempt.workspaceDir);
1035
+ if (!workspaceDir) return {
1036
+ bootstrapFiles: [],
1037
+ contextFiles: []
1038
+ };
1039
+ try {
1040
+ const bootstrapContext = await resolveBootstrapContextForRun({
1041
+ workspaceDir,
1042
+ config: attempt.config,
1043
+ sessionKey: readNonEmptyString(attempt.sessionKey),
1044
+ sessionId: readNonEmptyString(attempt.sessionId),
1045
+ agentId: readNonEmptyString(attempt.agentId),
1046
+ warn: params.warn,
1047
+ contextMode: attempt.bootstrapContextMode,
1048
+ runKind: attempt.bootstrapContextRunKind
1049
+ });
1050
+ const contextFiles = remapCopilotBootstrapContextFiles({
1051
+ files: bootstrapContext.contextFiles,
1052
+ sourceWorkspaceDir: workspaceDir,
1053
+ targetWorkspaceDir: readResolvedWorkspacePath(params.effectiveWorkspaceDir) ?? workspaceDir
1054
+ });
1055
+ return {
1056
+ bootstrapFiles: bootstrapContext.bootstrapFiles,
1057
+ contextFiles,
1058
+ instructions: renderCopilotWorkspaceBootstrapInstructions(contextFiles)
1059
+ };
1060
+ } catch (error) {
1061
+ params.warn?.(`[copilot-attempt] failed to load workspace bootstrap instructions: ${error instanceof Error ? error.message : String(error)}`);
1062
+ return {
1063
+ bootstrapFiles: [],
1064
+ contextFiles: []
1065
+ };
1066
+ }
1067
+ }
1068
+ /**
1069
+ * Rewrites context-file paths from a source workspace root to a
1070
+ * target workspace root, mirroring PI's
1071
+ * `remapInjectedContextFilesToWorkspace`
1072
+ * (`src/agents/pi-embedded-runner/run/attempt.ts:603`). Files whose
1073
+ * resolved relative path escapes the source workspace (parent
1074
+ * traversal or absolute) are left untouched so we never pretend a
1075
+ * file lives inside the sandbox when it does not. Exported for unit
1076
+ * tests; intentionally local to the Copilot extension (codex keeps
1077
+ * similar helpers extension-local rather than importing from PI).
1078
+ */
1079
+ function remapCopilotBootstrapContextFiles(params) {
1080
+ if (params.sourceWorkspaceDir === params.targetWorkspaceDir) return params.files;
1081
+ return params.files.map((file) => {
1082
+ const relative = path.relative(params.sourceWorkspaceDir, file.path);
1083
+ if (!isRelativePathInsideOrEqual(relative)) return file;
1084
+ return {
1085
+ ...file,
1086
+ path: relative === "" ? params.targetWorkspaceDir : path.join(params.targetWorkspaceDir, relative)
1087
+ };
1088
+ });
1089
+ }
1090
+ function isRelativePathInsideOrEqual(relativePath) {
1091
+ return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(relativePath);
1092
+ }
1093
+ /**
1094
+ * Renders bootstrap context files into a single string for
1095
+ * `SessionConfig.systemMessage.content` (append mode). Returns
1096
+ * `undefined` when no relevant files remain after filtering, so the
1097
+ * caller can skip setting `systemMessage` altogether.
1098
+ *
1099
+ * Files whose basename matches a doc the Copilot SDK already loads
1100
+ * natively (see {@link COPILOT_NATIVE_PROJECT_DOC_BASENAMES}) are
1101
+ * dropped to avoid duplication with SDK-managed sections.
1102
+ */
1103
+ function renderCopilotWorkspaceBootstrapInstructions(contextFiles) {
1104
+ const files = contextFiles.filter((file) => {
1105
+ const baseName = getCopilotContextFileBasename(file.path);
1106
+ return baseName.length > 0 && !COPILOT_NATIVE_PROJECT_DOC_BASENAMES.has(baseName);
1107
+ }).toSorted(compareCopilotContextFiles);
1108
+ if (files.length === 0) return;
1109
+ const hasSoulFile = files.some((file) => getCopilotContextFileBasename(file.path) === "soul.md");
1110
+ const lines = [
1111
+ "OpenClaw loaded these user-editable workspace files. Treat them as project/user context. The Copilot SDK loads AGENTS.md natively from its instruction directories, so AGENTS.md is not repeated here.",
1112
+ "",
1113
+ "# Project Context",
1114
+ "",
1115
+ "The following project context files have been loaded:"
1116
+ ];
1117
+ if (hasSoulFile) lines.push("SOUL.md: persona/tone. Follow it unless higher-priority instructions override.");
1118
+ lines.push("");
1119
+ for (const file of files) lines.push(`## ${file.path}`, "", file.content, "");
1120
+ return lines.join("\n").trim();
1121
+ }
1122
+ function compareCopilotContextFiles(left, right) {
1123
+ const leftBase = getCopilotContextFileBasename(left.path);
1124
+ const rightBase = getCopilotContextFileBasename(right.path);
1125
+ const leftOrder = COPILOT_BOOTSTRAP_CONTEXT_ORDER.get(leftBase) ?? Number.MAX_SAFE_INTEGER;
1126
+ const rightOrder = COPILOT_BOOTSTRAP_CONTEXT_ORDER.get(rightBase) ?? Number.MAX_SAFE_INTEGER;
1127
+ if (leftOrder !== rightOrder) return leftOrder - rightOrder;
1128
+ const leftPath = normalizeCopilotContextFilePath(left.path);
1129
+ const rightPath = normalizeCopilotContextFilePath(right.path);
1130
+ if (leftPath < rightPath) return -1;
1131
+ if (leftPath > rightPath) return 1;
1132
+ return 0;
1133
+ }
1134
+ function normalizeCopilotContextFilePath(filePath) {
1135
+ return filePath.trim().replaceAll("\\", "/").toLowerCase();
1136
+ }
1137
+ function getCopilotContextFileBasename(filePath) {
1138
+ return normalizeCopilotContextFilePath(filePath).split("/").pop() ?? "";
1139
+ }
1140
+ function readNonEmptyString(value) {
1141
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1142
+ }
1143
+ function readResolvedWorkspacePath(value) {
1144
+ const raw = readNonEmptyString(value);
1145
+ if (!raw) return;
1146
+ if (process.platform !== "win32" && /^[A-Za-z]:[\\/]/.test(raw)) return raw.trim();
1147
+ return resolveUserPath(raw);
1148
+ }
1149
+ //#endregion
1150
+ //#region extensions/copilot/src/attempt.ts
1151
+ const SUPPORTED_PROVIDERS = new Set(["github-copilot"]);
1152
+ async function runCopilotAttempt(params, deps) {
1153
+ const now = deps.now ?? Date.now;
1154
+ const input = params;
1155
+ const createToolBridge = deps.createToolBridge ?? createCopilotToolBridge;
1156
+ const messages = getMessagesSnapshotInput(input);
1157
+ if (params.abortSignal?.aborted) return createResult(input, {
1158
+ aborted: true,
1159
+ externalAbort: true,
1160
+ messagesSnapshot: messages,
1161
+ now,
1162
+ promptError: void 0,
1163
+ sdkSessionId: void 0,
1164
+ sessionIdUsed: input.sessionId
1165
+ });
1166
+ const modelRef = resolveModelRef(input);
1167
+ if (!SUPPORTED_PROVIDERS.has(modelRef.provider)) return createResult(input, {
1168
+ messagesSnapshot: messages,
1169
+ now,
1170
+ promptError: createPromptError("model_not_supported", `[copilot-attempt] provider ${modelRef.provider} is not supported at MVP (subscription Copilot models only; BYOK arrives via byok-mapping-skeleton)`),
1171
+ sdkSessionId: void 0,
1172
+ sessionIdUsed: input.sessionId
1173
+ });
1174
+ let abortRequested = false;
1175
+ let aborted = false;
1176
+ let externalAbort = false;
1177
+ let settled = false;
1178
+ let sentTurnStarted = false;
1179
+ let timedOut = false;
1180
+ let promptError;
1181
+ let sdkSessionId;
1182
+ let sessionIdUsed = input.sessionId;
1183
+ let disconnectError;
1184
+ let handle;
1185
+ let session;
1186
+ let bridge;
1187
+ let releaseError;
1188
+ let downgradedFromResume = false;
1189
+ let resumeFailureRecovered = false;
1190
+ let yieldDetected = false;
1191
+ const onAbort = () => {
1192
+ abortRequested = true;
1193
+ externalAbort = true;
1194
+ aborted = true;
1195
+ if (settled || !sentTurnStarted || !session) return;
1196
+ session.abort().catch(() => void 0);
1197
+ };
1198
+ params.abortSignal?.addEventListener("abort", onAbort, { once: true });
1199
+ const resolvedWorkspaceForSandbox = readResolvedAttemptPath(input.workspaceDir) ?? readResolvedAttemptPath(input.cwd);
1200
+ const sandboxSessionKey = readString(input.sandboxSessionKey) ?? readString(input.sessionKey) ?? readString(input.sessionId);
1201
+ const resolveSandbox = deps.resolveSandboxContextOverride ?? resolveSandboxContext;
1202
+ let sandbox = null;
1203
+ let effectiveWorkspaceDir = resolvedWorkspaceForSandbox;
1204
+ if (resolvedWorkspaceForSandbox) try {
1205
+ sandbox = await resolveSandbox({
1206
+ config: input.config,
1207
+ sessionKey: sandboxSessionKey,
1208
+ workspaceDir: resolvedWorkspaceForSandbox
1209
+ });
1210
+ effectiveWorkspaceDir = sandbox?.enabled ? sandbox.workspaceAccess === "rw" ? resolvedWorkspaceForSandbox : sandbox.workspaceDir : resolvedWorkspaceForSandbox;
1211
+ if (sandbox?.enabled && effectiveWorkspaceDir && effectiveWorkspaceDir !== resolvedWorkspaceForSandbox) await fsp.mkdir(effectiveWorkspaceDir, { recursive: true });
1212
+ } catch (error) {
1213
+ settled = true;
1214
+ params.abortSignal?.removeEventListener("abort", onAbort);
1215
+ if (abortRequested || params.abortSignal?.aborted) return createResult(input, {
1216
+ aborted: true,
1217
+ externalAbort: true,
1218
+ messagesSnapshot: messages,
1219
+ now,
1220
+ promptError: void 0,
1221
+ sdkSessionId: void 0,
1222
+ sessionIdUsed: input.sessionId
1223
+ });
1224
+ return createResult(input, {
1225
+ messagesSnapshot: messages,
1226
+ now,
1227
+ promptError: createPromptError("sandbox_resolution_failure", `[copilot-attempt] sandbox resolution failed: ${toError(error).message}`, error),
1228
+ sdkSessionId: void 0,
1229
+ sessionIdUsed: input.sessionId
1230
+ });
1231
+ }
1232
+ const requestedCwd = readResolvedAttemptPath(input.cwd);
1233
+ if (sandbox?.enabled && requestedCwd && requestedCwd !== resolvedWorkspaceForSandbox) {
1234
+ settled = true;
1235
+ params.abortSignal?.removeEventListener("abort", onAbort);
1236
+ return createResult(input, {
1237
+ messagesSnapshot: messages,
1238
+ now,
1239
+ promptError: createPromptError("sandbox_cwd_override_unsupported", "[copilot-attempt] cwd override is not supported for sandboxed Copilot runs; omit cwd or use the agent workspace as cwd"),
1240
+ sdkSessionId: void 0,
1241
+ sessionIdUsed: input.sessionId
1242
+ });
1243
+ }
1244
+ const effectiveCwd = sandbox?.enabled ? effectiveWorkspaceDir : requestedCwd ?? effectiveWorkspaceDir;
1245
+ const { sessionAgentId } = resolveSessionAgentIds({
1246
+ sessionKey: readString(input.sessionKey),
1247
+ config: input.config,
1248
+ agentId: readString(params.agentId)
1249
+ });
1250
+ const effectiveFsWorkspaceOnly = resolveAttemptFsWorkspaceOnly({
1251
+ config: input.config,
1252
+ sessionAgentId
1253
+ });
1254
+ const sandboxAwareSpawnWorkspaceDir = resolvedWorkspaceForSandbox ? resolveAttemptSpawnWorkspaceDir({
1255
+ sandbox,
1256
+ resolvedWorkspace: resolvedWorkspaceForSandbox
1257
+ }) : void 0;
1258
+ const poolAcquire = resolvePoolAcquire(input);
1259
+ const sessionRef = { current: void 0 };
1260
+ try {
1261
+ let sdkTools;
1262
+ try {
1263
+ sdkTools = (await createToolBridge({
1264
+ modelProvider: modelRef.provider,
1265
+ modelId: modelRef.id,
1266
+ agentId: readString(params.agentId) ?? "copilot",
1267
+ sessionId: readString(input.sessionId) ?? "copilot-session",
1268
+ sessionKey: readString(input.sessionKey),
1269
+ agentDir: readString(input.agentDir),
1270
+ workspaceDir: effectiveWorkspaceDir,
1271
+ cwd: effectiveCwd,
1272
+ sandbox,
1273
+ spawnWorkspaceDir: sandboxAwareSpawnWorkspaceDir,
1274
+ abortSignal: params.abortSignal,
1275
+ attemptParams: input,
1276
+ sessionRef,
1277
+ onYieldDetected: () => {
1278
+ yieldDetected = true;
1279
+ }
1280
+ })).sdkTools;
1281
+ } catch (error) {
1282
+ return createResult(input, {
1283
+ messagesSnapshot: messages,
1284
+ now,
1285
+ promptError: createPromptError("tool_bridge_failure", `[copilot-attempt] tool-bridge construction failed: ${toError(error).message}`, error),
1286
+ sdkSessionId: void 0,
1287
+ sessionIdUsed: input.sessionId
1288
+ });
1289
+ }
1290
+ handle = await deps.pool.acquire(poolAcquire.key, poolAcquire.options);
1291
+ const client = handle.client;
1292
+ const workspaceBootstrap = await resolveCopilotWorkspaceBootstrapContext({
1293
+ attempt: input,
1294
+ effectiveWorkspaceDir,
1295
+ warn: (message) => console.warn(message)
1296
+ });
1297
+ const sessionConfig = createSessionConfig(input, modelRef.id, sdkTools, poolAcquire.auth, workspaceBootstrap.instructions, effectiveWorkspaceDir, effectiveCwd);
1298
+ const replayDecision = decideReplayAction({
1299
+ sdkSessionId: input.initialReplayState?.sdkSessionId,
1300
+ replayInvalid: input.initialReplayState?.replayInvalid
1301
+ });
1302
+ downgradedFromResume = replayDecision.downgradedFromResume;
1303
+ const resumeSessionId = replayDecision.action === "resume" ? replayDecision.sdkSessionId : void 0;
1304
+ if (resumeSessionId) try {
1305
+ session = await client.resumeSession(resumeSessionId, {
1306
+ ...sessionConfig,
1307
+ continuePendingWork: false
1308
+ });
1309
+ } catch (error) {
1310
+ if (!classifyResumeFailure(error).recoverable) throw error;
1311
+ resumeFailureRecovered = true;
1312
+ session = await client.createSession(sessionConfig);
1313
+ }
1314
+ else session = await client.createSession(sessionConfig);
1315
+ sessionRef.current = session;
1316
+ sdkSessionId = readSessionId(session) ?? (resumeFailureRecovered ? void 0 : resumeSessionId);
1317
+ sessionIdUsed = sdkSessionId ?? input.sessionId;
1318
+ if (sdkSessionId && deps.onSessionEstablished) try {
1319
+ deps.onSessionEstablished({
1320
+ sdkSessionId,
1321
+ pooledClient: handle
1322
+ });
1323
+ } catch {}
1324
+ bridge = attachEventBridge(session, {
1325
+ onAssistantDelta: input.onAssistantDelta,
1326
+ getSdkSessionId: () => sdkSessionId,
1327
+ isAborted: () => aborted
1328
+ });
1329
+ const messageOptions = await createMessageOptions(input, {
1330
+ effectiveCwd,
1331
+ effectiveWorkspaceDir,
1332
+ sandbox,
1333
+ workspaceOnly: effectiveFsWorkspaceOnly
1334
+ });
1335
+ if (abortRequested || params.abortSignal?.aborted) {
1336
+ aborted = true;
1337
+ externalAbort = true;
1338
+ } else {
1339
+ sentTurnStarted = true;
1340
+ const result = await session.sendAndWait(messageOptions, input.timeoutMs);
1341
+ await bridge.awaitDeltaChain();
1342
+ if (!bridge.recordSendResult(result) && !aborted) timedOut = true;
1343
+ const snap = bridge.snapshot();
1344
+ if (!promptError && !timedOut && !aborted && snap.streamError) promptError = snap.streamError;
1345
+ }
1346
+ } catch (error) {
1347
+ if (!aborted) if (isSdkSendAndWaitTimeoutError(error)) {
1348
+ timedOut = true;
1349
+ try {
1350
+ await bridge?.awaitDeltaChain();
1351
+ } catch {}
1352
+ } else promptError = toError(error);
1353
+ } finally {
1354
+ settled = true;
1355
+ bridge?.detach();
1356
+ params.abortSignal?.removeEventListener("abort", onAbort);
1357
+ if (session) try {
1358
+ await session.disconnect();
1359
+ } catch (error) {
1360
+ disconnectError = toError(error);
1361
+ if (!promptError && !timedOut) promptError = disconnectError;
1362
+ }
1363
+ if (handle) try {
1364
+ await deps.pool.release(handle);
1365
+ } catch (error) {
1366
+ const releaseFailure = toError(error);
1367
+ if (promptError) console.warn("[copilot-attempt] pool.release failed after primary error", releaseFailure);
1368
+ else releaseError = releaseFailure;
1369
+ }
1370
+ }
1371
+ if (releaseError) throw releaseError;
1372
+ const snap = bridge?.snapshot();
1373
+ const assistantTexts = bridge?.finalizeAssistantTexts() ?? [];
1374
+ const lastAssistant = bridge?.buildAssistantMessage({
1375
+ modelRef,
1376
+ now
1377
+ });
1378
+ const syntheticUserText = readString(input.transcriptPrompt) ?? readString(input.prompt);
1379
+ const tailUserText = readTailUserText(messages);
1380
+ const syntheticUser = syntheticUserText && syntheticUserText !== tailUserText ? attachCopilotMirrorIdentity({
1381
+ role: "user",
1382
+ content: syntheticUserText,
1383
+ timestamp: now()
1384
+ }, `${input.runId}:prompt`) : void 0;
1385
+ const taggedLastAssistant = lastAssistant ? attachCopilotMirrorIdentity(lastAssistant, `${input.runId}:assistant:final`) : void 0;
1386
+ const messagesSnapshot = [
1387
+ ...messages,
1388
+ ...syntheticUser ? [syntheticUser] : [],
1389
+ ...taggedLastAssistant ? [taggedLastAssistant] : []
1390
+ ];
1391
+ const sessionFileForMirror = readString(input.sessionFile);
1392
+ const sessionIdForScope = sessionIdUsed ?? readString(input.sessionId);
1393
+ if (sessionFileForMirror && messagesSnapshot.length > 0) {
1394
+ const taggedMessages = messagesSnapshot.map((message, index) => {
1395
+ if (message.role !== "user" && message.role !== "assistant" && message.role !== "toolResult") return message;
1396
+ if (hasMirrorIdentity(message)) return message;
1397
+ return attachCopilotMirrorIdentity(message, `${sdkSessionId ?? sessionIdForScope ?? "attempt"}:${message.role}:${index}`);
1398
+ });
1399
+ await dualWriteCopilotTranscriptBestEffort({
1400
+ sessionFile: sessionFileForMirror,
1401
+ sessionKey: readString(input.sessionKey),
1402
+ agentId: readString(input.agentId),
1403
+ messages: taggedMessages,
1404
+ idempotencyScope: sessionIdForScope ? `copilot:${sessionIdForScope}` : void 0,
1405
+ config: input.config
1406
+ }).catch((mirrorError) => {
1407
+ console.warn("[copilot-attempt] dual-write transcript wrapper rejected unexpectedly", mirrorError);
1408
+ });
1409
+ }
1410
+ return createResult(input, {
1411
+ aborted,
1412
+ assistantTexts,
1413
+ currentAttemptAssistant: lastAssistant,
1414
+ downgradedFromResume,
1415
+ externalAbort,
1416
+ itemLifecycle: {
1417
+ activeCount: Math.max((snap?.startedCount ?? 0) - (snap?.completedCount ?? 0), 0),
1418
+ completedCount: snap?.completedCount ?? 0,
1419
+ startedCount: snap?.startedCount ?? 0
1420
+ },
1421
+ lastAssistant,
1422
+ messagesSnapshot,
1423
+ now,
1424
+ promptError,
1425
+ resumeFailureRecovered,
1426
+ sdkSessionId,
1427
+ sessionIdUsed,
1428
+ timedOut,
1429
+ toolMetas: snap ? [...snap.toolMetas] : [],
1430
+ usage: snap?.usage,
1431
+ yieldDetected
1432
+ });
1433
+ }
1434
+ function createResult(params, state) {
1435
+ const promptError = state.promptError;
1436
+ const timedOut = state.timedOut === true;
1437
+ const toolMetas = state.toolMetas ?? [];
1438
+ const replayMetadata = computeReplayMetadata({
1439
+ priorReplayInvalid: params.initialReplayState?.replayInvalid,
1440
+ priorHadPotentialSideEffects: params.initialReplayState?.hadPotentialSideEffects,
1441
+ thisAttemptTimedOut: timedOut,
1442
+ thisAttemptHadPotentialSideEffects: copilotToolMetasHavePotentialSideEffects(toolMetas),
1443
+ thisAttemptDowngradedFromResume: state.downgradedFromResume,
1444
+ thisAttemptResumeFailureRecovered: state.resumeFailureRecovered
1445
+ });
1446
+ return {
1447
+ aborted: state.aborted === true,
1448
+ ...state.sdkSessionId ? { sdkSessionId: state.sdkSessionId } : {},
1449
+ assistantTexts: state.assistantTexts ?? [],
1450
+ attemptUsage: state.usage,
1451
+ cloudCodeAssistFormatError: false,
1452
+ currentAttemptAssistant: state.currentAttemptAssistant,
1453
+ didSendViaMessagingTool: false,
1454
+ externalAbort: state.externalAbort === true,
1455
+ idleTimedOut: false,
1456
+ itemLifecycle: state.itemLifecycle ?? {
1457
+ activeCount: 0,
1458
+ completedCount: 0,
1459
+ startedCount: 0
1460
+ },
1461
+ lastAssistant: state.lastAssistant,
1462
+ messagesSnapshot: state.messagesSnapshot,
1463
+ messagingToolSentMediaUrls: [],
1464
+ messagingToolSentTargets: [],
1465
+ messagingToolSentTexts: [],
1466
+ promptError,
1467
+ promptErrorSource: promptError ? "prompt" : null,
1468
+ replayMetadata,
1469
+ sessionFileUsed: readString(params.sessionFile),
1470
+ sessionIdUsed: state.sessionIdUsed ?? readString(params.sessionId) ?? "copilot-session",
1471
+ timedOut,
1472
+ timedOutDuringCompaction: false,
1473
+ toolMetas,
1474
+ yieldDetected: state.yieldDetected === true
1475
+ };
1476
+ }
1477
+ function createPromptError(code, message, cause) {
1478
+ const error = new Error(message);
1479
+ error.code = code;
1480
+ if (cause !== void 0) error.cause = cause;
1481
+ return error;
1482
+ }
1483
+ function createSessionConfig(params, sdkModelId, sdkTools, resolvedAuth, workspaceBootstrapInstructions, effectiveWorkspaceDir, effectiveCwd) {
1484
+ const permissionPolicy = params.permissionPolicy ?? rejectAllPolicy;
1485
+ const hooks = createHooksBridge(params.hooksConfig);
1486
+ const infiniteSessions = createInfiniteSessionConfig(params.infiniteSessionConfig);
1487
+ const systemMessageContent = createSystemMessageContent(params, workspaceBootstrapInstructions);
1488
+ return {
1489
+ model: sdkModelId,
1490
+ onPermissionRequest: createPermissionBridge(permissionPolicy),
1491
+ ...hooks ? { hooks } : {},
1492
+ ...typeof params.enableSessionTelemetry === "boolean" ? { enableSessionTelemetry: params.enableSessionTelemetry } : {},
1493
+ ...infiniteSessions ? { infiniteSessions } : {},
1494
+ reasoningEffort: params.reasoningEffort,
1495
+ tools: sdkTools,
1496
+ availableTools: sdkTools.map((tool) => tool.name),
1497
+ workingDirectory: effectiveCwd ?? effectiveWorkspaceDir ?? readResolvedAttemptPath(params.workspaceDir),
1498
+ ...effectiveWorkspaceDir && effectiveCwd && effectiveCwd !== effectiveWorkspaceDir ? { instructionDirectories: [effectiveWorkspaceDir] } : {},
1499
+ ...resolvedAuth.authMode === "gitHubToken" && resolvedAuth.gitHubToken ? { gitHubToken: resolvedAuth.gitHubToken } : {},
1500
+ ...systemMessageContent ? { systemMessage: {
1501
+ mode: "append",
1502
+ content: systemMessageContent
1503
+ } } : {}
1504
+ };
1505
+ }
1506
+ async function createMessageOptions(params, context) {
1507
+ const attachments = createPromptImageAttachments(await resolvePromptImages(params, context));
1508
+ return attachments.length > 0 ? {
1509
+ prompt: params.prompt,
1510
+ attachments
1511
+ } : { prompt: params.prompt };
1512
+ }
1513
+ function createPromptImageAttachments(images) {
1514
+ return images.flatMap((image, index) => {
1515
+ if (!image || typeof image !== "object" || image.type !== "image" || typeof image.data !== "string" || typeof image.mimeType !== "string") return [];
1516
+ return [{
1517
+ type: "blob",
1518
+ data: image.data,
1519
+ mimeType: image.mimeType,
1520
+ displayName: `prompt-image-${index + 1}`
1521
+ }];
1522
+ });
1523
+ }
1524
+ async function resolvePromptImages(params, context) {
1525
+ const workspaceDir = context.effectiveCwd ?? context.effectiveWorkspaceDir ?? readResolvedAttemptPath(params.cwd) ?? readResolvedAttemptPath(params.workspaceDir);
1526
+ if (!workspaceDir) return [];
1527
+ const localRoots = context.workspaceOnly && context.effectiveWorkspaceDir ? [context.effectiveWorkspaceDir] : void 0;
1528
+ return (await detectAndLoadAgentHarnessPromptImages({
1529
+ prompt: params.prompt,
1530
+ workspaceDir,
1531
+ model: resolveImageCapabilityModel(params),
1532
+ existingImages: Array.isArray(params.images) ? params.images : void 0,
1533
+ imageOrder: Array.isArray(params.imageOrder) ? params.imageOrder : void 0,
1534
+ config: params.config,
1535
+ workspaceOnly: context.workspaceOnly,
1536
+ localRoots,
1537
+ sandbox: context.sandbox?.enabled && context.sandbox.fsBridge ? {
1538
+ root: context.sandbox.workspaceDir,
1539
+ bridge: context.sandbox.fsBridge
1540
+ } : void 0
1541
+ })).images;
1542
+ }
1543
+ function resolveImageCapabilityModel(params) {
1544
+ const model = params.model;
1545
+ if (model && typeof model === "object" && Array.isArray(model.input)) return { input: model.input };
1546
+ return { input: ["image"] };
1547
+ }
1548
+ function createSystemMessageContent(params, workspaceBootstrapInstructions) {
1549
+ const sections = [];
1550
+ const bootstrap = workspaceBootstrapInstructions?.trim();
1551
+ if (bootstrap) sections.push(bootstrap);
1552
+ const extraSystemPrompt = readString(params.extraSystemPrompt)?.trim();
1553
+ if (extraSystemPrompt && !isRawCopilotModelRun(params)) {
1554
+ const contextHeader = params.promptMode === "minimal" ? "## Subagent Context" : "## Group Chat Context";
1555
+ sections.push(`${contextHeader}\n${extraSystemPrompt}`);
1556
+ }
1557
+ return sections.length > 0 ? sections.join("\n\n") : void 0;
1558
+ }
1559
+ function isRawCopilotModelRun(params) {
1560
+ return params.modelRun === true || params.promptMode === "none";
1561
+ }
1562
+ function getMessagesSnapshotInput(params) {
1563
+ return Array.isArray(params.messages) ? [...params.messages] : [];
1564
+ }
1565
+ function readTailUserText(messages) {
1566
+ const tail = messages[messages.length - 1];
1567
+ if (!tail || tail.role !== "user") return;
1568
+ const content = tail.content;
1569
+ if (typeof content === "string") return content;
1570
+ if (Array.isArray(content)) {
1571
+ for (const part of content) if (part && typeof part === "object" && part.type === "text") {
1572
+ const text = part.text;
1573
+ if (typeof text === "string" && text.length > 0) return text;
1574
+ }
1575
+ }
1576
+ }
1577
+ function hasMirrorIdentity(message) {
1578
+ const meta = message["__openclaw"];
1579
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return false;
1580
+ const id = meta.mirrorIdentity;
1581
+ return typeof id === "string" && id.length > 0;
1582
+ }
1583
+ function readSessionId(session) {
1584
+ if (!session) return;
1585
+ return readString(session.sessionId) ?? readString(session.id);
1586
+ }
1587
+ function readString(value) {
1588
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1589
+ }
1590
+ function readResolvedAttemptPath(value) {
1591
+ const raw = readString(value)?.trim();
1592
+ if (!raw) return;
1593
+ if (process.platform !== "win32" && /^[A-Za-z]:[\\/]/.test(raw)) return raw;
1594
+ return resolveUserPath(raw);
1595
+ }
1596
+ function resolveModelRef(params) {
1597
+ const rawModel = params.model;
1598
+ if (rawModel && typeof rawModel === "object") return {
1599
+ api: readString(rawModel.api),
1600
+ id: readString(rawModel.id) ?? readString(params.modelId) ?? "unknown-model",
1601
+ provider: readString(rawModel.provider) ?? readString(params.provider) ?? "unknown-provider"
1602
+ };
1603
+ return {
1604
+ id: readString(typeof rawModel === "string" ? rawModel : void 0) ?? readString(params.modelId) ?? "unknown-model",
1605
+ provider: readString(params.provider) ?? "unknown-provider"
1606
+ };
1607
+ }
1608
+ function resolvePoolAcquire(params) {
1609
+ const resolved = resolveCopilotAuth({
1610
+ agentId: readString(params.agentId),
1611
+ agentDir: readString(params.agentDir),
1612
+ workspaceDir: readString(params.workspaceDir),
1613
+ copilotHome: readString(params.copilotHome),
1614
+ auth: params.auth,
1615
+ resolvedApiKey: readString(params.resolvedApiKey),
1616
+ authProfileId: readString(params.authProfileId),
1617
+ profileVersion: readString(params.profileVersion)
1618
+ });
1619
+ return {
1620
+ key: {
1621
+ agentId: resolved.agentId,
1622
+ authMode: resolved.authMode,
1623
+ ...resolved.authMode === "gitHubToken" ? {
1624
+ authProfileId: resolved.authProfileId,
1625
+ authProfileVersion: resolved.authProfileVersion
1626
+ } : {},
1627
+ copilotHome: resolved.copilotHome
1628
+ },
1629
+ options: {
1630
+ copilotHome: resolved.copilotHome,
1631
+ cwd: readString(params.cwd) ?? readString(params.workspaceDir),
1632
+ gitHubToken: resolved.authMode === "gitHubToken" ? resolved.gitHubToken : void 0,
1633
+ useLoggedInUser: resolved.authMode === "useLoggedInUser"
1634
+ },
1635
+ auth: resolved
1636
+ };
1637
+ }
1638
+ function toError(error) {
1639
+ return error instanceof Error ? error : new Error(String(error));
1640
+ }
1641
+ /**
1642
+ * Detect the @github/copilot-sdk `session.sendAndWait` timeout
1643
+ * rejection shape. The SDK's `sendAndWait` races the internal
1644
+ * `session.idle` event against a timer; when the timer fires first
1645
+ * it REJECTS the promise with
1646
+ * `new Error(`Timeout after ${effectiveTimeout}ms waiting for
1647
+ * session.idle`)` (see
1648
+ * `node_modules/@github/copilot-sdk/dist/session.js:156-164`), and
1649
+ * the SDK docs explicitly note the timeout "does not abort in-flight
1650
+ * agent work". The caller is therefore responsible for setting the
1651
+ * timed-out state and (for paths where in-flight work should be
1652
+ * stopped) calling `session.abort()`.
1653
+ *
1654
+ * Keep the regex anchored and narrow so unrelated errors that happen
1655
+ * to mention "Timeout" are NOT mis-classified. The shape is a literal
1656
+ * template-string concatenation in the 1.0.0-beta line; a minor
1657
+ * version bump that changes the wording will safely fall through to
1658
+ * the generic prompt-error path.
1659
+ */
1660
+ function isSdkSendAndWaitTimeoutError(error) {
1661
+ if (error === null || typeof error !== "object") return false;
1662
+ const message = error.message;
1663
+ if (typeof message !== "string") return false;
1664
+ return /^Timeout after \d+ms waiting for session\.idle$/.test(message);
1665
+ }
1666
+ //#endregion
1667
+ export { runCopilotAttempt };