copilotoffice 2.1.0 → 2.3.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.
@@ -31,7 +31,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var events_watcher_exports = {};
32
32
  __export(events_watcher_exports, {
33
33
  EventsWatcher: () => EventsWatcher,
34
- formatToolStatus: () => formatToolStatus
34
+ buildAskUserRelay: () => buildAskUserRelay,
35
+ formatToolStatus: () => formatToolStatus,
36
+ normalizeAskUserArgs: () => normalizeAskUserArgs
35
37
  });
36
38
  module.exports = __toCommonJS(events_watcher_exports);
37
39
  var fs = __toESM(require("fs"));
@@ -223,8 +225,57 @@ function formatToolStatus(toolName, args) {
223
225
  return `Using ${toolName}`;
224
226
  }
225
227
  }
228
+ function normalizeAskUserArgs(args) {
229
+ const a = args ?? {};
230
+ const questionRaw = a.question ?? a.prompt ?? a.message ?? a.text ?? "";
231
+ const question = typeof questionRaw === "string" ? questionRaw : String(questionRaw ?? "");
232
+ const rawOptions = a.options ?? a.choices ?? a.answers ?? a.selections ?? [];
233
+ const options = [];
234
+ if (Array.isArray(rawOptions)) {
235
+ for (const opt of rawOptions) {
236
+ if (typeof opt === "string") {
237
+ options.push({ text: opt });
238
+ } else if (opt && typeof opt === "object") {
239
+ const o = opt;
240
+ const text = o.text ?? o.label ?? o.value ?? o.name ?? "";
241
+ options.push({ text: typeof text === "string" ? text : String(text ?? "") });
242
+ }
243
+ }
244
+ }
245
+ const freeformRaw = a.freeform ?? a.allowFreeform ?? a.allowFreeText ?? a.allowCustom ?? a.freeText ?? false;
246
+ const freeform = Boolean(freeformRaw);
247
+ return { question, options, freeform };
248
+ }
249
+ function buildAskUserRelay(event, backendName) {
250
+ const d = event.data ?? {};
251
+ if (event.type === "user_input.requested") {
252
+ const options = Array.isArray(d.choices) ? d.choices.map((c) => ({
253
+ text: typeof c === "string" ? c : String(c?.text ?? c?.label ?? c?.value ?? "")
254
+ })) : [];
255
+ return {
256
+ toolId: String(d.toolCallId ?? ""),
257
+ requestId: String(d.requestId ?? ""),
258
+ question: String(d.question ?? ""),
259
+ options,
260
+ freeform: Boolean(d.allowFreeform)
261
+ };
262
+ }
263
+ if (event.type === "tool.execution_start" && d.toolName === "ask_user" && backendName === "node-pty") {
264
+ const norm = normalizeAskUserArgs(d.arguments);
265
+ return {
266
+ toolId: String(d.toolCallId ?? ""),
267
+ requestId: "",
268
+ question: norm.question,
269
+ options: norm.options,
270
+ freeform: norm.freeform
271
+ };
272
+ }
273
+ return null;
274
+ }
226
275
  // Annotate the CommonJS export names for ESM import in node:
227
276
  0 && (module.exports = {
228
277
  EventsWatcher,
229
- formatToolStatus
278
+ buildAskUserRelay,
279
+ formatToolStatus,
280
+ normalizeAskUserArgs
230
281
  });
