poly-weaver 0.15.3 → 1.0.0

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 (174) hide show
  1. package/dist/agents/implementors/handler.d.ts.map +1 -1
  2. package/dist/agents/implementors/handler.js +7 -2
  3. package/dist/agents/implementors/handler.js.map +1 -1
  4. package/dist/agents/interviewers/handler.d.ts.map +1 -1
  5. package/dist/agents/interviewers/handler.js +3 -1
  6. package/dist/agents/interviewers/handler.js.map +1 -1
  7. package/dist/agents/planners/adapter.d.ts.map +1 -1
  8. package/dist/agents/planners/adapter.js +30 -11
  9. package/dist/agents/planners/adapter.js.map +1 -1
  10. package/dist/agents/planners/handler.d.ts.map +1 -1
  11. package/dist/agents/planners/handler.js +7 -2
  12. package/dist/agents/planners/handler.js.map +1 -1
  13. package/dist/agents/reviewers/adapter.d.ts.map +1 -1
  14. package/dist/agents/reviewers/adapter.js +1 -0
  15. package/dist/agents/reviewers/adapter.js.map +1 -1
  16. package/dist/agents/reviewers/handler.d.ts.map +1 -1
  17. package/dist/agents/reviewers/handler.js +23 -4
  18. package/dist/agents/reviewers/handler.js.map +1 -1
  19. package/dist/agents/reviewers/types.d.ts +2 -0
  20. package/dist/agents/reviewers/types.d.ts.map +1 -1
  21. package/dist/agents/runner.d.ts +18 -1
  22. package/dist/agents/runner.d.ts.map +1 -1
  23. package/dist/agents/runner.js +136 -20
  24. package/dist/agents/runner.js.map +1 -1
  25. package/dist/agents/simplifiers/handler.d.ts.map +1 -1
  26. package/dist/agents/simplifiers/handler.js +7 -2
  27. package/dist/agents/simplifiers/handler.js.map +1 -1
  28. package/dist/auto-update/detect-agents.d.ts +1 -0
  29. package/dist/auto-update/detect-agents.d.ts.map +1 -1
  30. package/dist/auto-update/detect-agents.js +8 -7
  31. package/dist/auto-update/detect-agents.js.map +1 -1
  32. package/dist/cli.js +16 -4
  33. package/dist/cli.js.map +1 -1
  34. package/dist/crosscheck/coordinator.d.ts +5 -0
  35. package/dist/crosscheck/coordinator.d.ts.map +1 -1
  36. package/dist/crosscheck/coordinator.js +177 -91
  37. package/dist/crosscheck/coordinator.js.map +1 -1
  38. package/dist/crosscheck/live-agent-session.d.ts +7 -1
  39. package/dist/crosscheck/live-agent-session.d.ts.map +1 -1
  40. package/dist/crosscheck/live-agent-session.js +61 -7
  41. package/dist/crosscheck/live-agent-session.js.map +1 -1
  42. package/dist/crosscheck/state.d.ts +3 -0
  43. package/dist/crosscheck/state.d.ts.map +1 -1
  44. package/dist/crosscheck/state.js +17 -2
  45. package/dist/crosscheck/state.js.map +1 -1
  46. package/dist/dump/collector.d.ts.map +1 -1
  47. package/dist/dump/collector.js +3 -0
  48. package/dist/dump/collector.js.map +1 -1
  49. package/dist/dump/service.d.ts.map +1 -1
  50. package/dist/dump/service.js +3 -0
  51. package/dist/dump/service.js.map +1 -1
  52. package/dist/dump/types.d.ts +3 -0
  53. package/dist/dump/types.d.ts.map +1 -1
  54. package/dist/dump/types.js.map +1 -1
  55. package/dist/flow/executor.d.ts.map +1 -1
  56. package/dist/flow/executor.js +5 -0
  57. package/dist/flow/executor.js.map +1 -1
  58. package/dist/flow/types.d.ts +5 -0
  59. package/dist/flow/types.d.ts.map +1 -1
  60. package/dist/flow/types.js.map +1 -1
  61. package/dist/offscreen/controller.d.ts +13 -2
  62. package/dist/offscreen/controller.d.ts.map +1 -1
  63. package/dist/offscreen/controller.js +212 -91
  64. package/dist/offscreen/controller.js.map +1 -1
  65. package/dist/offscreen/fork-launch.d.ts +2 -2
  66. package/dist/offscreen/fork-launch.d.ts.map +1 -1
  67. package/dist/offscreen/fork-launch.js +2 -2
  68. package/dist/offscreen/fork-launch.js.map +1 -1
  69. package/dist/offscreen/fork.d.ts +16 -2
  70. package/dist/offscreen/fork.d.ts.map +1 -1
  71. package/dist/offscreen/fork.js +79 -40
  72. package/dist/offscreen/fork.js.map +1 -1
  73. package/dist/offscreen/inquiry-pane.d.ts +2 -1
  74. package/dist/offscreen/inquiry-pane.d.ts.map +1 -1
  75. package/dist/offscreen/inquiry-pane.js +8 -2
  76. package/dist/offscreen/inquiry-pane.js.map +1 -1
  77. package/dist/offscreen/panes.d.ts +1 -0
  78. package/dist/offscreen/panes.d.ts.map +1 -1
  79. package/dist/offscreen/registry.d.ts +2 -2
  80. package/dist/offscreen/registry.d.ts.map +1 -1
  81. package/dist/offscreen/registry.js +5 -1
  82. package/dist/offscreen/registry.js.map +1 -1
  83. package/dist/offscreen/resolve.d.ts +2 -0
  84. package/dist/offscreen/resolve.d.ts.map +1 -1
  85. package/dist/offscreen/resolve.js +5 -1
  86. package/dist/offscreen/resolve.js.map +1 -1
  87. package/dist/offscreen/session-turn-monitor.d.ts +3 -1
  88. package/dist/offscreen/session-turn-monitor.d.ts.map +1 -1
  89. package/dist/offscreen/session-turn-monitor.js +10 -2
  90. package/dist/offscreen/session-turn-monitor.js.map +1 -1
  91. package/dist/offscreen/split-inquiry.d.ts.map +1 -1
  92. package/dist/offscreen/split-inquiry.js +5 -0
  93. package/dist/offscreen/split-inquiry.js.map +1 -1
  94. package/dist/offscreen/starting-pane.d.ts +13 -0
  95. package/dist/offscreen/starting-pane.d.ts.map +1 -0
  96. package/dist/offscreen/starting-pane.js +24 -0
  97. package/dist/offscreen/starting-pane.js.map +1 -0
  98. package/dist/offscreen/surface.d.ts +1 -0
  99. package/dist/offscreen/surface.d.ts.map +1 -1
  100. package/dist/offscreen/surface.js +5 -3
  101. package/dist/offscreen/surface.js.map +1 -1
  102. package/dist/orchestrator.d.ts.map +1 -1
  103. package/dist/orchestrator.js +7 -1
  104. package/dist/orchestrator.js.map +1 -1
  105. package/dist/providers/copilot/completion.d.ts +32 -21
  106. package/dist/providers/copilot/completion.d.ts.map +1 -1
  107. package/dist/providers/copilot/completion.js +100 -165
  108. package/dist/providers/copilot/completion.js.map +1 -1
  109. package/dist/providers/copilot/environment.d.ts +2 -0
  110. package/dist/providers/copilot/environment.d.ts.map +1 -0
  111. package/dist/providers/copilot/environment.js +8 -0
  112. package/dist/providers/copilot/environment.js.map +1 -0
  113. package/dist/providers/copilot/fork-state.d.ts +7 -0
  114. package/dist/providers/copilot/fork-state.d.ts.map +1 -0
  115. package/dist/providers/copilot/fork-state.js +150 -0
  116. package/dist/providers/copilot/fork-state.js.map +1 -0
  117. package/dist/providers/copilot/observation.d.ts +275 -0
  118. package/dist/providers/copilot/observation.d.ts.map +1 -0
  119. package/dist/providers/copilot/observation.js +2072 -0
  120. package/dist/providers/copilot/observation.js.map +1 -0
  121. package/dist/providers/copilot/observer-extension/com.github.copilot/extensions/poly-weaver-observer/extension.d.mts +2 -0
  122. package/dist/providers/copilot/observer-extension/com.github.copilot/extensions/poly-weaver-observer/extension.d.mts.map +1 -0
  123. package/dist/providers/copilot/observer-extension/com.github.copilot/extensions/poly-weaver-observer/extension.mjs +426 -0
  124. package/dist/providers/copilot/observer-extension/com.github.copilot/extensions/poly-weaver-observer/extension.mjs.map +1 -0
  125. package/dist/providers/copilot/observer-extension/plugin.json +6 -0
  126. package/dist/providers/copilot/record-properties.d.ts +4 -0
  127. package/dist/providers/copilot/record-properties.d.ts.map +1 -0
  128. package/dist/providers/copilot/record-properties.js +14 -0
  129. package/dist/providers/copilot/record-properties.js.map +1 -0
  130. package/dist/providers/copilot/session.d.ts +8 -33
  131. package/dist/providers/copilot/session.d.ts.map +1 -1
  132. package/dist/providers/copilot/session.js +10 -176
  133. package/dist/providers/copilot/session.js.map +1 -1
  134. package/dist/providers/copilot/strategy.d.ts +3 -5
  135. package/dist/providers/copilot/strategy.d.ts.map +1 -1
  136. package/dist/providers/copilot/strategy.js +59 -66
  137. package/dist/providers/copilot/strategy.js.map +1 -1
  138. package/dist/providers/fork-cleanup.d.ts +4 -0
  139. package/dist/providers/fork-cleanup.d.ts.map +1 -0
  140. package/dist/providers/fork-cleanup.js +21 -0
  141. package/dist/providers/fork-cleanup.js.map +1 -0
  142. package/dist/providers/fork.d.ts +8 -3
  143. package/dist/providers/fork.d.ts.map +1 -1
  144. package/dist/providers/fork.js.map +1 -1
  145. package/dist/providers/invoke.d.ts.map +1 -1
  146. package/dist/providers/invoke.js +85 -34
  147. package/dist/providers/invoke.js.map +1 -1
  148. package/dist/providers/live.d.ts.map +1 -1
  149. package/dist/providers/live.js +43 -17
  150. package/dist/providers/live.js.map +1 -1
  151. package/dist/providers/observer-run.d.ts +4 -0
  152. package/dist/providers/observer-run.d.ts.map +1 -0
  153. package/dist/providers/observer-run.js +23 -0
  154. package/dist/providers/observer-run.js.map +1 -0
  155. package/dist/providers/session-launch.d.ts +3 -1
  156. package/dist/providers/session-launch.d.ts.map +1 -1
  157. package/dist/providers/session-launch.js +38 -19
  158. package/dist/providers/session-launch.js.map +1 -1
  159. package/dist/providers/types.d.ts +156 -4
  160. package/dist/providers/types.d.ts.map +1 -1
  161. package/dist/providers/types.js.map +1 -1
  162. package/dist/session/resume.d.ts.map +1 -1
  163. package/dist/session/resume.js +16 -2
  164. package/dist/session/resume.js.map +1 -1
  165. package/dist/terminal/paste-submit.d.ts.map +1 -1
  166. package/dist/terminal/paste-submit.js +43 -37
  167. package/dist/terminal/paste-submit.js.map +1 -1
  168. package/dist/user/host-curate-prompt.d.ts.map +1 -1
  169. package/dist/user/host-curate-prompt.js +9 -6
  170. package/dist/user/host-curate-prompt.js.map +1 -1
  171. package/dist/user/host-prompt.d.ts.map +1 -1
  172. package/dist/user/host-prompt.js +9 -6
  173. package/dist/user/host-prompt.js.map +1 -1
  174. package/package.json +4 -2
