copilotoffice 1.1.1 → 2.1.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.
- package/dist/electron/main.js +2384 -175
- package/dist/electron/terminal/ipc-relay.js +191 -6
- package/dist/electron/terminal/preload.js +45 -0
- package/dist/electron/terminal/server.js +310 -68
- package/dist/game.bundle.js +14319 -8771
- package/package.json +8 -6
|
@@ -34,11 +34,131 @@ __export(ipc_relay_exports, {
|
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(ipc_relay_exports);
|
|
36
36
|
var import_electron = require("electron");
|
|
37
|
+
var import_child_process2 = require("child_process");
|
|
38
|
+
var import_events = require("events");
|
|
39
|
+
var crypto2 = __toESM(require("crypto"));
|
|
40
|
+
var path2 = __toESM(require("path"));
|
|
41
|
+
|
|
42
|
+
// electron/terminal/pty-registry.ts
|
|
37
43
|
var import_child_process = require("child_process");
|
|
38
44
|
var crypto = __toESM(require("crypto"));
|
|
45
|
+
var fs = __toESM(require("fs"));
|
|
46
|
+
var os = __toESM(require("os"));
|
|
39
47
|
var path = __toESM(require("path"));
|
|
48
|
+
var START_TIME_GRACE_MS = 2e3;
|
|
49
|
+
var DEFAULT_REGISTRY_FILE = path.join(process.cwd(), ".data", "pty-pids.json");
|
|
50
|
+
function readPtyRegistry(file = DEFAULT_REGISTRY_FILE) {
|
|
51
|
+
try {
|
|
52
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
53
|
+
const parsed = JSON.parse(raw);
|
|
54
|
+
if (!Array.isArray(parsed)) return [];
|
|
55
|
+
return parsed.filter(
|
|
56
|
+
(r) => !!r && typeof r.pid === "number" && Number.isFinite(r.pid)
|
|
57
|
+
);
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function writePtyRegistry(records, file = DEFAULT_REGISTRY_FILE) {
|
|
63
|
+
try {
|
|
64
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
65
|
+
const tmp = `${file}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`;
|
|
66
|
+
fs.writeFileSync(tmp, JSON.stringify(records, null, 2));
|
|
67
|
+
fs.renameSync(tmp, file);
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function isPidAlive(pid) {
|
|
72
|
+
try {
|
|
73
|
+
process.kill(pid, 0);
|
|
74
|
+
return true;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return err?.code === "EPERM";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function processStartTimeMs(pid) {
|
|
80
|
+
try {
|
|
81
|
+
if (os.platform() === "win32") {
|
|
82
|
+
const cmd = `powershell -NoProfile -NonInteractive -Command "$p = Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}'; if ($p) { [DateTimeOffset]::new($p.CreationDate).ToUnixTimeMilliseconds() }"`;
|
|
83
|
+
const out2 = (0, import_child_process.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
84
|
+
const ms2 = parseInt(out2, 10);
|
|
85
|
+
return Number.isFinite(ms2) ? ms2 : null;
|
|
86
|
+
}
|
|
87
|
+
const out = (0, import_child_process.execSync)(`ps -o lstart= -p ${pid}`, {
|
|
88
|
+
encoding: "utf8",
|
|
89
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
90
|
+
}).trim();
|
|
91
|
+
if (!out) return null;
|
|
92
|
+
const ms = Date.parse(out);
|
|
93
|
+
return Number.isNaN(ms) ? null : ms;
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function processIdentityMatches(record, startTimeOf) {
|
|
99
|
+
const startMs = startTimeOf(record.pid);
|
|
100
|
+
if (startMs == null) return false;
|
|
101
|
+
return startMs <= record.startedAt + START_TIME_GRACE_MS;
|
|
102
|
+
}
|
|
103
|
+
function killTree(pid) {
|
|
104
|
+
try {
|
|
105
|
+
if (os.platform() === "win32") {
|
|
106
|
+
(0, import_child_process.execSync)(`taskkill /T /F /PID ${pid}`, { stdio: "ignore" });
|
|
107
|
+
} else {
|
|
108
|
+
try {
|
|
109
|
+
process.kill(-pid, "SIGKILL");
|
|
110
|
+
} catch {
|
|
111
|
+
process.kill(pid, "SIGKILL");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function reapRegisteredPtys(options = {}) {
|
|
120
|
+
const file = options.file ?? DEFAULT_REGISTRY_FILE;
|
|
121
|
+
const isAlive = options.isAlive ?? isPidAlive;
|
|
122
|
+
const kill = options.kill ?? killTree;
|
|
123
|
+
const startTimeOf = options.startTimeOf ?? processStartTimeMs;
|
|
124
|
+
const protectedPids = /* @__PURE__ */ new Set([process.pid, ...options.protectedPids ?? []]);
|
|
125
|
+
const records = readPtyRegistry(file);
|
|
126
|
+
const reaped = [];
|
|
127
|
+
const skipped = [];
|
|
128
|
+
const failed = [];
|
|
129
|
+
const survivors = [];
|
|
130
|
+
for (const record of records) {
|
|
131
|
+
const { pid } = record;
|
|
132
|
+
if (protectedPids.has(pid) || !isAlive(pid)) {
|
|
133
|
+
skipped.push(pid);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!processIdentityMatches(record, startTimeOf)) {
|
|
137
|
+
skipped.push(pid);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (kill(pid)) {
|
|
141
|
+
reaped.push(pid);
|
|
142
|
+
} else {
|
|
143
|
+
failed.push(pid);
|
|
144
|
+
survivors.push(record);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
writePtyRegistry(survivors, file);
|
|
148
|
+
return { reaped, skipped, failed };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// electron/terminal/ipc-relay.ts
|
|
40
152
|
var TerminalRelay = class _TerminalRelay {
|
|
41
153
|
constructor(getWindow) {
|
|
154
|
+
/**
|
|
155
|
+
* Event bus for main-process consumers (e.g. the Teams service) that need
|
|
156
|
+
* server→main copilot/terminal events without going through the renderer.
|
|
157
|
+
* Emits: 'copilot-event' (agentId, event), 'copilot-turn-start' (agentId),
|
|
158
|
+
* 'copilot-turn-end' (agentId), 'copilot-tool-start' (agentId, toolName, toolId, status),
|
|
159
|
+
* 'session-meta-updated' (agentId, meta), 'terminal-exit' (agentId, exitCode).
|
|
160
|
+
*/
|
|
161
|
+
this.mainEvents = new import_events.EventEmitter();
|
|
42
162
|
this.server = null;
|
|
43
163
|
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
44
164
|
/** True while a deliberate shutdown→respawn is in progress (prevents double-spawn). */
|
|
@@ -54,9 +174,9 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
54
174
|
// ── Server Lifecycle ──────────────────────────────────────────
|
|
55
175
|
spawnServer(distDir) {
|
|
56
176
|
return new Promise((resolve, reject) => {
|
|
57
|
-
const serverPath =
|
|
177
|
+
const serverPath = path2.join(distDir, "terminal", "server.js");
|
|
58
178
|
console.log("[Relay] Forking terminal server:", serverPath);
|
|
59
|
-
this.server = (0,
|
|
179
|
+
this.server = (0, import_child_process2.fork)(serverPath, [], {
|
|
60
180
|
cwd: process.cwd(),
|
|
61
181
|
stdio: ["pipe", "inherit", "inherit", "ipc"]
|
|
62
182
|
});
|
|
@@ -80,6 +200,14 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
80
200
|
}
|
|
81
201
|
const win = this.getWindow();
|
|
82
202
|
if (win && !win.isDestroyed()) {
|
|
203
|
+
try {
|
|
204
|
+
const { reaped } = reapRegisteredPtys();
|
|
205
|
+
if (reaped.length > 0) {
|
|
206
|
+
console.log(`[Relay] Reaped ${reaped.length} orphaned PTY tree(s) from crashed server:`, reaped);
|
|
207
|
+
}
|
|
208
|
+
} catch (e) {
|
|
209
|
+
console.error("[Relay] Failed to reap orphaned PTYs after crash:", e);
|
|
210
|
+
}
|
|
83
211
|
console.log("[Relay] Respawning terminal server...");
|
|
84
212
|
this.spawnServer(distDir).catch(
|
|
85
213
|
(e) => console.error("[Relay] Failed to respawn:", e)
|
|
@@ -124,7 +252,7 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
124
252
|
process.kill(oldPid, 0);
|
|
125
253
|
console.log(`[Relay] Server PID ${oldPid} still alive after timeout \u2014 force killing`);
|
|
126
254
|
if (process.platform === "win32") {
|
|
127
|
-
(0,
|
|
255
|
+
(0, import_child_process2.execSync)(`taskkill /T /F /PID ${oldPid}`, { stdio: "ignore" });
|
|
128
256
|
} else {
|
|
129
257
|
process.kill(oldPid, "SIGKILL");
|
|
130
258
|
}
|
|
@@ -163,7 +291,33 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
163
291
|
});
|
|
164
292
|
}
|
|
165
293
|
id() {
|
|
166
|
-
return
|
|
294
|
+
return crypto2.randomUUID();
|
|
295
|
+
}
|
|
296
|
+
// ── Main-process gateway (for the Teams service) ──────────────
|
|
297
|
+
// These let a main-process consumer talk to the terminal server directly,
|
|
298
|
+
// reusing the same request/response plumbing as the renderer IPC handlers.
|
|
299
|
+
mainGetSessionId(officeId, agentId) {
|
|
300
|
+
return this.request({ type: "get-session-id", requestId: this.id(), officeId, agentId });
|
|
301
|
+
}
|
|
302
|
+
/** True only when the agent's PTY is alive AND the CLI has signalled ready. */
|
|
303
|
+
mainIsAgentReady(officeId, agentId) {
|
|
304
|
+
return this.request({ type: "query-agent-statuses", requestId: this.id(), officeId }).then((statuses) => {
|
|
305
|
+
const s = statuses?.[agentId];
|
|
306
|
+
return !!(s && s.alive && s.ready);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
mainGetSessionMeta(officeId, agentId) {
|
|
310
|
+
return this.request({ type: "get-session-meta", requestId: this.id(), officeId, agentId });
|
|
311
|
+
}
|
|
312
|
+
mainWrite(officeId, agentId, data) {
|
|
313
|
+
return this.request({ type: "write", requestId: this.id(), officeId, agentId, data });
|
|
314
|
+
}
|
|
315
|
+
mainSubmitPrompt(officeId, agentId, prompt, label) {
|
|
316
|
+
return this.request({ type: "submit-prompt", requestId: this.id(), officeId, agentId, prompt, label });
|
|
317
|
+
}
|
|
318
|
+
/** Fire-and-forget: control whether copilot-events are mirrored to main for an agent without a viewer. */
|
|
319
|
+
mainSetAgentForwarding(officeId, agentId, enabled) {
|
|
320
|
+
this.send({ type: "set-agent-forwarding", officeId, agentId, enabled });
|
|
167
321
|
}
|
|
168
322
|
handleServerMessage(msg, readyTimeout, onReady) {
|
|
169
323
|
if (msg.type === "ready") {
|
|
@@ -189,6 +343,29 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
189
343
|
}
|
|
190
344
|
return;
|
|
191
345
|
}
|
|
346
|
+
switch (msg.type) {
|
|
347
|
+
case "copilot-event":
|
|
348
|
+
this.mainEvents.emit("copilot-event", msg.agentId, msg.event);
|
|
349
|
+
break;
|
|
350
|
+
case "copilot-turn-start":
|
|
351
|
+
this.mainEvents.emit("copilot-turn-start", msg.agentId);
|
|
352
|
+
break;
|
|
353
|
+
case "copilot-turn-end":
|
|
354
|
+
this.mainEvents.emit("copilot-turn-end", msg.agentId);
|
|
355
|
+
break;
|
|
356
|
+
case "copilot-user-message":
|
|
357
|
+
this.mainEvents.emit("copilot-user-message", msg.agentId, msg.text);
|
|
358
|
+
break;
|
|
359
|
+
case "copilot-tool-start":
|
|
360
|
+
this.mainEvents.emit("copilot-tool-start", msg.agentId, msg.toolName, msg.toolId, msg.status);
|
|
361
|
+
break;
|
|
362
|
+
case "session-meta-updated":
|
|
363
|
+
this.mainEvents.emit("session-meta-updated", msg.agentId, msg.meta);
|
|
364
|
+
break;
|
|
365
|
+
case "terminal-exit":
|
|
366
|
+
this.mainEvents.emit("terminal-exit", msg.agentId, msg.exitCode);
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
192
369
|
const win = this.getWindow();
|
|
193
370
|
if (!win || win.isDestroyed()) return;
|
|
194
371
|
switch (msg.type) {
|
|
@@ -199,7 +376,7 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
199
376
|
win.webContents.send("terminal-exit", msg.agentId, msg.exitCode);
|
|
200
377
|
break;
|
|
201
378
|
case "copilot-event":
|
|
202
|
-
win.webContents.send("copilot-event", msg.agentId, msg.event);
|
|
379
|
+
if (!msg.mainOnly) win.webContents.send("copilot-event", msg.agentId, msg.event);
|
|
203
380
|
break;
|
|
204
381
|
case "copilot-tool-start":
|
|
205
382
|
win.webContents.send("copilot-tool-start", msg.agentId, msg.toolName, msg.toolId, msg.status);
|
|
@@ -214,7 +391,7 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
214
391
|
win.webContents.send("copilot-turn-start", msg.agentId);
|
|
215
392
|
break;
|
|
216
393
|
case "copilot-user-message":
|
|
217
|
-
win.webContents.send("copilot-user-message", msg.agentId);
|
|
394
|
+
win.webContents.send("copilot-user-message", msg.agentId, msg.text);
|
|
218
395
|
break;
|
|
219
396
|
case "session-meta-updated":
|
|
220
397
|
win.webContents.send("session-meta-updated", msg.agentId, msg.meta);
|
|
@@ -246,6 +423,14 @@ var TerminalRelay = class _TerminalRelay {
|
|
|
246
423
|
this.send({ type: "resize", officeId, agentId, cols, rows });
|
|
247
424
|
return { success: true };
|
|
248
425
|
});
|
|
426
|
+
import_electron.ipcMain.handle("set-yolo", (_event, enabled) => {
|
|
427
|
+
this.send({ type: "set-yolo", enabled: !!enabled });
|
|
428
|
+
return { success: true };
|
|
429
|
+
});
|
|
430
|
+
import_electron.ipcMain.handle("set-additional-params", (_event, params) => {
|
|
431
|
+
this.send({ type: "set-additional-params", params: String(params ?? "") });
|
|
432
|
+
return { success: true };
|
|
433
|
+
});
|
|
249
434
|
import_electron.ipcMain.handle(
|
|
250
435
|
"terminal-kill",
|
|
251
436
|
(_event, officeId, agentId) => this.request({ type: "kill", requestId: this.id(), officeId, agentId })
|
|
@@ -17,6 +17,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
17
17
|
var preload_exports = {};
|
|
18
18
|
module.exports = __toCommonJS(preload_exports);
|
|
19
19
|
var import_electron = require("electron");
|
|
20
|
+
import_electron.contextBridge.exposeInMainWorld("__copilotOfficeE2E", process.env.COPILOT_E2E === "1");
|
|
20
21
|
import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
|
|
21
22
|
// Terminal management
|
|
22
23
|
terminalStart: (officeId, agentId, workingDir, cols, rows, preseededPrompt, launchMode) => {
|
|
@@ -31,6 +32,14 @@ import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
|
|
|
31
32
|
terminalKill: (officeId, agentId) => {
|
|
32
33
|
return import_electron.ipcRenderer.invoke("terminal-kill", officeId, agentId);
|
|
33
34
|
},
|
|
35
|
+
// YOLO mode: push global flag to the PTY server so new copilot sessions launch with --yolo.
|
|
36
|
+
setYolo: (enabled) => {
|
|
37
|
+
return import_electron.ipcRenderer.invoke("set-yolo", enabled);
|
|
38
|
+
},
|
|
39
|
+
// Additional parameters: push the effective param string (empty = none) to the PTY server.
|
|
40
|
+
setAdditionalParams: (params) => {
|
|
41
|
+
return import_electron.ipcRenderer.invoke("set-additional-params", params);
|
|
42
|
+
},
|
|
34
43
|
terminalExists: (officeId, agentId) => {
|
|
35
44
|
return import_electron.ipcRenderer.invoke("terminal-exists", officeId, agentId);
|
|
36
45
|
},
|
|
@@ -148,5 +157,41 @@ import_electron.contextBridge.exposeInMainWorld("copilotBridge", {
|
|
|
148
157
|
// Native OS notifications
|
|
149
158
|
showNativeNotification: (title, body) => {
|
|
150
159
|
return import_electron.ipcRenderer.invoke("show-native-notification", title, body);
|
|
160
|
+
},
|
|
161
|
+
// Spec 003 follow-up: write to OS clipboard via Electron main process.
|
|
162
|
+
// Bypasses Permissions API + focus restrictions that make
|
|
163
|
+
// navigator.clipboard.writeText unreliable in xterm-focused contexts.
|
|
164
|
+
clipboardWriteText: (text) => {
|
|
165
|
+
return import_electron.ipcRenderer.invoke("clipboard-write-text", text);
|
|
166
|
+
},
|
|
167
|
+
// Spec 004: read OS clipboard via Electron main. Renderer pairs this with
|
|
168
|
+
// terminalWrite to implement Paste in the terminal context menu.
|
|
169
|
+
clipboardReadText: () => {
|
|
170
|
+
return import_electron.ipcRenderer.invoke("clipboard-read-text");
|
|
171
|
+
},
|
|
172
|
+
// ── Teams Remote Agents (011) ────────────────────────────────
|
|
173
|
+
teamsStatus: (args) => {
|
|
174
|
+
return import_electron.ipcRenderer.invoke("teams:status", args ?? {});
|
|
175
|
+
},
|
|
176
|
+
teamsRegister: (ctx) => {
|
|
177
|
+
return import_electron.ipcRenderer.invoke("teams:register", ctx);
|
|
178
|
+
},
|
|
179
|
+
teamsStop: (args) => {
|
|
180
|
+
return import_electron.ipcRenderer.invoke("teams:stop", args);
|
|
181
|
+
},
|
|
182
|
+
teamsReconcile: () => {
|
|
183
|
+
return import_electron.ipcRenderer.invoke("teams:reconcile");
|
|
184
|
+
},
|
|
185
|
+
teamsGetSettings: () => {
|
|
186
|
+
return import_electron.ipcRenderer.invoke("teams:getSettings");
|
|
187
|
+
},
|
|
188
|
+
teamsSaveSettings: (settings) => {
|
|
189
|
+
return import_electron.ipcRenderer.invoke("teams:saveSettings", { settings });
|
|
190
|
+
},
|
|
191
|
+
onTeamsStatusChanged: (callback) => {
|
|
192
|
+
import_electron.ipcRenderer.on("teams:status:changed", (_event, status) => callback(status));
|
|
193
|
+
},
|
|
194
|
+
onTeamsToast: (callback) => {
|
|
195
|
+
import_electron.ipcRenderer.on("teams:toast", (_event, toast) => callback(toast));
|
|
151
196
|
}
|
|
152
197
|
});
|