@@ -156,7 +156,9 @@ var TerminalRelay = class _TerminalRelay {
156
156
  * server→main copilot/terminal events without going through the renderer.
157
157
  * Emits: 'copilot-event' (agentId, event), 'copilot-turn-start' (agentId),
158
158
  * 'copilot-turn-end' (agentId), 'copilot-tool-start' (agentId, toolName, toolId, status),
159
+ * 'copilot-ask-user' (agentId, toolId, requestId, question, options, freeform),
159
160
  * 'session-meta-updated' (agentId, meta), 'terminal-exit' (agentId, exitCode).
161
+ * Accepts (via mainSubmitAnswer): 'submit-answer' (officeId, agentId, requestId?, answer, wasFreeform).
160
162
  */
161
163
  this.mainEvents = new import_events.EventEmitter();
162
164
  this.server = null;
@@ -165,6 +167,8 @@ var TerminalRelay = class _TerminalRelay {
165
167
  this.shuttingDown = false;
166
168
  /** Requests that arrived while the server was not connected. Flushed on ready. */
167
169
  this.queuedRequests = [];
170
+ /** Latest backend-selection outcome reported by the server on 'ready'. */
171
+ this.backendInfo = null;
168
172
  this.getWindow = getWindow;
169
173
  }
170
174
  static {
@@ -315,6 +319,22 @@ var TerminalRelay = class _TerminalRelay {
315
319
  mainSubmitPrompt(officeId, agentId, prompt, label) {
316
320
  return this.request({ type: "submit-prompt", requestId: this.id(), officeId, agentId, prompt, label });
317
321
  }
322
+ /**
323
+ * spec 015: answer a pending `ask_user` interaction. Distinct from
324
+ * mainSubmitPrompt — resolves the pending user-input interaction (SDK/ui-server)
325
+ * or injects keystrokes (node-pty). `requestId` is the single-resolution key.
326
+ */
327
+ mainSubmitAnswer(officeId, agentId, a) {
328
+ return this.request({
329
+ type: "submit-answer",
330
+ requestId: this.id(),
331
+ officeId,
332
+ agentId,
333
+ answerRequestId: a.requestId,
334
+ answer: a.answer,
335
+ wasFreeform: a.wasFreeform
336
+ });
337
+ }
318
338
  /** Fire-and-forget: control whether copilot-events are mirrored to main for an agent without a viewer. */
319
339
  mainSetAgentForwarding(officeId, agentId, enabled) {
320
340
  this.send({ type: "set-agent-forwarding", officeId, agentId, enabled });
@@ -324,6 +344,15 @@ var TerminalRelay = class _TerminalRelay {
324
344
  clearTimeout(readyTimeout);
325
345
  this.shuttingDown = false;
326
346
  console.log("[Relay] Terminal server ready");
347
+ if (msg.backend) {
348
+ this.backendInfo = msg.backend;
349
+ if (msg.backend.fellBack) {
350
+ const win2 = this.getWindow();
351
+ if (win2 && !win2.isDestroyed()) {
352
+ win2.webContents.send("backend-fallback", this.backendInfo);
353
+ }
354
+ }
355
+ }
327
356
  if (this.queuedRequests.length > 0) {
328
357
  console.log(`[Relay] Flushing ${this.queuedRequests.length} queued request(s)`);
329
358
  for (const queued of this.queuedRequests) {
@@ -359,6 +388,12 @@ var TerminalRelay = class _TerminalRelay {
359
388
  case "copilot-tool-start":
360
389
  this.mainEvents.emit("copilot-tool-start", msg.agentId, msg.toolName, msg.toolId, msg.status);
361
390
  break;
391
+ case "copilot-ask-user":
392
+ this.mainEvents.emit("copilot-ask-user", msg.agentId, msg.toolId, msg.requestId, msg.question, msg.options, msg.freeform);
393
+ break;
394
+ case "copilot-ask-user-complete":
395
+ this.mainEvents.emit("copilot-ask-user-complete", msg.agentId, msg.requestId);
396
+ break;
362
397
  case "session-meta-updated":
363
398
  this.mainEvents.emit("session-meta-updated", msg.agentId, msg.meta);
364
399
  break;
@@ -381,6 +416,12 @@ var TerminalRelay = class _TerminalRelay {
381
416
  case "copilot-tool-start":
382
417
  win.webContents.send("copilot-tool-start", msg.agentId, msg.toolName, msg.toolId, msg.status);
383
418
  break;
419
+ case "copilot-ask-user":
420
+ win.webContents.send("copilot-ask-user", msg.agentId, msg.toolId, msg.requestId, msg.question, msg.options, msg.freeform);
421
+ break;
422
+ case "copilot-ask-user-complete":
423
+ win.webContents.send("copilot-ask-user-complete", msg.agentId, msg.requestId);
424
+ break;
384
425
  case "copilot-tool-complete":
385
426
  win.webContents.send("copilot-tool-complete", msg.agentId, msg.toolId, msg.success);
386
427
  break;
@@ -399,17 +440,24 @@ var TerminalRelay = class _TerminalRelay {
399
440
  case "terminal-preload-status":
400
441
  win.webContents.send("terminal-preload-status", msg.agentId, msg.status);
401
442
  break;
443
+ case "backend-online":
444
+ win.webContents.send("backend-online", msg.officeId, msg.backend);
445
+ break;
446
+ case "backend-session-fallback":
447
+ win.webContents.send("backend-session-fallback", msg.officeId, msg.agentId, msg.reason);
448
+ break;
402
449
  }
403
450
  }
404
451
  // ── IPC Handler Registration ──────────────────────────────────
405
452
  registerIpc() {
453
+ import_electron.ipcMain.handle("terminal-backend-info", () => this.backendInfo);
406
454
  import_electron.ipcMain.handle(
407
455
  "terminal-start",
408
456
  (_event, officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode) => this.request({ type: "start", requestId: this.id(), officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode })
409
457
  );
410
458
  import_electron.ipcMain.handle(
411
459
  "terminal-attach",
412
- (_event, officeId, agentId) => this.request({ type: "attach", requestId: this.id(), officeId, agentId })
460
+ (_event, officeId, agentId, foreground) => this.request({ type: "attach", requestId: this.id(), officeId, agentId, foreground })
413
461
  );
414
462
  import_electron.ipcMain.handle("terminal-detach", (_event, officeId, agentId) => {
415
463
  this.send({ type: "detach", officeId, agentId });
@@ -43,8 +43,8 @@ import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
43
43
  terminalExists: (officeId, agentId) => {
44
44
  return import_electron.ipcRenderer.invoke("terminal-exists", officeId, agentId);
45
45
  },
46
- terminalAttach: (officeId, agentId) => {
47
- return import_electron.ipcRenderer.invoke("terminal-attach", officeId, agentId);
46
+ terminalAttach: (officeId, agentId, foreground) => {
47
+ return import_electron.ipcRenderer.invoke("terminal-attach", officeId, agentId, foreground);
48
48
  },
49
49
  terminalDetach: (officeId, agentId) => {
50
50
  return import_electron.ipcRenderer.invoke("terminal-detach", officeId, agentId);
@@ -105,37 +105,67 @@ import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
105
105
  loadOffices: () => {
106
106
  return import_electron.ipcRenderer.invoke("load-offices");
107
107
  },
108
- // Terminal event listeners
108
+ // Terminal event listeners. Each returns an unsubscribe function that removes
109
+ // ONLY this registration (not every listener on the channel), so a controller
110
+ // can dispose its own listener before re-registering. This prevents duplicate
111
+ // registrations from writing the same PTY byte to xterm more than once (the
112
+ // classic "double characters" bug). Callers may ignore the return value.
109
113
  onTerminalData: (callback) => {
110
- import_electron.ipcRenderer.on("terminal-data", (_event, agentId, data) => callback(agentId, data));
114
+ const handler = (_event, agentId, data) => callback(agentId, data);
115
+ import_electron.ipcRenderer.on("terminal-data", handler);
116
+ return () => import_electron.ipcRenderer.removeListener("terminal-data", handler);
111
117
  },
112
118
  onTerminalExit: (callback) => {
113
- import_electron.ipcRenderer.on("terminal-exit", (_event, agentId, exitCode) => callback(agentId, exitCode));
119
+ const handler = (_event, agentId, exitCode) => callback(agentId, exitCode);
120
+ import_electron.ipcRenderer.on("terminal-exit", handler);
121
+ return () => import_electron.ipcRenderer.removeListener("terminal-exit", handler);
114
122
  },
115
123
  onTerminalPreloadStatus: (callback) => {
116
- import_electron.ipcRenderer.on("terminal-preload-status", (_event, agentId, status) => callback(agentId, status));
124
+ const handler = (_event, agentId, status) => callback(agentId, status);
125
+ import_electron.ipcRenderer.on("terminal-preload-status", handler);
126
+ return () => import_electron.ipcRenderer.removeListener("terminal-preload-status", handler);
117
127
  },
118
128
  // Copilot activity event listeners
119
129
  onCopilotEvent: (callback) => {
120
- import_electron.ipcRenderer.on("copilot-event", (_event, agentId, copilotEvent) => callback(agentId, copilotEvent));
130
+ const handler = (_event, agentId, copilotEvent) => callback(agentId, copilotEvent);
131
+ import_electron.ipcRenderer.on("copilot-event", handler);
132
+ return () => import_electron.ipcRenderer.removeListener("copilot-event", handler);
121
133
  },
122
134
  onCopilotToolStart: (callback) => {
123
- import_electron.ipcRenderer.on("copilot-tool-start", (_event, agentId, toolName, toolId, status) => callback(agentId, toolName, toolId, status));
135
+ const handler = (_event, agentId, toolName, toolId, status) => callback(agentId, toolName, toolId, status);
136
+ import_electron.ipcRenderer.on("copilot-tool-start", handler);
137
+ return () => import_electron.ipcRenderer.removeListener("copilot-tool-start", handler);
138
+ },
139
+ // spec 015: additive ask_user relay (renderer parity — no Phaser consumer required).
140
+ onCopilotAskUser: (callback) => {
141
+ const handler = (_event, agentId, toolId, requestId, question, options, freeform) => callback(agentId, toolId, requestId, question, options, freeform);
142
+ import_electron.ipcRenderer.on("copilot-ask-user", handler);
143
+ return () => import_electron.ipcRenderer.removeListener("copilot-ask-user", handler);
124
144
  },
125
145
  onCopilotToolComplete: (callback) => {
126
- import_electron.ipcRenderer.on("copilot-tool-complete", (_event, agentId, toolId, success) => callback(agentId, toolId, success));
146
+ const handler = (_event, agentId, toolId, success) => callback(agentId, toolId, success);
147
+ import_electron.ipcRenderer.on("copilot-tool-complete", handler);
148
+ return () => import_electron.ipcRenderer.removeListener("copilot-tool-complete", handler);
127
149
  },
128
150
  onCopilotTurnEnd: (callback) => {
129
- import_electron.ipcRenderer.on("copilot-turn-end", (_event, agentId) => callback(agentId));
151
+ const handler = (_event, agentId) => callback(agentId);
152
+ import_electron.ipcRenderer.on("copilot-turn-end", handler);
153
+ return () => import_electron.ipcRenderer.removeListener("copilot-turn-end", handler);
130
154
  },
131
155
  onCopilotTurnStart: (callback) => {
132
- import_electron.ipcRenderer.on("copilot-turn-start", (_event, agentId) => callback(agentId));
156
+ const handler = (_event, agentId) => callback(agentId);
157
+ import_electron.ipcRenderer.on("copilot-turn-start", handler);
158
+ return () => import_electron.ipcRenderer.removeListener("copilot-turn-start", handler);
133
159
  },
134
160
  onCopilotUserMessage: (callback) => {
135
- import_electron.ipcRenderer.on("copilot-user-message", (_event, agentId) => callback(agentId));
161
+ const handler = (_event, agentId) => callback(agentId);
162
+ import_electron.ipcRenderer.on("copilot-user-message", handler);
163
+ return () => import_electron.ipcRenderer.removeListener("copilot-user-message", handler);
136
164
  },
137
165
  onSessionMetaUpdated: (callback) => {
138
- import_electron.ipcRenderer.on("session-meta-updated", (_event, agentId, meta) => callback(agentId, meta));
166
+ const handler = (_event, agentId, meta) => callback(agentId, meta);
167
+ import_electron.ipcRenderer.on("session-meta-updated", handler);
168
+ return () => import_electron.ipcRenderer.removeListener("session-meta-updated", handler);
139
169
  },
140
170
  removeTerminalListeners: () => {
141
171
  import_electron.ipcRenderer.removeAllListeners("terminal-data");
@@ -144,6 +174,7 @@ import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
144
174
  removeCopilotListeners: () => {
145
175
  import_electron.ipcRenderer.removeAllListeners("copilot-event");
146
176
  import_electron.ipcRenderer.removeAllListeners("copilot-tool-start");
177
+ import_electron.ipcRenderer.removeAllListeners("copilot-ask-user");
147
178
  import_electron.ipcRenderer.removeAllListeners("copilot-tool-complete");
148
179
  import_electron.ipcRenderer.removeAllListeners("copilot-turn-end");
149
180
  import_electron.ipcRenderer.removeAllListeners("copilot-turn-start");
@@ -158,6 +189,19 @@ import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
158
189
  showNativeNotification: (title, body) => {
159
190
  return import_electron.ipcRenderer.invoke("show-native-notification", title, body);
160
191
  },
192
+ // Terminal backend selection (ui-server / node-pty / sdk).
193
+ getBackendInfo: () => {
194
+ return import_electron.ipcRenderer.invoke("terminal-backend-info");
195
+ },
196
+ onBackendFallback: (callback) => {
197
+ import_electron.ipcRenderer.on("backend-fallback", (_event, info) => callback(info));
198
+ },
199
+ onBackendOnline: (callback) => {
200
+ import_electron.ipcRenderer.on("backend-online", (_event, officeId, backend) => callback(officeId, backend));
201
+ },
202
+ onBackendSessionFallback: (callback) => {
203
+ import_electron.ipcRenderer.on("backend-session-fallback", (_event, officeId, agentId, reason) => callback(officeId, agentId, reason));
204
+ },
161
205
  // Spec 003 follow-up: write to OS clipboard via Electron main process.
162
206
  // Bypasses Permissions API + focus restrictions that make
163
207
  // navigator.clipboard.writeText unreliable in xterm-focused contexts.