@@ -0,0 +1,2072 @@
1
+ import { randomBytes, randomUUID } from "node:crypto";
2
+ import { COPILOT_PARENT_ENV_TO_CLEAR } from "./environment.js";
3
+ import { existsSync } from "node:fs";
4
+ import { chmod, cp, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
5
+ import { createRequire } from "node:module";
6
+ import { tmpdir } from "node:os";
7
+ import { basename, dirname, join, normalize } from "node:path";
8
+ import { fileURLToPath, pathToFileURL } from "node:url";
9
+ import { createServer } from "node:net";
10
+ import { sendComposerText } from "../../terminal/paste-submit.js";
11
+ import { abortableSleep, throwIfAborted } from "../../session/locator.js";
12
+ import { SessionTurnReceipt, } from "../../offscreen/session-turn-receipt.js";
13
+ import { isRecord } from "../trust-utils.js";
14
+ import { recordProp, stringProp, rawStringProp } from "./record-properties.js";
15
+ import { CopilotBackgroundWorkState, CopilotFinalBoundaryState, isCopilotTurnInitiatingUserLine as isLiveRootSubmit, } from "./completion.js";
16
+ import { CopilotQACaptureError, extractCopilotClarifications, getCopilotSessionDir, } from "./session.js";
17
+ import { CopilotComposerInspector } from "./composer.js";
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const OBSERVER_PLUGIN_ASSET_DIR = join(__dirname, "observer-extension");
20
+ const OBSERVER_PLUGIN_TEMPLATE_NAME = "poly-weaver-copilot-observer";
21
+ const OBSERVER_EXTENSION_TEMPLATE_NAME = "poly-weaver-observer";
22
+ const OBSERVER_PLUGIN_IDENTITY_PREFIX = "poly-weaver-observer";
23
+ const COPILOT_PLUGIN_NAME_MAX_LENGTH = 64;
24
+ const COPILOT_PLUGIN_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
25
+ const COPILOT_EXTENSIONS_CONTRACT = "com.github.copilot/extensions";
26
+ const READY_TIMEOUT_MS = 60_000;
27
+ const PLAN_READ_TIMEOUT_MS = 5_000;
28
+ const PLAN_READ_RETRY_MS = 250;
29
+ const OBSERVER_DIAGNOSTIC_EVENT_TAIL_LIMIT = 80;
30
+ // Yield to timers/input and sibling launches, not just the microtask queue.
31
+ export const COPILOT_OBSERVER_WORK_QUANTUM = 128;
32
+ const OBSERVER_MAX_FRAME_CHARS = 16 * 1024 * 1024;
33
+ const OBSERVER_MAX_PENDING_CHARS = 64 * 1024 * 1024;
34
+ const OBSERVER_MAX_TENTATIVE_EVENTS = 131_072;
35
+ export const COPILOT_OBSERVER_ENV = {
36
+ endpoint: "POLY_WEAVER_COPILOT_OBSERVER_ENDPOINT",
37
+ launchId: "POLY_WEAVER_COPILOT_OBSERVER_LAUNCH_ID",
38
+ sessionId: "POLY_WEAVER_COPILOT_OBSERVER_SESSION_ID",
39
+ credential: "POLY_WEAVER_COPILOT_OBSERVER_CREDENTIAL",
40
+ runSecret: "POLY_WEAVER_COPILOT_OBSERVER_RUN_SECRET",
41
+ sdkImport: "POLY_WEAVER_COPILOT_SDK_IMPORT",
42
+ };
43
+ export class CopilotObservationBroker {
44
+ registrations = new Map();
45
+ runId = randomUUID();
46
+ runSecret = token();
47
+ server;
48
+ endpointValue;
49
+ closed = false;
50
+ static async start() {
51
+ const broker = new CopilotObservationBroker();
52
+ await broker.listen();
53
+ return broker;
54
+ }
55
+ get endpoint() {
56
+ if (!this.endpointValue) {
57
+ throw new Error("Copilot observation broker has not started");
58
+ }
59
+ return this.endpointValue;
60
+ }
61
+ registerLaunch(sessionId) {
62
+ if (this.closed) {
63
+ throw new Error("Copilot observation broker is closed");
64
+ }
65
+ const launchId = randomUUID();
66
+ const registration = new CopilotLaunchRegistration({
67
+ launchId,
68
+ sessionId,
69
+ credential: token(),
70
+ runSecret: this.runSecret,
71
+ runId: this.runId,
72
+ broker: this,
73
+ });
74
+ this.registrations.set(launchId, registration);
75
+ return registration;
76
+ }
77
+ unregisterLaunch(registration) {
78
+ if (this.registrations.get(registration.launchId) === registration) {
79
+ this.registrations.delete(registration.launchId);
80
+ }
81
+ }
82
+ async close() {
83
+ if (this.closed)
84
+ return;
85
+ this.closed = true;
86
+ for (const registration of this.registrations.values()) {
87
+ registration.dispose();
88
+ }
89
+ this.registrations.clear();
90
+ const server = this.server;
91
+ this.server = undefined;
92
+ if (!server)
93
+ return;
94
+ await new Promise((resolve, reject) => {
95
+ server.close((err) => (err ? reject(err) : resolve()));
96
+ }).catch(() => { });
97
+ if (this.endpointValue && process.platform !== "win32") {
98
+ await rm(this.endpointValue, { force: true }).catch(() => { });
99
+ }
100
+ }
101
+ async listen() {
102
+ const endpoint = await createEndpoint();
103
+ const server = createServer((socket) => this.accept(socket));
104
+ this.server = server;
105
+ this.endpointValue = endpoint;
106
+ await new Promise((resolve, reject) => {
107
+ server.once("error", reject);
108
+ server.listen(endpoint, () => {
109
+ server.off("error", reject);
110
+ resolve();
111
+ });
112
+ });
113
+ if (process.platform !== "win32") {
114
+ await chmod(endpoint, 0o600).catch(() => { });
115
+ }
116
+ }
117
+ accept(socket) {
118
+ let authed;
119
+ let chunks = [];
120
+ let cursor = 0;
121
+ let offset = 0;
122
+ let pendingChars = 0;
123
+ let parts = [];
124
+ let frameChars = 0;
125
+ let scheduled;
126
+ let ended = false;
127
+ let stopped = false;
128
+ let closeCause;
129
+ const fail = (message) => {
130
+ stopped = true;
131
+ if (scheduled)
132
+ clearImmediate(scheduled);
133
+ chunks = [];
134
+ parts = [];
135
+ const error = new Error(message);
136
+ if (authed) {
137
+ authed.protocolFailure(socket, error);
138
+ socket.destroy(error);
139
+ return;
140
+ }
141
+ sendAuthResult(socket, { accepted: false, reason: message });
142
+ socket.end();
143
+ };
144
+ const drain = () => {
145
+ scheduled = undefined;
146
+ if (stopped)
147
+ return;
148
+ if (authed && !authed.ownsConnection(socket)) {
149
+ stopped = true;
150
+ chunks = [];
151
+ parts = [];
152
+ return;
153
+ }
154
+ // Let admitted replay catch up before accepting more live frames. The
155
+ // paused socket carries this pressure back to the extension's writer.
156
+ if (authed?.isConnectionDraining(socket)) {
157
+ scheduled = setImmediate(drain);
158
+ return;
159
+ }
160
+ let work = 0;
161
+ while (cursor < chunks.length && work++ < COPILOT_OBSERVER_WORK_QUANTUM) {
162
+ const chunk = chunks[cursor];
163
+ const newline = chunk.indexOf("\n", offset);
164
+ const end = newline < 0 ? chunk.length : newline;
165
+ const fragment = chunk.slice(offset, end);
166
+ frameChars += fragment.length;
167
+ if (frameChars > OBSERVER_MAX_FRAME_CHARS) {
168
+ fail("Copilot observer frame exceeds the transport resource limit");
169
+ return;
170
+ }
171
+ parts.push(fragment);
172
+ pendingChars -= end - offset + (newline < 0 ? 0 : 1);
173
+ offset = end + 1;
174
+ if (offset >= chunk.length) {
175
+ cursor++;
176
+ offset = 0;
177
+ }
178
+ if (newline < 0)
179
+ continue;
180
+ const raw = parts.length === 1 ? parts[0] : parts.join("");
181
+ parts = [];
182
+ frameChars = 0;
183
+ if (raw.trim().length === 0)
184
+ continue;
185
+ let message;
186
+ try {
187
+ const parsed = JSON.parse(raw);
188
+ message = parseInbound(parsed);
189
+ }
190
+ catch (err) {
191
+ fail(err instanceof Error ? err.message : String(err));
192
+ return;
193
+ }
194
+ if (!authed) {
195
+ if (message.kind !== "auth") {
196
+ fail("Copilot observer sent data before authentication");
197
+ return;
198
+ }
199
+ const registration = this.registrations.get(message.launchId);
200
+ if (!registration) {
201
+ fail("Copilot observer authentication referenced an unknown launch");
202
+ return;
203
+ }
204
+ let decision;
205
+ try {
206
+ decision = registration.authenticate(message, socket);
207
+ sendAuthResult(socket, decision);
208
+ if (!decision.accepted) {
209
+ socket.end();
210
+ return;
211
+ }
212
+ }
213
+ catch (err) {
214
+ fail(err instanceof Error ? err.message : String(err));
215
+ return;
216
+ }
217
+ authed = registration;
218
+ continue;
219
+ }
220
+ try {
221
+ authed.accept(message, socket);
222
+ }
223
+ catch (err) {
224
+ fail(err instanceof Error ? err.message : String(err));
225
+ return;
226
+ }
227
+ }
228
+ if (cursor < chunks.length) {
229
+ scheduled = setImmediate(drain);
230
+ }
231
+ else {
232
+ chunks = [];
233
+ cursor = 0;
234
+ if (ended) {
235
+ authed?.finishConnection(socket, closeCause);
236
+ stopped = true;
237
+ }
238
+ else {
239
+ socket.resume();
240
+ }
241
+ }
242
+ };
243
+ socket.setEncoding("utf8");
244
+ socket.on("data", (chunk) => {
245
+ if (stopped)
246
+ return;
247
+ chunks.push(chunk);
248
+ pendingChars += chunk.length;
249
+ if (pendingChars + frameChars > OBSERVER_MAX_PENDING_CHARS) {
250
+ fail("Copilot observer pending input exceeds the transport resource limit");
251
+ return;
252
+ }
253
+ socket.pause();
254
+ scheduled ??= setImmediate(drain);
255
+ });
256
+ socket.on("close", () => {
257
+ ended = true;
258
+ if (!stopped)
259
+ scheduled ??= setImmediate(drain);
260
+ });
261
+ socket.on("error", (err) => {
262
+ closeCause = err;
263
+ });
264
+ }
265
+ }
266
+ export class CopilotLaunchRegistration {
267
+ launchId;
268
+ sessionId;
269
+ credential;
270
+ runSecret;
271
+ runId;
272
+ broker;
273
+ activeConnection;
274
+ consumed = false;
275
+ connectionAttemptCount = 0;
276
+ rejectedDuplicateCount = 0;
277
+ lifecycleEventCount = 0;
278
+ epochCount = 0;
279
+ observerReadinessTimeoutMs = READY_TIMEOUT_MS;
280
+ workspacePath;
281
+ preferredExtensionDir;
282
+ readyIdentityValue;
283
+ recoveryCause;
284
+ recoveryDeadlineAt;
285
+ recoveryTimer;
286
+ failed;
287
+ disposed = false;
288
+ events = [];
289
+ seenEventIds = new Set();
290
+ seenEventReplayKeys = new Set();
291
+ excludeInheritedHistory = false;
292
+ inheritedEventIds;
293
+ eventHandlers = new Set();
294
+ errorHandlers = new Set();
295
+ readyWaiters = new Set();
296
+ pendingRpcs = new Map();
297
+ evidence = new CopilotLiveEvidence();
298
+ constructor(opts) {
299
+ this.launchId = opts.launchId;
300
+ this.sessionId = opts.sessionId;
301
+ this.credential = opts.credential;
302
+ this.runSecret = opts.runSecret;
303
+ this.runId = opts.runId;
304
+ this.broker = opts.broker;
305
+ }
306
+ get eventCount() {
307
+ return this.events.length;
308
+ }
309
+ ownsConnection(socket) {
310
+ return !this.disposed && !this.failed && this.activeConnection?.socket === socket;
311
+ }
312
+ isConnectionDraining(socket) {
313
+ return this.activeConnection?.socket === socket && this.activeConnection.drain !== undefined;
314
+ }
315
+ snapshotEvents(startIndex = 0) {
316
+ return this.events.slice(startIndex);
317
+ }
318
+ snapshotDiagnostics() {
319
+ const connection = this.activeConnection;
320
+ const currentReady = connection !== undefined && this.isConnectionReady(connection);
321
+ return {
322
+ provider: "copilot",
323
+ runId: this.runId,
324
+ launchId: this.launchId,
325
+ sessionId: this.sessionId,
326
+ connected: connection !== undefined,
327
+ accepted: this.consumed,
328
+ ready: currentReady,
329
+ subscriptionReady: connection?.subscriptionReady ?? false,
330
+ joinedSessionReady: connection?.joinedSessionReady ?? false,
331
+ lifecycleReady: connection?.lifecycleReady ?? false,
332
+ currentEpoch: connection?.epoch ?? 0,
333
+ epochCount: this.epochCount,
334
+ replacementCount: Math.max(0, this.epochCount - 1),
335
+ recovering: this.recoveryCause !== undefined,
336
+ connectionAttemptCount: this.connectionAttemptCount,
337
+ rejectedDuplicateCount: this.rejectedDuplicateCount,
338
+ lifecycleEventCount: this.lifecycleEventCount,
339
+ eventCount: this.events.length,
340
+ lastEventSeq: connection ? connection.nextSeq - 1 : 0,
341
+ eventTail: diagnosticEventTail(this.events),
342
+ disposed: this.disposed,
343
+ ...(this.workspacePath && { workspacePath: this.workspacePath }),
344
+ ...(this.recoveryDeadlineAt !== undefined && { recoveryDeadlineAt: this.recoveryDeadlineAt }),
345
+ ...(this.recoveryCause && { retainedDisconnectReason: this.recoveryCause.message }),
346
+ ...(this.failed && { failureReason: this.failed.message }),
347
+ };
348
+ }
349
+ authenticate(message, socket) {
350
+ this.connectionAttemptCount++;
351
+ if (this.disposed) {
352
+ return {
353
+ accepted: false,
354
+ reason: "Copilot observer attempted to use a disposed launch registration",
355
+ };
356
+ }
357
+ if (this.failed) {
358
+ return {
359
+ accepted: false,
360
+ reason: "Copilot observer attempted to use a failed launch registration",
361
+ };
362
+ }
363
+ if (message.sessionId !== this.sessionId ||
364
+ message.credential !== this.credential ||
365
+ message.runSecret !== this.runSecret) {
366
+ throw new Error("Copilot observer authentication failed");
367
+ }
368
+ const incomingIsPreferred = this.isPreferredExtensionPath(message.extensionPath);
369
+ if (this.activeConnection && this.activeConnection.socket !== socket) {
370
+ this.rejectedDuplicateCount++;
371
+ return {
372
+ accepted: false,
373
+ reason: "Copilot observer duplicate activation rejected",
374
+ };
375
+ }
376
+ if (!incomingIsPreferred) {
377
+ this.rejectedDuplicateCount++;
378
+ return {
379
+ accepted: false,
380
+ reason: "Copilot observer non-authoritative activation rejected",
381
+ };
382
+ }
383
+ this.consumed = true;
384
+ this.activeConnection = {
385
+ socket,
386
+ epoch: ++this.epochCount,
387
+ nextSeq: 1,
388
+ subscriptionReady: false,
389
+ joinedSessionReady: false,
390
+ lifecycleReady: false,
391
+ bufferedEvents: [],
392
+ bufferedCursor: 0,
393
+ bufferedChars: 0,
394
+ extensionPath: message.extensionPath,
395
+ };
396
+ return {
397
+ accepted: true,
398
+ replayHistory: this.events.length > 0,
399
+ ...(this.excludeInheritedHistory && !this.inheritedEventIds && { captureHistoryBaseline: true }),
400
+ };
401
+ }
402
+ accept(message, socket) {
403
+ if (this.disposed)
404
+ return;
405
+ const connection = this.activeConnection;
406
+ if (!connection || socket !== connection.socket)
407
+ return;
408
+ if (message.kind === "auth") {
409
+ this.fail(new Error("Copilot observer attempted to authenticate twice"));
410
+ return;
411
+ }
412
+ if (message.kind === "event") {
413
+ this.acceptEvent(message, connection);
414
+ return;
415
+ }
416
+ if (message.kind === "control") {
417
+ this.acceptControl(message, connection);
418
+ return;
419
+ }
420
+ this.acceptRpcResponse(message, connection);
421
+ }
422
+ waitForReady(opts = {}) {
423
+ const timeoutMs = opts.timeoutMs ?? this.observerReadinessTimeoutMs;
424
+ if (!this.readyIdentityValue) {
425
+ this.observerReadinessTimeoutMs = timeoutMs;
426
+ }
427
+ return this.waitForCurrentReady({
428
+ ...opts,
429
+ timeoutMs,
430
+ timeoutMessage: `Timed out waiting for Copilot observer readiness for session ${this.sessionId}`,
431
+ });
432
+ }
433
+ waitForUsableConnection(opts = {}) {
434
+ return this.waitForCurrentReady({
435
+ ...opts,
436
+ timeoutMessage: `Timed out waiting for usable Copilot observer connection for session ${this.sessionId}`,
437
+ });
438
+ }
439
+ async waitForCurrentReady(opts) {
440
+ throwIfAborted(opts.signal);
441
+ if (this.failed)
442
+ return Promise.reject(this.failed);
443
+ if (this.disposed)
444
+ return Promise.reject(new Error("Copilot observer disposed"));
445
+ const ready = this.currentReadyIdentity();
446
+ if (ready)
447
+ return Promise.resolve(ready);
448
+ return new Promise((resolve, reject) => {
449
+ const waiter = {
450
+ resolve,
451
+ reject,
452
+ signal: opts.signal,
453
+ };
454
+ if (opts.timeoutMs !== undefined) {
455
+ waiter.timer = setTimeout(() => {
456
+ this.readyWaiters.delete(waiter);
457
+ cleanupReadyWaiter(waiter);
458
+ reject(new Error(opts.timeoutMessage));
459
+ }, opts.timeoutMs);
460
+ }
461
+ if (opts.signal) {
462
+ waiter.onAbort = () => {
463
+ this.readyWaiters.delete(waiter);
464
+ if (waiter.timer)
465
+ clearTimeout(waiter.timer);
466
+ cleanupReadyWaiter(waiter);
467
+ try {
468
+ throwIfAborted(opts.signal);
469
+ }
470
+ catch (err) {
471
+ reject(err instanceof Error ? err : new Error(String(err)));
472
+ }
473
+ };
474
+ opts.signal.addEventListener("abort", waiter.onAbort, { once: true });
475
+ }
476
+ this.readyWaiters.add(waiter);
477
+ });
478
+ }
479
+ onEvent(handler) {
480
+ this.eventHandlers.add(handler);
481
+ return () => {
482
+ this.eventHandlers.delete(handler);
483
+ };
484
+ }
485
+ onError(handler) {
486
+ this.errorHandlers.add(handler);
487
+ if (this.failed)
488
+ handler(this.failed);
489
+ return () => {
490
+ this.errorHandlers.delete(handler);
491
+ };
492
+ }
493
+ requestPlanRead(timeoutMs = PLAN_READ_TIMEOUT_MS) {
494
+ return this.requestRpc({
495
+ kind: "rpc.request",
496
+ requestId: randomUUID(),
497
+ method: "plan.read",
498
+ }, timeoutMs).then(parsePlanReadResult);
499
+ }
500
+ requestRpc(request, timeoutMs) {
501
+ if (this.failed)
502
+ return Promise.reject(this.failed);
503
+ if (this.disposed)
504
+ return Promise.reject(new Error("Copilot observer disposed"));
505
+ return new Promise((resolve, reject) => {
506
+ const timer = setTimeout(() => {
507
+ this.pendingRpcs.delete(request.requestId);
508
+ reject(new Error(`Timed out waiting for Copilot observer RPC ${request.method}`));
509
+ }, timeoutMs);
510
+ this.pendingRpcs.set(request.requestId, {
511
+ request,
512
+ resolve,
513
+ reject,
514
+ timer,
515
+ });
516
+ this.sendPendingRpcIfUsable(request.requestId);
517
+ });
518
+ }
519
+ disconnect(socket, cause) {
520
+ if (this.disposed || this.failed)
521
+ return;
522
+ const connection = this.activeConnection;
523
+ if (!connection || socket !== connection.socket)
524
+ return;
525
+ const launchWasReady = this.readyIdentityValue !== undefined;
526
+ if (connection.drain)
527
+ clearImmediate(connection.drain);
528
+ this.activeConnection = undefined;
529
+ for (const pending of this.pendingRpcs.values()) {
530
+ if (pending.sentEpoch === connection.epoch)
531
+ pending.sentEpoch = undefined;
532
+ }
533
+ if (!launchWasReady) {
534
+ this.workspacePath = undefined;
535
+ return;
536
+ }
537
+ this.startRecovery(this.disconnectCause(connection, cause));
538
+ }
539
+ /** Drain admitted evidence before reporting transport loss; tentative epochs are discarded. */
540
+ finishConnection(socket, cause) {
541
+ const connection = this.activeConnection;
542
+ if (!connection || connection.socket !== socket)
543
+ return;
544
+ if (connection.drain) {
545
+ connection.closed = { cause };
546
+ return;
547
+ }
548
+ this.disconnect(socket, cause);
549
+ }
550
+ protocolFailure(socket, error) {
551
+ const connection = this.activeConnection;
552
+ if (!connection || socket !== connection.socket)
553
+ return;
554
+ this.fail(error);
555
+ }
556
+ fail(error) {
557
+ if (this.failed)
558
+ return;
559
+ this.failed = error;
560
+ this.clearRecovery();
561
+ const connection = this.activeConnection;
562
+ this.activeConnection = undefined;
563
+ if (connection?.drain)
564
+ clearImmediate(connection.drain);
565
+ connection?.socket.destroy();
566
+ for (const waiter of this.readyWaiters) {
567
+ if (waiter.timer)
568
+ clearTimeout(waiter.timer);
569
+ cleanupReadyWaiter(waiter);
570
+ waiter.reject(error);
571
+ }
572
+ this.readyWaiters.clear();
573
+ for (const pending of this.pendingRpcs.values()) {
574
+ clearTimeout(pending.timer);
575
+ pending.reject(error);
576
+ }
577
+ this.pendingRpcs.clear();
578
+ for (const handler of this.errorHandlers)
579
+ handler(error);
580
+ }
581
+ dispose() {
582
+ if (this.disposed)
583
+ return;
584
+ this.disposed = true;
585
+ this.broker.unregisterLaunch(this);
586
+ const disposedError = new Error("Copilot observer disposed");
587
+ this.clearRecovery();
588
+ for (const waiter of this.readyWaiters) {
589
+ if (waiter.timer)
590
+ clearTimeout(waiter.timer);
591
+ cleanupReadyWaiter(waiter);
592
+ waiter.reject(disposedError);
593
+ }
594
+ this.readyWaiters.clear();
595
+ for (const pending of this.pendingRpcs.values()) {
596
+ clearTimeout(pending.timer);
597
+ pending.reject(disposedError);
598
+ }
599
+ this.pendingRpcs.clear();
600
+ this.eventHandlers.clear();
601
+ this.errorHandlers.clear();
602
+ if (this.activeConnection?.drain)
603
+ clearImmediate(this.activeConnection.drain);
604
+ this.activeConnection?.socket.destroy();
605
+ this.activeConnection = undefined;
606
+ }
607
+ setPreferredExtensionDir(extensionDir) {
608
+ this.preferredExtensionDir = extensionDir;
609
+ }
610
+ requireInheritedHistoryBoundary() {
611
+ this.excludeInheritedHistory = true;
612
+ }
613
+ acceptEvent(message, connection) {
614
+ if (message.seq !== connection.nextSeq) {
615
+ this.fail(new Error(`Copilot observer sequence violation for launch ${this.launchId}: expected ${connection.nextSeq}, got ${message.seq}`));
616
+ return;
617
+ }
618
+ connection.nextSeq++;
619
+ const eventId = stringProp(message.event, "id");
620
+ if (eventId && this.inheritedEventIds?.has(eventId))
621
+ return;
622
+ const historyPayloadError = validateLiveHistoryEventPayload(message.event);
623
+ if (historyPayloadError) {
624
+ this.fail(new Error(historyPayloadError));
625
+ return;
626
+ }
627
+ connection.bufferedEvents.push(message.event);
628
+ connection.bufferedChars += JSON.stringify(message.event).length;
629
+ if (connection.bufferedEvents.length - connection.bufferedCursor > OBSERVER_MAX_TENTATIVE_EVENTS
630
+ || connection.bufferedChars > OBSERVER_MAX_PENDING_CHARS) {
631
+ this.fail(new Error("Copilot observer readiness buffer exceeds the transport resource limit"));
632
+ return;
633
+ }
634
+ if (!this.updateLifecycle(message.event, connection))
635
+ return;
636
+ this.resolveReadyIfPossible(connection);
637
+ }
638
+ acceptControl(message, connection) {
639
+ if (message.type === "inquiry.history_baseline") {
640
+ if (!this.excludeInheritedHistory || this.inheritedEventIds || connection.inheritedEventIds
641
+ || !connection.joinedSessionReady || connection.subscriptionReady
642
+ || !Array.isArray(message.eventIds)
643
+ || !message.eventIds.every((id) => typeof id === "string" && id.length > 0)) {
644
+ this.fail(new Error("Copilot observer reported an invalid inherited-history boundary"));
645
+ return;
646
+ }
647
+ connection.inheritedEventIds = new Set(message.eventIds);
648
+ this.resolveReadyIfPossible(connection);
649
+ return;
650
+ }
651
+ if (message.type === "subscription.ready") {
652
+ connection.subscriptionReady = true;
653
+ this.resolveReadyIfPossible(connection);
654
+ return;
655
+ }
656
+ if (message.type === "session.joined") {
657
+ if (message.sessionId && message.sessionId !== this.sessionId) {
658
+ this.fail(new Error(`Copilot observer joined unexpected session ${message.sessionId}; expected ${this.sessionId}`));
659
+ return;
660
+ }
661
+ if (!message.sessionId) {
662
+ this.fail(new Error("Copilot observer did not report a joined session id"));
663
+ return;
664
+ }
665
+ this.workspacePath = message.workspacePath;
666
+ connection.workspacePath = message.workspacePath;
667
+ connection.joinedSessionReady = true;
668
+ this.resolveReadyIfPossible(connection);
669
+ return;
670
+ }
671
+ if (message.type === "sdk.error" || message.type === "observer.error") {
672
+ this.fail(new Error(message.error || "Copilot observer reported an error"));
673
+ }
674
+ }
675
+ acceptRpcResponse(message, connection) {
676
+ const pending = this.pendingRpcs.get(message.requestId);
677
+ if (!pending) {
678
+ this.fail(new Error(`Copilot observer returned unknown RPC request ${message.requestId}`));
679
+ return;
680
+ }
681
+ if (pending.sentEpoch !== connection.epoch)
682
+ return;
683
+ this.pendingRpcs.delete(message.requestId);
684
+ clearTimeout(pending.timer);
685
+ if (message.ok) {
686
+ pending.resolve(message.result);
687
+ }
688
+ else {
689
+ pending.reject(new Error(message.error || "Copilot observer RPC failed"));
690
+ }
691
+ }
692
+ updateLifecycle(event, connection) {
693
+ if (event.type !== "session.start" && event.type !== "session.resume") {
694
+ return true;
695
+ }
696
+ const rawSessionId = sessionIdFromEvent(event);
697
+ if (rawSessionId !== undefined && rawSessionId !== this.sessionId) {
698
+ this.fail(new Error(`Copilot observer lifecycle session mismatch: expected ${this.sessionId}, got ${rawSessionId}`));
699
+ return false;
700
+ }
701
+ connection.lifecycleReady = true;
702
+ return !this.failed;
703
+ }
704
+ resolveReadyIfPossible(connection) {
705
+ if (connection !== this.activeConnection || !this.isConnectionAdmitted(connection))
706
+ return;
707
+ if (connection.bufferedCursor < connection.bufferedEvents.length) {
708
+ connection.drain ??= setImmediate(() => this.flushConnectionEventsIfReady(connection));
709
+ return;
710
+ }
711
+ const ready = this.readyIdentityValue ?? this.createReadyIdentity();
712
+ if (this.excludeInheritedHistory && !this.inheritedEventIds) {
713
+ this.inheritedEventIds = connection.inheritedEventIds;
714
+ }
715
+ this.readyIdentityValue = ready;
716
+ this.clearRecovery();
717
+ if (connection.closed) {
718
+ this.disconnect(connection.socket, connection.closed.cause);
719
+ return;
720
+ }
721
+ for (const waiter of this.readyWaiters) {
722
+ if (waiter.timer)
723
+ clearTimeout(waiter.timer);
724
+ cleanupReadyWaiter(waiter);
725
+ waiter.resolve(ready);
726
+ }
727
+ this.readyWaiters.clear();
728
+ this.flushPendingRpcs();
729
+ }
730
+ currentReadyIdentity() {
731
+ const connection = this.activeConnection;
732
+ if (!connection || !this.isConnectionReady(connection)) {
733
+ return undefined;
734
+ }
735
+ return this.readyIdentityValue ?? this.createReadyIdentity();
736
+ }
737
+ createReadyIdentity() {
738
+ return {
739
+ sessionId: this.sessionId,
740
+ sessionFilePath: join(getCopilotSessionDir(), this.sessionId, "events.jsonl"),
741
+ lineOffset: 0,
742
+ };
743
+ }
744
+ isConnectionReady(connection) {
745
+ return this.isConnectionAdmitted(connection)
746
+ && connection.bufferedCursor === connection.bufferedEvents.length && !connection.drain;
747
+ }
748
+ isConnectionAdmitted(connection) {
749
+ return connection.subscriptionReady && connection.joinedSessionReady && connection.lifecycleReady
750
+ && (!this.excludeInheritedHistory || this.inheritedEventIds !== undefined || connection.inheritedEventIds !== undefined);
751
+ }
752
+ flushConnectionEventsIfReady(connection) {
753
+ connection.drain = undefined;
754
+ if (connection !== this.activeConnection || !this.isConnectionAdmitted(connection))
755
+ return;
756
+ const end = Math.min(connection.bufferedEvents.length, connection.bufferedCursor + COPILOT_OBSERVER_WORK_QUANTUM);
757
+ while (connection.bufferedCursor < end && connection === this.activeConnection) {
758
+ const event = connection.bufferedEvents[connection.bufferedCursor++];
759
+ const id = stringProp(event, "id");
760
+ if (id)
761
+ connection.inheritedEventIds?.delete(id);
762
+ this.commitEvent(event);
763
+ }
764
+ if (connection.bufferedCursor === connection.bufferedEvents.length) {
765
+ connection.bufferedEvents = [];
766
+ connection.bufferedCursor = 0;
767
+ connection.bufferedChars = 0;
768
+ }
769
+ this.resolveReadyIfPossible(connection);
770
+ }
771
+ commitEvent(event) {
772
+ if (event.type === "session.start" || event.type === "session.resume") {
773
+ this.lifecycleEventCount++;
774
+ }
775
+ const eventId = eventStableId(event);
776
+ const replayKey = eventReplayKey(event);
777
+ if (eventId && this.seenEventIds.has(eventId))
778
+ return;
779
+ if (replayKey && this.seenEventReplayKeys.has(replayKey))
780
+ return;
781
+ if (eventId)
782
+ this.seenEventIds.add(eventId);
783
+ if (replayKey)
784
+ this.seenEventReplayKeys.add(replayKey);
785
+ this.events.push(event);
786
+ this.evidence.accept(event, this.events.length - 1, this.sessionId);
787
+ for (const handler of this.eventHandlers)
788
+ handler(event);
789
+ }
790
+ flushPendingRpcs() {
791
+ for (const requestId of this.pendingRpcs.keys()) {
792
+ this.sendPendingRpcIfUsable(requestId);
793
+ }
794
+ }
795
+ sendPendingRpcIfUsable(requestId) {
796
+ const pending = this.pendingRpcs.get(requestId);
797
+ const connection = this.activeConnection;
798
+ if (!pending || !connection || !this.isConnectionReady(connection))
799
+ return;
800
+ if (pending.sentEpoch === connection.epoch)
801
+ return;
802
+ try {
803
+ connection.socket.write(`${JSON.stringify(pending.request)}\n`);
804
+ pending.sentEpoch = connection.epoch;
805
+ }
806
+ catch (err) {
807
+ pending.sentEpoch = undefined;
808
+ this.disconnect(connection.socket, err instanceof Error ? err : new Error(String(err)));
809
+ }
810
+ }
811
+ startRecovery(cause) {
812
+ if (this.recoveryCause || this.failed || this.disposed)
813
+ return;
814
+ this.recoveryCause = cause;
815
+ this.recoveryDeadlineAt = Date.now() + this.observerReadinessTimeoutMs;
816
+ this.recoveryTimer = setTimeout(() => {
817
+ this.recoveryTimer = undefined;
818
+ this.fail(this.recoveryCause ?? cause);
819
+ }, Math.max(0, this.recoveryDeadlineAt - Date.now()));
820
+ }
821
+ clearRecovery() {
822
+ if (this.recoveryTimer)
823
+ clearTimeout(this.recoveryTimer);
824
+ this.recoveryTimer = undefined;
825
+ this.recoveryCause = undefined;
826
+ this.recoveryDeadlineAt = undefined;
827
+ }
828
+ disconnectCause(connection, cause) {
829
+ if (cause)
830
+ return cause;
831
+ return new Error(connection.extensionPath
832
+ ? `Copilot observer disconnected before process completion (${connection.extensionPath})`
833
+ : "Copilot observer disconnected before process completion");
834
+ }
835
+ isPreferredExtensionPath(extensionPath) {
836
+ if (!this.preferredExtensionDir || !extensionPath)
837
+ return true;
838
+ return samePath(dirname(extensionPath), this.preferredExtensionDir);
839
+ }
840
+ }
841
+ export function copilotObserverPluginIdentityForLaunchId(launchId) {
842
+ const compactId = launchId.toLowerCase().replace(/-/g, "");
843
+ const identity = `${OBSERVER_PLUGIN_IDENTITY_PREFIX}-${compactId}`;
844
+ assertValidCopilotObserverPluginIdentity(identity);
845
+ return identity;
846
+ }
847
+ export class CopilotProcessObserver {
848
+ broker;
849
+ registration;
850
+ tempRoot;
851
+ pluginDir;
852
+ pluginIdentity;
853
+ extensionDir;
854
+ sessionExtensionDir;
855
+ sdkPath;
856
+ ownsBroker;
857
+ ownsTempRoot;
858
+ onDispose;
859
+ deferInitialPrompt = true;
860
+ args;
861
+ env;
862
+ disposed = false;
863
+ composer = new CopilotComposerInspector();
864
+ constructor(broker, registration, tempRoot, pluginDir, pluginIdentity, extensionDir, sessionExtensionDir, sdkPath, sdkEntryPath, ownsBroker, ownsTempRoot, onDispose) {
865
+ this.broker = broker;
866
+ this.registration = registration;
867
+ this.tempRoot = tempRoot;
868
+ this.pluginDir = pluginDir;
869
+ this.pluginIdentity = pluginIdentity;
870
+ this.extensionDir = extensionDir;
871
+ this.sessionExtensionDir = sessionExtensionDir;
872
+ this.sdkPath = sdkPath;
873
+ this.ownsBroker = ownsBroker;
874
+ this.ownsTempRoot = ownsTempRoot;
875
+ this.onDispose = onDispose;
876
+ this.args = [
877
+ "--plugin-dir",
878
+ pluginDir,
879
+ "--extension-sdk-path",
880
+ sdkPath,
881
+ "--experimental",
882
+ ];
883
+ this.env = {
884
+ ...Object.fromEntries(COPILOT_PARENT_ENV_TO_CLEAR.map((key) => [key, ""])),
885
+ [COPILOT_OBSERVER_ENV.endpoint]: broker.endpoint,
886
+ [COPILOT_OBSERVER_ENV.launchId]: registration.launchId,
887
+ [COPILOT_OBSERVER_ENV.sessionId]: registration.sessionId,
888
+ [COPILOT_OBSERVER_ENV.credential]: registration.credential,
889
+ [COPILOT_OBSERVER_ENV.runSecret]: registration.runSecret,
890
+ [COPILOT_OBSERVER_ENV.sdkImport]: pathToFileURL(sdkEntryPath).href,
891
+ };
892
+ }
893
+ static async create(opts) {
894
+ const sessionId = requiredCopilotObserverSessionId(opts);
895
+ const broker = await CopilotObservationBroker.start();
896
+ try {
897
+ const sdkAssets = await resolveCopilotObserverSdkAssets();
898
+ return await CopilotProcessObserver.createWithResources(broker, sdkAssets, sessionId, true, opts.excludeInheritedHistory);
899
+ }
900
+ catch (err) {
901
+ await broker.close();
902
+ throw err;
903
+ }
904
+ }
905
+ static async createWithSharedBroker(broker, sdkAssets, opts, onDispose) {
906
+ const sessionId = requiredCopilotObserverSessionId(opts);
907
+ return CopilotProcessObserver.createWithResources(broker, sdkAssets, sessionId, false, opts.excludeInheritedHistory, onDispose);
908
+ }
909
+ static async createWithResources(broker, sdkAssets, sessionId, ownsBroker, excludeInheritedHistory = false, onDispose) {
910
+ let pluginAssets;
911
+ let registration;
912
+ try {
913
+ registration = broker.registerLaunch(sessionId);
914
+ if (excludeInheritedHistory)
915
+ registration.requireInheritedHistoryBoundary();
916
+ const pluginIdentity = copilotObserverPluginIdentityForLaunchId(registration.launchId);
917
+ pluginAssets = await prepareCopilotObserverPluginAssets(pluginIdentity, registration.launchId, sessionId);
918
+ registration.setPreferredExtensionDir(pluginAssets.sessionExtensionDir);
919
+ return new CopilotProcessObserver(broker, registration, pluginAssets.tempRoot, pluginAssets.pluginDir, pluginAssets.pluginIdentity, pluginAssets.extensionDir, pluginAssets.sessionExtensionDir, sdkAssets.sdkPath, sdkAssets.sdkEntryPath, ownsBroker, true, onDispose);
920
+ }
921
+ catch (err) {
922
+ registration?.dispose();
923
+ if (pluginAssets) {
924
+ await cleanupCopilotObserverPluginAssets(pluginAssets);
925
+ }
926
+ throw err;
927
+ }
928
+ }
929
+ get launchId() {
930
+ return this.registration.launchId;
931
+ }
932
+ get sessionId() {
933
+ return this.registration.sessionId;
934
+ }
935
+ waitForReady(_handle, opts) {
936
+ return this.registration.waitForReady(opts);
937
+ }
938
+ async submitPrompt(target, prompt, opts = {}) {
939
+ await this.registration.waitForUsableConnection({ signal: opts.signal });
940
+ await sendComposerText(target, prompt, this.composer, {
941
+ signal: opts.signal,
942
+ escapeBeforePaste: true,
943
+ beforeSubmit: async () => {
944
+ await this.registration.waitForUsableConnection({ signal: opts.signal });
945
+ },
946
+ });
947
+ }
948
+ createDetector(opts) {
949
+ return new CopilotLiveCompletionDetector(this.registration, opts);
950
+ }
951
+ createTurnMonitor(opts) {
952
+ return new CopilotLiveTurnMonitor(this.registration, opts);
953
+ }
954
+ onError(handler) {
955
+ return this.registration.onError(handler);
956
+ }
957
+ snapshotDiagnostics() {
958
+ return {
959
+ ...this.registration.snapshotDiagnostics(),
960
+ observerPluginName: this.pluginIdentity,
961
+ observerPluginDir: this.pluginDir,
962
+ observerExtensionDir: this.extensionDir,
963
+ observerSessionExtensionDir: this.sessionExtensionDir,
964
+ sdkPath: this.sdkPath,
965
+ };
966
+ }
967
+ async dispose() {
968
+ if (this.disposed)
969
+ return;
970
+ this.disposed = true;
971
+ this.onDispose?.(this);
972
+ this.registration.dispose();
973
+ if (this.ownsBroker)
974
+ await this.broker.close();
975
+ await Promise.all([
976
+ this.ownsTempRoot
977
+ ? rm(this.tempRoot, { recursive: true, force: true }).catch(() => { })
978
+ : Promise.resolve(),
979
+ rm(this.sessionExtensionDir, { recursive: true, force: true }).catch(() => { }),
980
+ ]);
981
+ }
982
+ }
983
+ export class CopilotObserverRun {
984
+ provider = "copilot";
985
+ disposed = false;
986
+ observers = new Set();
987
+ sharedResourcesValue;
988
+ sharedResourcesPromise;
989
+ constructor() { }
990
+ static async create() {
991
+ return new CopilotObserverRun();
992
+ }
993
+ async prepareLaunchObserver(opts) {
994
+ if (this.disposed) {
995
+ throw disposedCopilotObserverRunError();
996
+ }
997
+ requiredCopilotObserverSessionId(opts);
998
+ const resources = await this.sharedResources();
999
+ const observer = await CopilotProcessObserver.createWithSharedBroker(resources.broker, resources.sdkAssets, opts, (observer) => this.observers.delete(observer));
1000
+ if (this.disposed) {
1001
+ await observer.dispose();
1002
+ throw disposedCopilotObserverRunError();
1003
+ }
1004
+ this.observers.add(observer);
1005
+ return observer;
1006
+ }
1007
+ async dispose() {
1008
+ if (this.disposed)
1009
+ return;
1010
+ this.disposed = true;
1011
+ const observers = [...this.observers];
1012
+ this.observers.clear();
1013
+ await Promise.all(observers.map((observer) => observer.dispose()));
1014
+ let resources = this.sharedResourcesValue;
1015
+ if (this.sharedResourcesPromise) {
1016
+ try {
1017
+ resources = await this.sharedResourcesPromise;
1018
+ }
1019
+ catch {
1020
+ // Initialization failure is reported to the launch caller.
1021
+ }
1022
+ }
1023
+ this.sharedResourcesValue = undefined;
1024
+ this.sharedResourcesPromise = undefined;
1025
+ await resources?.broker.close();
1026
+ }
1027
+ sharedResources() {
1028
+ if (this.disposed) {
1029
+ return Promise.reject(disposedCopilotObserverRunError());
1030
+ }
1031
+ if (this.sharedResourcesValue) {
1032
+ return Promise.resolve(this.sharedResourcesValue);
1033
+ }
1034
+ this.sharedResourcesPromise ??= this.initializeSharedResources().catch((err) => {
1035
+ this.sharedResourcesPromise = undefined;
1036
+ throw err;
1037
+ });
1038
+ return this.sharedResourcesPromise.then(async (resources) => {
1039
+ if (!this.disposed)
1040
+ return resources;
1041
+ if (this.sharedResourcesValue === resources) {
1042
+ this.sharedResourcesValue = undefined;
1043
+ }
1044
+ await resources.broker.close();
1045
+ throw disposedCopilotObserverRunError();
1046
+ });
1047
+ }
1048
+ async initializeSharedResources() {
1049
+ const broker = await CopilotObservationBroker.start();
1050
+ try {
1051
+ const sdkAssets = await resolveCopilotObserverSdkAssets();
1052
+ if (this.disposed) {
1053
+ throw disposedCopilotObserverRunError();
1054
+ }
1055
+ const resources = { broker, sdkAssets };
1056
+ this.sharedResourcesValue = resources;
1057
+ return resources;
1058
+ }
1059
+ catch (err) {
1060
+ await broker.close();
1061
+ throw err;
1062
+ }
1063
+ }
1064
+ }
1065
+ export function createCopilotObserverRun() {
1066
+ return CopilotObserverRun.create();
1067
+ }
1068
+ async function resolveCopilotObserverSdkAssets() {
1069
+ const sdkRequireEntry = createRequire(import.meta.url).resolve("@github/copilot-sdk/extension");
1070
+ const sdkPath = sdkDistDir(sdkRequireEntry);
1071
+ return {
1072
+ sdkPath,
1073
+ sdkEntryPath: join(sdkPath, "extension.js"),
1074
+ };
1075
+ }
1076
+ async function prepareCopilotObserverPluginAssets(pluginIdentity, launchId, sessionId) {
1077
+ assertValidCopilotObserverPluginIdentity(pluginIdentity);
1078
+ assertSafeCopilotObserverPathSegment(sessionId, "session id");
1079
+ const tempRoot = await mkdtemp(join(tmpdir(), "poly-weaver-copilot-observer-"));
1080
+ const pluginDir = join(tempRoot, pluginIdentity);
1081
+ const sessionExtensionDir = copilotObserverSessionExtensionDir(sessionId, pluginIdentity);
1082
+ try {
1083
+ const extensionDir = await materializeCopilotObserverPlugin(pluginDir, pluginIdentity, launchId);
1084
+ await materializeCopilotObserverSessionExtensionDir(extensionDir, sessionExtensionDir);
1085
+ return {
1086
+ tempRoot,
1087
+ pluginDir,
1088
+ pluginIdentity,
1089
+ extensionDir,
1090
+ sessionExtensionDir,
1091
+ };
1092
+ }
1093
+ catch (err) {
1094
+ await Promise.allSettled([
1095
+ rm(tempRoot, { recursive: true, force: true }),
1096
+ rm(sessionExtensionDir, { recursive: true, force: true }),
1097
+ ]);
1098
+ throw err;
1099
+ }
1100
+ }
1101
+ async function materializeCopilotObserverPlugin(pluginDir, pluginIdentity, launchId) {
1102
+ assertValidCopilotObserverPluginIdentity(pluginIdentity);
1103
+ await cp(OBSERVER_PLUGIN_ASSET_DIR, pluginDir, { recursive: true });
1104
+ await rewriteCopilotObserverPluginManifest(pluginDir, pluginIdentity);
1105
+ return await materializeCopilotObserverExtensionDir(pluginDir, pluginIdentity, launchId);
1106
+ }
1107
+ async function rewriteCopilotObserverPluginManifest(pluginDir, pluginIdentity) {
1108
+ const manifestPath = join(pluginDir, "plugin.json");
1109
+ const raw = await readFile(manifestPath, "utf-8");
1110
+ const manifest = JSON.parse(raw);
1111
+ if (manifest.name !== OBSERVER_PLUGIN_TEMPLATE_NAME) {
1112
+ throw new Error("Copilot observer plugin template has an unexpected name");
1113
+ }
1114
+ if (typeof manifest.description !== "string" || manifest.description.length === 0) {
1115
+ throw new Error("Copilot observer plugin template is missing description");
1116
+ }
1117
+ if (typeof manifest.version !== "string" || manifest.version.length === 0) {
1118
+ throw new Error("Copilot observer plugin template is missing version");
1119
+ }
1120
+ if (manifest.extensions !== COPILOT_EXTENSIONS_CONTRACT) {
1121
+ throw new Error("Copilot observer plugin template has an unexpected extensions contract");
1122
+ }
1123
+ await writeFile(manifestPath, `${JSON.stringify({
1124
+ name: pluginIdentity,
1125
+ description: manifest.description,
1126
+ version: manifest.version,
1127
+ extensions: manifest.extensions,
1128
+ }, null, 2)}\n`, "utf-8");
1129
+ }
1130
+ async function materializeCopilotObserverExtensionDir(pluginDir, pluginIdentity, launchId) {
1131
+ const extensionsRoot = join(pluginDir, "com.github.copilot", "extensions");
1132
+ const sourceDir = join(extensionsRoot, OBSERVER_EXTENSION_TEMPLATE_NAME);
1133
+ const targetDir = join(extensionsRoot, pluginIdentity);
1134
+ if (sourceDir !== targetDir) {
1135
+ await rename(sourceDir, targetDir);
1136
+ }
1137
+ const entrypoint = join(targetDir, "extension.mjs");
1138
+ const source = await readFile(entrypoint, "utf-8");
1139
+ await writeFile(entrypoint, `const polyWeaverExpectedLaunchId = ${JSON.stringify(launchId)};\n` +
1140
+ `if (process.env.POLY_WEAVER_COPILOT_OBSERVER_LAUNCH_ID !== polyWeaverExpectedLaunchId) process.exit(0);\n` +
1141
+ source, "utf-8");
1142
+ return targetDir;
1143
+ }
1144
+ async function materializeCopilotObserverSessionExtensionDir(sourceDir, targetDir) {
1145
+ await rm(targetDir, { recursive: true, force: true }).catch(() => { });
1146
+ await mkdir(dirname(targetDir), { recursive: true });
1147
+ await cp(sourceDir, targetDir, { recursive: true });
1148
+ }
1149
+ function copilotObserverSessionExtensionDir(sessionId, pluginIdentity) {
1150
+ assertSafeCopilotObserverPathSegment(sessionId, "session id");
1151
+ assertValidCopilotObserverPluginIdentity(pluginIdentity);
1152
+ return join(getCopilotSessionDir(), sessionId, "extensions", pluginIdentity);
1153
+ }
1154
+ async function cleanupCopilotObserverPluginAssets(assets) {
1155
+ await Promise.all([
1156
+ rm(assets.tempRoot, { recursive: true, force: true }).catch(() => { }),
1157
+ rm(assets.sessionExtensionDir, { recursive: true, force: true }).catch(() => { }),
1158
+ ]);
1159
+ }
1160
+ function assertValidCopilotObserverPluginIdentity(identity) {
1161
+ if (identity.length > COPILOT_PLUGIN_NAME_MAX_LENGTH
1162
+ || !COPILOT_PLUGIN_NAME_PATTERN.test(identity)) {
1163
+ throw new Error(`Invalid Copilot observer plugin identity ${JSON.stringify(identity)}`);
1164
+ }
1165
+ }
1166
+ function assertSafeCopilotObserverPathSegment(value, label) {
1167
+ if (value.length === 0
1168
+ || value === "."
1169
+ || value === ".."
1170
+ || value.includes("/")
1171
+ || value.includes("\\")
1172
+ || value.includes(":")) {
1173
+ throw new Error(`Invalid Copilot observer ${label} ${JSON.stringify(value)}`);
1174
+ }
1175
+ }
1176
+ function samePath(left, right) {
1177
+ const normalizedLeft = normalize(left);
1178
+ const normalizedRight = normalize(right);
1179
+ return process.platform === "win32"
1180
+ ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
1181
+ : normalizedLeft === normalizedRight;
1182
+ }
1183
+ function sendAuthResult(socket, decision) {
1184
+ const message = decision.accepted
1185
+ ? {
1186
+ kind: "auth.result",
1187
+ accepted: true,
1188
+ ...(decision.replayHistory && { replayHistory: true }),
1189
+ ...(decision.captureHistoryBaseline && { captureHistoryBaseline: true }),
1190
+ }
1191
+ : { kind: "auth.result", accepted: false, reason: decision.reason };
1192
+ socket.write(`${JSON.stringify(message)}\n`);
1193
+ }
1194
+ function requiredCopilotObserverSessionId(opts) {
1195
+ const sessionId = opts.resumeSessionId ?? opts.sessionId;
1196
+ if (!sessionId) {
1197
+ throw new Error("Copilot observation requires a pre-assigned or resume session id");
1198
+ }
1199
+ return sessionId;
1200
+ }
1201
+ function disposedCopilotObserverRunError() {
1202
+ return new Error("Copilot observer run has been disposed");
1203
+ }
1204
+ /** Launch-local authority. Snapshots/materialized output are explicit operations. */
1205
+ class CopilotLiveEvidence {
1206
+ rewriteGeneration = 0;
1207
+ submitIdentities = [];
1208
+ removedSubmitIdentities = [];
1209
+ submitIndex = -1;
1210
+ abortIndex = -1;
1211
+ assistantIndex = -1;
1212
+ idleIndex = -1;
1213
+ background = new CopilotBackgroundWorkState();
1214
+ boundary = new CopilotFinalBoundaryState();
1215
+ boundarySettled = false;
1216
+ get submitCount() { return this.submitIdentities.length; }
1217
+ get settled() {
1218
+ return this.submitIndex >= 0 && ((this.boundary.latest !== undefined && this.boundarySettled)
1219
+ || (this.assistantIndex > this.submitIndex && this.idleIndex > this.assistantIndex));
1220
+ }
1221
+ accept(event, index, sessionId) {
1222
+ if (isLiveHistoryRewriteEvent(event)) {
1223
+ this.rewriteGeneration++;
1224
+ this.removedSubmitIdentities = this.submitIdentities;
1225
+ this.submitIdentities = [];
1226
+ this.resetTurn();
1227
+ return;
1228
+ }
1229
+ if (isLiveRootSubmit(event)) {
1230
+ this.resetTurn();
1231
+ this.submitIndex = index;
1232
+ this.submitIdentities.push(liveSubmitIdentity(event, index, this.rewriteGeneration));
1233
+ }
1234
+ if (isAbortEvent(event) && this.submitIndex >= 0)
1235
+ this.abortIndex = index;
1236
+ if (!isAgentScoped(event) && event.type === "assistant.message"
1237
+ && stringProp(recordProp(event, "data"), "content"))
1238
+ this.assistantIndex = index;
1239
+ if (isSessionIdle(event, sessionId))
1240
+ this.idleIndex = index;
1241
+ this.background.accept(event);
1242
+ if (this.boundary.accept(event, index, sessionId)) {
1243
+ // Later background completion cannot validate an earlier stop.
1244
+ this.boundarySettled = this.background.size === 0;
1245
+ }
1246
+ }
1247
+ snapshot(scope) {
1248
+ const submitIdentities = [...this.submitIdentities];
1249
+ return {
1250
+ value: {
1251
+ kind: "copilot-live-observer-v2",
1252
+ launchId: scope.launchId,
1253
+ sessionId: scope.sessionId,
1254
+ rewriteGeneration: this.rewriteGeneration,
1255
+ submitIdentities,
1256
+ lineCount: scope.eventCount,
1257
+ },
1258
+ submitCount: submitIdentities.length,
1259
+ submitIdentities,
1260
+ lineCount: scope.eventCount,
1261
+ };
1262
+ }
1263
+ resetTurn() {
1264
+ this.submitIndex = -1;
1265
+ this.abortIndex = -1;
1266
+ this.assistantIndex = -1;
1267
+ this.idleIndex = -1;
1268
+ this.background = new CopilotBackgroundWorkState();
1269
+ this.boundary = new CopilotFinalBoundaryState();
1270
+ this.boundarySettled = false;
1271
+ }
1272
+ }
1273
+ class CopilotLiveTurnMonitor {
1274
+ registration;
1275
+ opts;
1276
+ unsubscribeEvent;
1277
+ unsubscribeError;
1278
+ busy = false;
1279
+ stopped = false;
1280
+ coveredSubmitCount = 0;
1281
+ expectedSubmitCount = 0;
1282
+ observedSubmitCount = 0;
1283
+ pendingAttempts = [];
1284
+ entries = new Map();
1285
+ attached = new WeakSet();
1286
+ highestOrdinal = 0;
1287
+ pendingAttemptWaiters = new Set();
1288
+ attemptTimeoutMs;
1289
+ historySnapshot;
1290
+ historyRevision;
1291
+ abort = new AbortController();
1292
+ constructor(registration, opts = {}) {
1293
+ this.registration = registration;
1294
+ this.opts = opts;
1295
+ this.attemptTimeoutMs = opts.attemptTimeoutMs ?? 5_000;
1296
+ this.historySnapshot = opts.initialHistorySnapshot;
1297
+ this.refreshCounts();
1298
+ this.unsubscribeEvent = registration.onEvent(() => this.onEvent());
1299
+ this.unsubscribeError = registration.onError((error) => {
1300
+ if (this.stopped)
1301
+ return;
1302
+ this.busy = true;
1303
+ this.opts.onReadFailure?.(error);
1304
+ this.opts.onChange?.(false);
1305
+ });
1306
+ }
1307
+ get isIdle() {
1308
+ return !this.busy;
1309
+ }
1310
+ get lineBaseline() {
1311
+ return this.coveredSubmitCount;
1312
+ }
1313
+ get currentSubmitFloor() {
1314
+ return this.coveredSubmitCount;
1315
+ }
1316
+ get supportsHistoryRegression() {
1317
+ return true;
1318
+ }
1319
+ startHistoryPolling() {
1320
+ this.reconcileLiveHistory();
1321
+ }
1322
+ reserveTurn(boundary = {}) {
1323
+ const receipt = new SessionTurnReceipt({
1324
+ ...boundary,
1325
+ anchorSubmitCount: boundary.anchorSubmitCount ?? this.observedSubmitCount,
1326
+ });
1327
+ this.attachTurn(receipt);
1328
+ return receipt;
1329
+ }
1330
+ async reserveTurnFromSession() {
1331
+ await this.registration.waitForUsableConnection({ signal: this.abort.signal });
1332
+ this.refreshCounts();
1333
+ this.reconcileLiveHistory();
1334
+ return this.reserveTurn();
1335
+ }
1336
+ attachTurns(receipts) {
1337
+ for (const receipt of [...receipts].sort((a, b) => a.sequence - b.sequence)) {
1338
+ this.attachTurn(receipt);
1339
+ }
1340
+ }
1341
+ attachTurn(receipt) {
1342
+ if (this.stopped || this.attached.has(receipt) || receipt.settlement)
1343
+ return;
1344
+ this.attached.add(receipt);
1345
+ const entry = { receipt };
1346
+ this.entries.set(receipt.id, entry);
1347
+ entry.unsubscribe = receipt.onChange(() => {
1348
+ if (receipt.settlement === "released" && entry.ordinal === undefined) {
1349
+ this.removeEntry(entry);
1350
+ this.evaluateSettlement();
1351
+ }
1352
+ });
1353
+ if (receipt.inputWritten && !receipt.rawAttempt)
1354
+ this.confirmTurn(entry);
1355
+ }
1356
+ beginTurn(receipt) {
1357
+ if (!receipt) {
1358
+ const adopted = [...this.entries.values()].reverse().find((entry) => entry.receipt.rawAttempt
1359
+ && entry.ordinal !== undefined
1360
+ && entry.ordinal > this.coveredSubmitCount);
1361
+ if (adopted)
1362
+ return adopted.receipt;
1363
+ receipt = this.reserveTurn();
1364
+ }
1365
+ if (this.stopped)
1366
+ return receipt;
1367
+ this.attachTurn(receipt);
1368
+ receipt.confirm();
1369
+ const entry = this.entries.get(receipt.id);
1370
+ if (entry)
1371
+ this.confirmTurn(entry);
1372
+ return receipt;
1373
+ }
1374
+ confirmTurn(entry) {
1375
+ if (entry.ordinal !== undefined || entry.receipt.settlement)
1376
+ return;
1377
+ this.refreshCounts();
1378
+ entry.ordinal = Math.max(this.highestOrdinal + 1, this.observedSubmitCount > this.coveredSubmitCount
1379
+ ? this.observedSubmitCount
1380
+ : this.coveredSubmitCount + 1);
1381
+ this.highestOrdinal = entry.ordinal;
1382
+ this.expectedSubmitCount = Math.max(this.expectedSubmitCount, entry.ordinal);
1383
+ this.markBusy();
1384
+ this.evaluateSettlement();
1385
+ }
1386
+ beginTurnAttempt(snapshot = {}) {
1387
+ const receipt = new SessionTurnReceipt({
1388
+ ...snapshot,
1389
+ anchorSubmitCount: snapshot.anchorSubmitCount ?? this.observedSubmitCount,
1390
+ }, true);
1391
+ receipt.markWritten();
1392
+ this.attachTurn(receipt);
1393
+ if (this.stopped)
1394
+ return receipt;
1395
+ this.refreshCounts();
1396
+ const startedAt = snapshot.startedAt ?? Date.now();
1397
+ const elapsed = Date.now() - startedAt;
1398
+ if (elapsed >= this.attemptTimeoutMs) {
1399
+ receipt.settle("expired");
1400
+ const entry = this.entries.get(receipt.id);
1401
+ if (entry)
1402
+ this.removeEntry(entry);
1403
+ this.evaluateSettlement({ notifyIfAlreadyIdle: true });
1404
+ return receipt;
1405
+ }
1406
+ const attempt = {
1407
+ anchorSubmitCount: snapshot.anchorSubmitCount ?? this.observedSubmitCount,
1408
+ receipt,
1409
+ };
1410
+ attempt.timer = setTimeout(() => {
1411
+ const index = this.pendingAttempts.indexOf(attempt);
1412
+ if (index >= 0)
1413
+ this.pendingAttempts.splice(index, 1);
1414
+ attempt.receipt.settle("expired");
1415
+ const entry = this.entries.get(attempt.receipt.id);
1416
+ if (entry)
1417
+ this.removeEntry(entry);
1418
+ this.resolvePendingAttemptWaitersIfSettled();
1419
+ this.evaluateSettlement();
1420
+ }, Math.max(0, this.attemptTimeoutMs - elapsed));
1421
+ this.pendingAttempts.push(attempt);
1422
+ this.markBusy();
1423
+ return receipt;
1424
+ }
1425
+ waitForPendingTurnAttempts() {
1426
+ if (this.pendingAttempts.length === 0 || this.stopped)
1427
+ return Promise.resolve();
1428
+ return new Promise((resolve) => {
1429
+ this.pendingAttemptWaiters.add(resolve);
1430
+ });
1431
+ }
1432
+ async expectedSubmitCountFromSession() {
1433
+ await this.registration.waitForUsableConnection({ signal: this.abort.signal });
1434
+ this.refreshCounts();
1435
+ return this.observedSubmitCount;
1436
+ }
1437
+ refreshExpiredAttempts() {
1438
+ return Promise.resolve();
1439
+ }
1440
+ async refreshIdleBaseline() {
1441
+ await this.registration.waitForUsableConnection({ signal: this.abort.signal });
1442
+ this.refreshCounts();
1443
+ this.coveredSubmitCount = this.observedSubmitCount;
1444
+ this.expectedSubmitCount = Math.max(this.expectedSubmitCount, this.coveredSubmitCount);
1445
+ this.reconcileLiveHistory();
1446
+ }
1447
+ classifyDeliverySince(baseline) {
1448
+ this.refreshCounts();
1449
+ if (baseline < 0)
1450
+ return "unknown";
1451
+ return this.observedSubmitCount > baseline ? "present" : "absent";
1452
+ }
1453
+ dispose() {
1454
+ this.abort.abort(new Error("Copilot turn monitor disposed"));
1455
+ this.unsubscribeEvent();
1456
+ this.unsubscribeError();
1457
+ for (const attempt of this.pendingAttempts) {
1458
+ if (attempt.timer)
1459
+ clearTimeout(attempt.timer);
1460
+ }
1461
+ this.pendingAttempts.length = 0;
1462
+ for (const entry of this.entries.values())
1463
+ entry.unsubscribe?.();
1464
+ this.entries.clear();
1465
+ this.resolvePendingAttemptWaiters();
1466
+ }
1467
+ shutdown() {
1468
+ this.stopped = true;
1469
+ this.dispose();
1470
+ return Promise.resolve();
1471
+ }
1472
+ onEvent() {
1473
+ if (this.stopped)
1474
+ return;
1475
+ const previousObserved = this.observedSubmitCount;
1476
+ this.refreshCounts();
1477
+ this.reconcileLiveHistory();
1478
+ if (this.observedSubmitCount > previousObserved) {
1479
+ this.adoptObservedSubmits(previousObserved);
1480
+ }
1481
+ this.evaluateSettlement();
1482
+ }
1483
+ adoptObservedSubmits(previousObserved) {
1484
+ for (let i = this.pendingAttempts.length - 1; i >= 0; i--) {
1485
+ const attempt = this.pendingAttempts[i];
1486
+ if (this.observedSubmitCount <= attempt.anchorSubmitCount)
1487
+ continue;
1488
+ if (attempt.timer)
1489
+ clearTimeout(attempt.timer);
1490
+ const entry = this.entries.get(attempt.receipt.id);
1491
+ if (entry && entry.ordinal === undefined) {
1492
+ entry.ordinal = attempt.anchorSubmitCount + 1;
1493
+ this.highestOrdinal = Math.max(this.highestOrdinal, entry.ordinal);
1494
+ this.expectedSubmitCount = Math.max(this.expectedSubmitCount, entry.ordinal);
1495
+ }
1496
+ this.pendingAttempts.splice(i, 1);
1497
+ }
1498
+ this.resolvePendingAttemptWaitersIfSettled();
1499
+ if (this.expectedSubmitCount < this.observedSubmitCount) {
1500
+ this.expectedSubmitCount = this.observedSubmitCount;
1501
+ }
1502
+ if (this.observedSubmitCount > previousObserved)
1503
+ this.markBusy();
1504
+ }
1505
+ refreshCounts() {
1506
+ this.observedSubmitCount = this.registration.evidence.submitCount;
1507
+ }
1508
+ evaluateSettlement(options = {}) {
1509
+ const evidence = this.registration.evidence;
1510
+ this.observedSubmitCount = evidence.submitCount;
1511
+ if (this.observedSubmitCount > this.coveredSubmitCount
1512
+ && (evidence.settled || evidence.abortIndex >= 0)) {
1513
+ this.coveredSubmitCount = this.observedSubmitCount;
1514
+ }
1515
+ const settlement = evidence.abortIndex >= 0 ? "cancelled" : "completed";
1516
+ for (const entry of this.entries.values()) {
1517
+ if (entry.ordinal !== undefined
1518
+ && entry.ordinal <= this.coveredSubmitCount
1519
+ && !entry.receipt.settlement) {
1520
+ entry.receipt.settle(settlement);
1521
+ this.removeEntry(entry);
1522
+ }
1523
+ }
1524
+ if (this.coveredSubmitCount < this.expectedSubmitCount
1525
+ || this.pendingAttempts.length > 0) {
1526
+ return;
1527
+ }
1528
+ const wasBusy = this.busy;
1529
+ this.busy = false;
1530
+ if ((wasBusy || options.notifyIfAlreadyIdle === true) && !this.stopped) {
1531
+ this.opts.onChange?.(true);
1532
+ }
1533
+ }
1534
+ reconcileLiveHistory() {
1535
+ const evidence = this.registration.evidence;
1536
+ if (this.historyRevision === evidence.rewriteGeneration)
1537
+ return;
1538
+ if (this.historyRevision === undefined && !this.historySnapshot && evidence.rewriteGeneration === 0) {
1539
+ this.historyRevision = 0;
1540
+ return;
1541
+ }
1542
+ const current = evidence.snapshot(this.registration);
1543
+ const previous = this.historySnapshot;
1544
+ const previousRevision = this.historyRevision;
1545
+ this.historyRevision = evidence.rewriteGeneration;
1546
+ this.historySnapshot = undefined;
1547
+ const comparable = previous && compatibleLiveHistorySnapshot(previous, current);
1548
+ // An incompatible checkpoint says nothing about this launch's work. Start
1549
+ // at its logical origin so even rewrites accepted before monitor creation count.
1550
+ const baseline = comparable ? previous : new CopilotLiveEvidence().snapshot({
1551
+ launchId: this.registration.launchId, sessionId: this.registration.sessionId, eventCount: 0,
1552
+ });
1553
+ const regression = previousRevision === undefined
1554
+ ? compareLiveHistorySnapshots(baseline, current)
1555
+ : {
1556
+ commonSubmitCount: 0, currentSubmitCount: current.submitCount,
1557
+ currentLineCount: current.lineCount,
1558
+ removedSubmitIdentities: evidence.removedSubmitIdentities,
1559
+ };
1560
+ if (!regression)
1561
+ return;
1562
+ this.coveredSubmitCount = Math.min(this.coveredSubmitCount, regression.commonSubmitCount);
1563
+ this.expectedSubmitCount = Math.min(this.expectedSubmitCount, regression.commonSubmitCount);
1564
+ this.observedSubmitCount = Math.min(this.observedSubmitCount, regression.commonSubmitCount);
1565
+ this.highestOrdinal = Math.min(this.highestOrdinal, regression.commonSubmitCount);
1566
+ for (const attempt of this.pendingAttempts) {
1567
+ attempt.anchorSubmitCount = Math.min(attempt.anchorSubmitCount, regression.commonSubmitCount);
1568
+ }
1569
+ for (const entry of this.entries.values()) {
1570
+ if (entry.ordinal !== undefined && entry.ordinal > regression.commonSubmitCount) {
1571
+ this.removeEntry(entry);
1572
+ entry.receipt.settle("removed");
1573
+ }
1574
+ }
1575
+ this.opts.onHistoryRegression?.(regression, current);
1576
+ }
1577
+ markBusy() {
1578
+ if (this.busy)
1579
+ return;
1580
+ this.busy = true;
1581
+ this.opts.onChange?.(false);
1582
+ }
1583
+ resolvePendingAttemptWaitersIfSettled() {
1584
+ if (this.pendingAttempts.length === 0)
1585
+ this.resolvePendingAttemptWaiters();
1586
+ }
1587
+ resolvePendingAttemptWaiters() {
1588
+ for (const resolve of this.pendingAttemptWaiters)
1589
+ resolve();
1590
+ this.pendingAttemptWaiters.clear();
1591
+ }
1592
+ removeEntry(entry) {
1593
+ entry.unsubscribe?.();
1594
+ this.entries.delete(entry.receipt.id);
1595
+ }
1596
+ }
1597
+ export async function prepareCopilotLaunchObserver(opts) {
1598
+ return CopilotProcessObserver.create(opts);
1599
+ }
1600
+ export class CopilotLiveCompletionDetector {
1601
+ registration;
1602
+ opts;
1603
+ unsubscribeEvent;
1604
+ unsubscribeError;
1605
+ markerTimer;
1606
+ resolve;
1607
+ reject;
1608
+ startIndex = 0;
1609
+ completed = false;
1610
+ activationFired = false;
1611
+ nativePlan;
1612
+ disposed = false;
1613
+ readingPlan = false;
1614
+ abort = new AbortController();
1615
+ responseParts = [];
1616
+ captureEvents = [];
1617
+ captureCalls = new Set();
1618
+ constructor(registration, opts = {}) {
1619
+ this.registration = registration;
1620
+ this.opts = opts;
1621
+ }
1622
+ start(_sessionFile, lineOffset = 0) {
1623
+ this.startIndex = Math.max(this.registration.eventCount, lineOffset);
1624
+ return new Promise((resolve, reject) => {
1625
+ this.resolve = resolve;
1626
+ this.reject = reject;
1627
+ this.unsubscribeEvent = this.registration.onEvent((event) => {
1628
+ void this.onEvent(event);
1629
+ });
1630
+ this.unsubscribeError = this.registration.onError((error) => {
1631
+ this.finishError(error);
1632
+ });
1633
+ if (this.opts.completionMarkerFile) {
1634
+ this.markerTimer = setInterval(() => this.checkMarker(), 100);
1635
+ this.checkMarker();
1636
+ }
1637
+ void this.evaluate();
1638
+ });
1639
+ }
1640
+ onPtyData(_data) {
1641
+ // Completion is event-driven for Copilot live observation.
1642
+ }
1643
+ dispose() {
1644
+ this.disposed = true;
1645
+ this.abort.abort(new Error("Copilot completion detector disposed"));
1646
+ this.unsubscribeEvent?.();
1647
+ this.unsubscribeError?.();
1648
+ this.unsubscribeEvent = undefined;
1649
+ this.unsubscribeError = undefined;
1650
+ if (this.markerTimer)
1651
+ clearInterval(this.markerTimer);
1652
+ this.markerTimer = undefined;
1653
+ }
1654
+ getClarifications() {
1655
+ try {
1656
+ return extractCopilotClarifications(this.captureEvents);
1657
+ }
1658
+ catch (err) {
1659
+ if (err instanceof CopilotQACaptureError)
1660
+ throw err;
1661
+ throw err instanceof Error ? err : new Error(String(err));
1662
+ }
1663
+ }
1664
+ getNativePlanResult() {
1665
+ return this.nativePlan;
1666
+ }
1667
+ async onEvent(event) {
1668
+ if (this.completed || this.disposed || this.registration.eventCount <= this.startIndex)
1669
+ return;
1670
+ this.fireActivationIfNeeded();
1671
+ if (isLiveRootSubmit(event) || isLiveHistoryRewriteEvent(event))
1672
+ this.responseParts = [];
1673
+ const data = recordProp(event, "data");
1674
+ if (!isAgentScoped(event) && event.type === "assistant.message") {
1675
+ const content = stringProp(data, "content");
1676
+ if (content)
1677
+ this.responseParts.push(content);
1678
+ }
1679
+ const callId = stringProp(data, "toolCallId");
1680
+ if (event.type === "tool.execution_start" && data?.toolName === "ask_user") {
1681
+ this.captureEvents.push(event);
1682
+ if (callId)
1683
+ this.captureCalls.add(callId);
1684
+ }
1685
+ else if (event.type === "tool.execution_complete" && callId && this.captureCalls.delete(callId)) {
1686
+ this.captureEvents.push(event);
1687
+ }
1688
+ if (this.opts.planMode && isExitPlanRequested(event)) {
1689
+ if (this.readingPlan)
1690
+ return;
1691
+ const inlinePlan = nativePlanFromExitEvent(event);
1692
+ if (inlinePlan?.content || inlinePlan?.path) {
1693
+ this.nativePlan = inlinePlan;
1694
+ this.finishSuccess(inlinePlan.content ?? "");
1695
+ return;
1696
+ }
1697
+ this.readingPlan = true;
1698
+ try {
1699
+ this.nativePlan = await readPlanWithRetry(this.registration, this.abort.signal);
1700
+ if (this.disposed)
1701
+ return;
1702
+ this.finishSuccess(this.nativePlan.content ?? "");
1703
+ }
1704
+ catch (err) {
1705
+ this.finishError(err instanceof Error ? err : new Error(String(err)));
1706
+ }
1707
+ return;
1708
+ }
1709
+ await this.evaluate();
1710
+ }
1711
+ async evaluate() {
1712
+ if (this.completed || this.disposed || this.readingPlan)
1713
+ return;
1714
+ if (this.opts.completionMarkerFile) {
1715
+ return;
1716
+ }
1717
+ const evidence = this.registration.evidence;
1718
+ if (evidence.submitIndex >= this.startIndex && evidence.abortIndex >= this.startIndex) {
1719
+ this.finishError(new Error("Copilot session aborted before completing the active turn"));
1720
+ return;
1721
+ }
1722
+ if (evidence.submitIndex < this.startIndex || !evidence.settled)
1723
+ return;
1724
+ this.finishSuccess(this.responseParts.join("\n"));
1725
+ }
1726
+ checkMarker() {
1727
+ if (this.completed || !this.opts.completionMarkerFile)
1728
+ return;
1729
+ let markerPresent = false;
1730
+ try {
1731
+ markerPresent = existsSync(this.opts.completionMarkerFile);
1732
+ }
1733
+ catch {
1734
+ markerPresent = false;
1735
+ }
1736
+ if (!markerPresent)
1737
+ return;
1738
+ this.finishSuccess(this.responseParts.join("\n"));
1739
+ }
1740
+ fireActivationIfNeeded() {
1741
+ if (this.activationFired)
1742
+ return;
1743
+ this.activationFired = true;
1744
+ this.opts.onActivation?.();
1745
+ }
1746
+ finishSuccess(text) {
1747
+ if (this.completed)
1748
+ return;
1749
+ this.completed = true;
1750
+ this.dispose();
1751
+ this.resolve?.(text);
1752
+ }
1753
+ finishError(error) {
1754
+ if (this.completed || this.disposed)
1755
+ return;
1756
+ this.completed = true;
1757
+ this.dispose();
1758
+ this.reject?.(error);
1759
+ }
1760
+ }
1761
+ async function readPlanWithRetry(registration, signal) {
1762
+ const deadline = Date.now() + PLAN_READ_TIMEOUT_MS;
1763
+ let last;
1764
+ while (Date.now() < deadline) {
1765
+ throwIfAborted(signal);
1766
+ last = await registration.requestPlanRead(Math.max(1, deadline - Date.now()));
1767
+ throwIfAborted(signal);
1768
+ if (last.content || last.path)
1769
+ return last;
1770
+ await abortableSleep(Math.min(PLAN_READ_RETRY_MS, Math.max(0, deadline - Date.now())), signal);
1771
+ }
1772
+ return last ?? {};
1773
+ }
1774
+ function isSessionIdle(line, sessionId) {
1775
+ if (line.type !== "session.idle")
1776
+ return false;
1777
+ const id = sessionIdFromEvent(line);
1778
+ return !id || id === sessionId;
1779
+ }
1780
+ function isAbortEvent(line) {
1781
+ return line.type === "session.abort" || line.type === "abort";
1782
+ }
1783
+ function isExitPlanRequested(line) {
1784
+ if (line.type === "exit_plan_mode.requested")
1785
+ return true;
1786
+ if (line.type !== "assistant.message")
1787
+ return false;
1788
+ const data = recordProp(line, "data");
1789
+ const requests = data?.toolRequests;
1790
+ return Array.isArray(requests)
1791
+ && requests.some((request) => isRecord(request) && stringProp(request, "name") === "exit_plan_mode");
1792
+ }
1793
+ function nativePlanFromExitEvent(line) {
1794
+ if (line.type !== "exit_plan_mode.requested")
1795
+ return undefined;
1796
+ const data = recordProp(line, "data");
1797
+ if (!data)
1798
+ return undefined;
1799
+ const content = rawStringProp(data, "planContent");
1800
+ const path = rawStringProp(data, "path")
1801
+ ?? rawStringProp(data, "filePath")
1802
+ ?? rawStringProp(data, "resolvedPath");
1803
+ if (content === undefined && path === undefined)
1804
+ return undefined;
1805
+ return {
1806
+ ...(content !== undefined && { content }),
1807
+ ...(path !== undefined && { path }),
1808
+ };
1809
+ }
1810
+ function parseInbound(value) {
1811
+ if (!isRecord(value))
1812
+ throw new Error("Copilot observer message must be a JSON object");
1813
+ if (value.kind === "auth") {
1814
+ const launchId = stringProp(value, "launchId");
1815
+ const sessionId = stringProp(value, "sessionId");
1816
+ const credential = stringProp(value, "credential");
1817
+ const runSecret = stringProp(value, "runSecret");
1818
+ if (!launchId || !sessionId || !credential || !runSecret) {
1819
+ throw new Error("Copilot observer auth message is missing required fields");
1820
+ }
1821
+ return {
1822
+ kind: "auth",
1823
+ launchId,
1824
+ sessionId,
1825
+ credential,
1826
+ runSecret,
1827
+ extensionPath: stringProp(value, "extensionPath"),
1828
+ };
1829
+ }
1830
+ if (value.kind === "control") {
1831
+ const type = stringProp(value, "type");
1832
+ if (!type)
1833
+ throw new Error("Copilot observer control message is missing type");
1834
+ return {
1835
+ kind: "control",
1836
+ type,
1837
+ sessionId: stringProp(value, "sessionId"),
1838
+ workspacePath: stringProp(value, "workspacePath"),
1839
+ error: stringProp(value, "error"),
1840
+ eventIds: value.eventIds,
1841
+ };
1842
+ }
1843
+ if (value.kind === "event") {
1844
+ const seq = value.seq;
1845
+ if (!Number.isInteger(seq) || Number(seq) <= 0 || !isRecord(value.event)) {
1846
+ throw new Error("Copilot observer event message is malformed");
1847
+ }
1848
+ return {
1849
+ kind: "event",
1850
+ seq: Number(seq),
1851
+ event: value.event,
1852
+ sessionId: stringProp(value, "sessionId"),
1853
+ };
1854
+ }
1855
+ if (value.kind === "rpc.response") {
1856
+ const requestId = stringProp(value, "requestId");
1857
+ if (!requestId || typeof value.ok !== "boolean") {
1858
+ throw new Error("Copilot observer RPC response is malformed");
1859
+ }
1860
+ return {
1861
+ kind: "rpc.response",
1862
+ requestId,
1863
+ ok: value.ok,
1864
+ result: value.result,
1865
+ error: stringProp(value, "error"),
1866
+ };
1867
+ }
1868
+ throw new Error("Copilot observer sent an unknown message kind");
1869
+ }
1870
+ function parsePlanReadResult(value) {
1871
+ if (!isRecord(value))
1872
+ return {};
1873
+ const exists = value.exists;
1874
+ if (exists === false)
1875
+ return {};
1876
+ return {
1877
+ content: rawStringProp(value, "content") ?? undefined,
1878
+ path: rawStringProp(value, "path")
1879
+ ?? rawStringProp(value, "filePath")
1880
+ ?? rawStringProp(value, "resolvedPath")
1881
+ ?? undefined,
1882
+ };
1883
+ }
1884
+ function sessionIdFromEvent(event) {
1885
+ const data = recordProp(event, "data");
1886
+ return stringProp(data, "sessionId") ?? stringProp(event, "sessionId");
1887
+ }
1888
+ function eventStableId(event) {
1889
+ return stringProp(event, "id") ?? stringProp(event, "eventId");
1890
+ }
1891
+ function eventReplayKey(event) {
1892
+ const data = recordProp(event, "data");
1893
+ const messageId = stringProp(data, "messageId");
1894
+ if (messageId
1895
+ && (event.type === "user.message" || event.type === "assistant.message")) {
1896
+ return `${event.type}:message:${messageId}`;
1897
+ }
1898
+ const toolCallId = stringProp(data, "toolCallId");
1899
+ if (toolCallId
1900
+ && (event.type === "tool.execution_start"
1901
+ || event.type === "tool.execution_complete")) {
1902
+ return `${event.type}:tool:${toolCallId}`;
1903
+ }
1904
+ const hookInvocationId = stringProp(data, "hookInvocationId");
1905
+ if (hookInvocationId
1906
+ && (event.type === "hook.start" || event.type === "hook.end")) {
1907
+ return `${event.type}:hook:${hookInvocationId}`;
1908
+ }
1909
+ const turnId = stringProp(data, "turnId") ?? stringProp(event, "turnId");
1910
+ const timestamp = stringProp(event, "timestamp");
1911
+ if (turnId
1912
+ && timestamp
1913
+ && (event.type === "assistant.turn_start" || event.type === "assistant.turn_end")) {
1914
+ return `${event.type}:turn:${turnId}:timestamp:${timestamp}`;
1915
+ }
1916
+ return undefined;
1917
+ }
1918
+ function diagnosticEventTail(events) {
1919
+ return events
1920
+ .slice(-OBSERVER_DIAGNOSTIC_EVENT_TAIL_LIMIT)
1921
+ .map(diagnosticEvent);
1922
+ }
1923
+ function diagnosticEvent(event) {
1924
+ const data = recordProp(event, "data");
1925
+ const input = recordProp(data, "input") ?? recordProp(event, "input");
1926
+ const id = eventStableId(event);
1927
+ const messageId = stringProp(data, "messageId");
1928
+ const turnId = stringProp(data, "turnId") ?? stringProp(event, "turnId");
1929
+ const phase = stringProp(data, "phase") ?? stringProp(event, "phase");
1930
+ const hookType = stringProp(data, "hookType") ?? stringProp(event, "hookType");
1931
+ const stopReason = stringProp(input, "stopReason");
1932
+ const sessionId = sessionIdFromEvent(event);
1933
+ return {
1934
+ type: rawStringProp(event, "type") ?? "unknown",
1935
+ ...(id && { id }),
1936
+ ...(messageId && { messageId }),
1937
+ ...(turnId && { turnId }),
1938
+ ...(phase && { phase }),
1939
+ ...(hookType && { hookType }),
1940
+ ...(stopReason && { stopReason }),
1941
+ ...(sessionId && { sessionId }),
1942
+ ...(isAgentScoped(event) && { agentScoped: true }),
1943
+ };
1944
+ }
1945
+ function isLiveHistoryRewriteEvent(line) {
1946
+ // Compaction compresses model context, not accepted task submissions.
1947
+ return line.type === "session.snapshot_rewind"
1948
+ || line.type === "session.context_cleared"
1949
+ || line.type === "session.truncation";
1950
+ }
1951
+ function validateLiveHistoryEventPayload(line) {
1952
+ if (line.type === "session.snapshot_rewind") {
1953
+ const data = recordProp(line, "data");
1954
+ if (!nonNegativeIntegerProp(data, "eventsRemoved") || !stringProp(data, "upToEventId")) {
1955
+ return "Copilot observer received malformed session.snapshot_rewind payload";
1956
+ }
1957
+ }
1958
+ else if (line.type === "session.context_cleared") {
1959
+ const data = recordProp(line, "data");
1960
+ if (!nonNegativeIntegerProp(data, "messagesCleared")) {
1961
+ return "Copilot observer received malformed session.context_cleared payload";
1962
+ }
1963
+ const initialMessage = data?.initialMessage;
1964
+ if (initialMessage !== undefined && typeof initialMessage !== "string") {
1965
+ return "Copilot observer received malformed session.context_cleared initialMessage";
1966
+ }
1967
+ }
1968
+ else if (line.type === "session.truncation") {
1969
+ const data = recordProp(line, "data");
1970
+ const required = [
1971
+ "messagesRemovedDuringTruncation",
1972
+ "preTruncationMessagesLength",
1973
+ "postTruncationMessagesLength",
1974
+ "preTruncationTokensInMessages",
1975
+ "postTruncationTokensInMessages",
1976
+ "tokenLimit",
1977
+ "tokensRemovedDuringTruncation",
1978
+ ];
1979
+ if (required.some((key) => !nonNegativeIntegerProp(data, key))) {
1980
+ return "Copilot observer received malformed session.truncation payload";
1981
+ }
1982
+ if (!stringProp(data, "performedBy")) {
1983
+ return "Copilot observer received malformed session.truncation performedBy";
1984
+ }
1985
+ }
1986
+ else if (line.type === "session.compaction_complete") {
1987
+ const data = recordProp(line, "data");
1988
+ if (typeof data?.success !== "boolean") {
1989
+ return "Copilot observer received malformed session.compaction_complete payload";
1990
+ }
1991
+ const messagesRemoved = data.messagesRemoved;
1992
+ if (messagesRemoved !== undefined
1993
+ && (!Number.isInteger(messagesRemoved) || Number(messagesRemoved) < 0)) {
1994
+ return "Copilot observer received malformed session.compaction_complete messagesRemoved";
1995
+ }
1996
+ }
1997
+ return undefined;
1998
+ }
1999
+ function liveSubmitIdentity(line, index, rewriteGeneration) {
2000
+ const eventId = stringProp(line, "id");
2001
+ if (eventId)
2002
+ return `${rewriteGeneration}:${eventId}`;
2003
+ const data = recordProp(line, "data");
2004
+ const messageId = stringProp(data, "messageId") ?? stringProp(line, "messageId");
2005
+ if (messageId)
2006
+ return `${rewriteGeneration}:${messageId}`;
2007
+ return `${rewriteGeneration}:${index}`;
2008
+ }
2009
+ function compatibleLiveHistorySnapshot(snapshot, current) {
2010
+ const value = snapshot.value;
2011
+ return isRecord(value)
2012
+ && value.kind === "copilot-live-observer-v2"
2013
+ && value.launchId === current.value.launchId
2014
+ && value.sessionId === current.value.sessionId
2015
+ && nonNegativeIntegerProp(value, "rewriteGeneration")
2016
+ && nonNegativeIntegerProp(value, "lineCount")
2017
+ && value.lineCount === snapshot.lineCount
2018
+ && Array.isArray(value.submitIdentities)
2019
+ && snapshot.submitCount === value.submitIdentities.length
2020
+ && snapshot.submitIdentities.length === value.submitIdentities.length
2021
+ && value.submitIdentities.every((identity, index) => typeof identity === "string" && identity === snapshot.submitIdentities[index]);
2022
+ }
2023
+ function compareLiveHistorySnapshots(previous, current) {
2024
+ let commonSubmitCount = 0;
2025
+ const limit = Math.min(previous.submitIdentities.length, current.submitIdentities.length);
2026
+ while (commonSubmitCount < limit
2027
+ && previous.submitIdentities[commonSubmitCount]
2028
+ === current.submitIdentities[commonSubmitCount]) {
2029
+ commonSubmitCount++;
2030
+ }
2031
+ if (current.value.rewriteGeneration === previous.value.rewriteGeneration
2032
+ && current.submitCount >= previous.submitCount
2033
+ && commonSubmitCount === previous.submitIdentities.length
2034
+ && current.lineCount >= previous.lineCount) {
2035
+ return undefined;
2036
+ }
2037
+ const currentSubmitIdentities = new Set(current.submitIdentities);
2038
+ return {
2039
+ commonSubmitCount,
2040
+ currentSubmitCount: current.submitCount,
2041
+ currentLineCount: current.lineCount,
2042
+ removedSubmitIdentities: previous.submitIdentities.filter((identity) => !currentSubmitIdentities.has(identity)),
2043
+ };
2044
+ }
2045
+ function nonNegativeIntegerProp(record, key) {
2046
+ const value = record?.[key];
2047
+ return Number.isInteger(value) && Number(value) >= 0;
2048
+ }
2049
+ function isAgentScoped(line) {
2050
+ return stringProp(line, "agentId") !== undefined;
2051
+ }
2052
+ function token() {
2053
+ return randomBytes(32).toString("base64url");
2054
+ }
2055
+ function sdkDistDir(resolvedExtensionEntry) {
2056
+ const parent = dirname(resolvedExtensionEntry);
2057
+ return basename(parent) === "cjs" ? dirname(parent) : parent;
2058
+ }
2059
+ async function createEndpoint() {
2060
+ const id = randomUUID();
2061
+ if (process.platform === "win32") {
2062
+ return `\\\\.\\pipe\\poly-weaver-copilot-${id}`;
2063
+ }
2064
+ const dir = await mkdtemp(join(tmpdir(), "poly-weaver-copilot-broker-"));
2065
+ return join(dir, "observer.sock");
2066
+ }
2067
+ function cleanupReadyWaiter(waiter) {
2068
+ if (waiter.signal && waiter.onAbort) {
2069
+ waiter.signal.removeEventListener("abort", waiter.onAbort);
2070
+ }
2071
+ }
2072
+ //# sourceMappingURL=observation.js.map