docs-combiner 0.2.2 → 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 (53) hide show
  1. package/dist/agentApi/agentControlClaim.js +26 -0
  2. package/dist/agentApi/approachMatrixView.js +19 -0
  3. package/dist/agentApi/capabilitiesMeta.js +78 -0
  4. package/dist/agentApi/catalogUrlPresets.js +26 -0
  5. package/dist/agentApi/constants.js +11 -0
  6. package/dist/agentApi/creativeSelections.js +25 -0
  7. package/dist/agentApi/escalation.js +221 -0
  8. package/dist/agentApi/generatedPairsGuard.js +85 -0
  9. package/dist/agentApi/httpHelpers.js +77 -0
  10. package/dist/agentApi/imagesReadiness.js +60 -0
  11. package/dist/agentApi/jobStatus.js +113 -0
  12. package/dist/agentApi/productImageDrive.js +39 -0
  13. package/dist/agentApi/regenerateImages.js +125 -0
  14. package/dist/agentApi/rendererTypes.js +3 -0
  15. package/dist/agentApi/routes.js +440 -0
  16. package/dist/agentApi/sessionSnapshot.js +82 -0
  17. package/dist/agentApi/sessionTypes.js +2 -0
  18. package/dist/agentApi/spec.js +1045 -0
  19. package/dist/agentApi/types.js +2 -0
  20. package/dist/agentApi/validation.js +32 -0
  21. package/dist/campaignsCsv.js +420 -0
  22. package/dist/contentPairs.js +62 -0
  23. package/dist/flexcardBalance.js +88 -0
  24. package/dist/integrations/driveApi.js +420 -0
  25. package/dist/integrations/httpTimeout.js +116 -0
  26. package/dist/integrations/openrouter.js +532 -0
  27. package/dist/main.js +830 -165
  28. package/dist/models.js +17 -0
  29. package/dist/offerSettings.js +19 -0
  30. package/dist/preload.js +28 -0
  31. package/dist/promptOverrides.js +437 -0
  32. package/dist/prompts.js +1243 -0
  33. package/dist/renderer.js +19 -19
  34. package/dist/renderer.js.LICENSE.txt +4 -0
  35. package/dist/renderer.js.map +1 -1
  36. package/dist/server/app.js +378 -0
  37. package/dist/server/auth.js +74 -0
  38. package/dist/server/contentJobs.js +367 -0
  39. package/dist/server/driveCatalog.js +122 -0
  40. package/dist/server/driveProxy.js +220 -0
  41. package/dist/server/flexcardMeta.js +29 -0
  42. package/dist/server/googleAuth.js +84 -0
  43. package/dist/server/imageAssets.js +87 -0
  44. package/dist/server/imageJobs.js +776 -0
  45. package/dist/server/index.js +38 -0
  46. package/dist/server/jobEvents.js +116 -0
  47. package/dist/server/jobs.js +358 -0
  48. package/dist/server/openRouterMeta.js +58 -0
  49. package/dist/server/spec.js +544 -0
  50. package/dist/server/storage.js +133 -0
  51. package/dist/server/telegram.js +53 -0
  52. package/dist/server/workspaceSnapshot.js +273 -0
  53. package/package.json +18 -4
package/dist/main.js CHANGED
@@ -41,14 +41,43 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
41
41
  step((generator = generator.apply(thisArg, _arguments || [])).next());
42
42
  });
43
43
  };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
44
47
  Object.defineProperty(exports, "__esModule", { value: true });
45
48
  const electron_1 = require("electron");
49
+ const express_1 = __importDefault(require("express"));
46
50
  const path = __importStar(require("path"));
47
51
  const fs = __importStar(require("fs"));
48
52
  const http = __importStar(require("http"));
49
53
  const url = __importStar(require("url"));
54
+ const routes_1 = require("./agentApi/routes");
55
+ const agentControlClaim_1 = require("./agentApi/agentControlClaim");
50
56
  let pendingGoogleAuth = null;
51
57
  let mainWindow = null;
58
+ let agentApiServer = null;
59
+ const windows = new Set();
60
+ const folderOwners = new Map();
61
+ const windowFolders = new Map();
62
+ const windowFolderPaths = new Map();
63
+ const windowWorkspaces = new Map();
64
+ const windowTitles = new Map();
65
+ const closingWindowIds = new Set();
66
+ const closedWorkspaceIds = new Set();
67
+ const agentControlledSessions = new Set();
68
+ const AGENT_API_HOST = '127.0.0.1';
69
+ const DEFAULT_AGENT_API_PORT = 17321;
70
+ const APP_WINDOW_TITLE = 'Комбайнер';
71
+ const AGENT_API_PORT = (() => {
72
+ const rawPort = Number(process.env.DOCS_COMBINER_AGENT_API_PORT);
73
+ return Number.isInteger(rawPort) && rawPort > 0 && rawPort <= 65535
74
+ ? rawPort
75
+ : DEFAULT_AGENT_API_PORT;
76
+ })();
77
+ const gotSingleInstanceLock = electron_1.app.requestSingleInstanceLock();
78
+ if (!gotSingleInstanceLock) {
79
+ electron_1.app.quit();
80
+ }
52
81
  if (process.env.DOCS_COMBINER_DISABLE_GPU === '1') {
53
82
  electron_1.app.disableHardwareAcceleration();
54
83
  }
@@ -56,6 +85,421 @@ function getInspectableWindow() {
56
85
  var _a, _b, _c;
57
86
  return (_c = (_b = (_a = electron_1.BrowserWindow.getFocusedWindow()) !== null && _a !== void 0 ? _a : mainWindow) !== null && _b !== void 0 ? _b : electron_1.BrowserWindow.getAllWindows()[0]) !== null && _c !== void 0 ? _c : null;
58
87
  }
88
+ function createWorkspaceId() {
89
+ return `workspace-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
90
+ }
91
+ function getAgentApiBaseUrl() {
92
+ return `http://${AGENT_API_HOST}:${AGENT_API_PORT}`;
93
+ }
94
+ function getWorkspaceWindows() {
95
+ var _a, _b;
96
+ const focusedId = (_b = (_a = electron_1.BrowserWindow.getFocusedWindow()) === null || _a === void 0 ? void 0 : _a.webContents.id) !== null && _b !== void 0 ? _b : null;
97
+ return electron_1.BrowserWindow.getAllWindows()
98
+ .filter(win => !win.isDestroyed() && !win.webContents.isDestroyed() && !closingWindowIds.has(win.webContents.id))
99
+ .map(win => {
100
+ var _a;
101
+ const webContentsId = win.webContents.id;
102
+ const workspaceId = (_a = windowWorkspaces.get(webContentsId)) !== null && _a !== void 0 ? _a : '';
103
+ return {
104
+ workspaceId,
105
+ webContentsId,
106
+ title: windowTitles.get(webContentsId) || 'Новый оффер',
107
+ folderId: windowFolders.get(webContentsId),
108
+ folderPath: windowFolderPaths.get(webContentsId),
109
+ active: webContentsId === focusedId,
110
+ };
111
+ });
112
+ }
113
+ function isSessionAgentControlled(sessionId) {
114
+ return agentControlledSessions.has(sessionId.trim());
115
+ }
116
+ function setSessionAgentControlMode(sessionId, mode) {
117
+ const id = sessionId.trim();
118
+ if (!id)
119
+ return;
120
+ if (mode === 'agent') {
121
+ agentControlledSessions.add(id);
122
+ }
123
+ else {
124
+ agentControlledSessions.delete(id);
125
+ }
126
+ const win = getWindowByWorkspaceId(id);
127
+ if (win && !win.isDestroyed() && !win.webContents.isDestroyed()) {
128
+ win.webContents.send('agent-control-mode-changed', { mode });
129
+ syncWindowTitleForAgentControl(win, id);
130
+ }
131
+ }
132
+ function isAgentApiReadyInSession(sessionId) {
133
+ return __awaiter(this, void 0, void 0, function* () {
134
+ const win = getWindowByWorkspaceId(sessionId);
135
+ if (!win || win.isDestroyed() || win.webContents.isDestroyed())
136
+ return false;
137
+ try {
138
+ return (yield win.webContents.executeJavaScript('Boolean(window.__docsCombinerAgentApi)', true)) === true;
139
+ }
140
+ catch (_a) {
141
+ return false;
142
+ }
143
+ });
144
+ }
145
+ function syncWindowTitleForAgentControl(win, workspaceId) {
146
+ const webContentsId = win.webContents.id;
147
+ const folderLabel = windowTitles.get(webContentsId) || 'Новый оффер';
148
+ const agentPrefix = isSessionAgentControlled(workspaceId) ? '🤖 ' : '';
149
+ win.setTitle(`${agentPrefix}${APP_WINDOW_TITLE} — ${folderLabel}`);
150
+ }
151
+ function buildAgentWorkSessionInfo(win) {
152
+ const baseUrl = getAgentApiBaseUrl();
153
+ const sessionUrl = `${baseUrl}/api/windows/${encodeURIComponent(win.workspaceId)}`;
154
+ const details = [
155
+ win.title,
156
+ win.folderPath ? `Drive folder: ${win.folderPath}` : 'No Drive folder selected',
157
+ win.active ? 'active session' : 'background session',
158
+ isSessionAgentControlled(win.workspaceId) ? 'agent-controlled' : 'manual control',
159
+ ];
160
+ return {
161
+ id: win.workspaceId,
162
+ title: win.title,
163
+ description: details.join('; '),
164
+ folderId: win.folderId,
165
+ folderPath: win.folderPath,
166
+ active: win.active,
167
+ agentControlled: isSessionAgentControlled(win.workspaceId),
168
+ url: sessionUrl,
169
+ endpoints: {
170
+ info: sessionUrl,
171
+ update: sessionUrl,
172
+ setupCampaign: `${sessionUrl}/setup-campaign`,
173
+ geoBlock: `${sessionUrl}/geo-block`,
174
+ creativeSelections: `${sessionUrl}/creative-selections`,
175
+ approachMatrix: `${sessionUrl}/approach-matrix`,
176
+ capabilities: `${sessionUrl}/capabilities`,
177
+ validation: `${sessionUrl}/validation`,
178
+ generated: `${sessionUrl}/generated`,
179
+ logs: `${sessionUrl}/logs`,
180
+ jobs: `${sessionUrl}/jobs`,
181
+ createCampaignGroup: `${sessionUrl}/catalog/groups`,
182
+ createWorkspaceFolder: `${sessionUrl}/catalog/workspaces`,
183
+ generate: `${sessionUrl}/generate`,
184
+ agentControl: `${sessionUrl}/agent-control`,
185
+ actions: `${sessionUrl}/actions`,
186
+ regeneratePair: `${sessionUrl}/actions/regenerate-pair`,
187
+ regenerateImages: `${sessionUrl}/actions/regenerate-images`,
188
+ },
189
+ };
190
+ }
191
+ function getAgentWorkSessions() {
192
+ return getWorkspaceWindows().map(buildAgentWorkSessionInfo);
193
+ }
194
+ function getWindowByWorkspaceId(workspaceId) {
195
+ var _a;
196
+ const id = workspaceId.trim();
197
+ return (_a = electron_1.BrowserWindow.getAllWindows().find(win => !win.isDestroyed() &&
198
+ !win.webContents.isDestroyed() &&
199
+ windowWorkspaces.get(win.webContents.id) === id)) !== null && _a !== void 0 ? _a : null;
200
+ }
201
+ function removeWorkspaceSnapshot(workspaceId) {
202
+ try {
203
+ const configPath = path.join(electron_1.app.getPath('userData'), 'config.json');
204
+ if (!fs.existsSync(configPath))
205
+ return;
206
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
207
+ const current = Array.isArray(config.workspaceSnapshots) ? config.workspaceSnapshots : [];
208
+ const next = current.filter((item) => (item === null || item === void 0 ? void 0 : item.id) !== workspaceId);
209
+ if (next.length !== current.length) {
210
+ fs.writeFileSync(configPath, JSON.stringify(Object.assign(Object.assign({}, config), { workspaceSnapshots: next })));
211
+ }
212
+ }
213
+ catch (_a) {
214
+ // ignore snapshot cleanup errors
215
+ }
216
+ }
217
+ function clearAllWorkspaceSnapshots() {
218
+ try {
219
+ const configPath = path.join(electron_1.app.getPath('userData'), 'config.json');
220
+ if (!fs.existsSync(configPath))
221
+ return;
222
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
223
+ const nextConfig = Object.assign(Object.assign({}, config), { workspaceSnapshots: [] });
224
+ fs.writeFileSync(configPath, JSON.stringify(nextConfig));
225
+ broadcastConfigUpdated(nextConfig);
226
+ }
227
+ catch (_a) {
228
+ // ignore snapshot cleanup errors
229
+ }
230
+ }
231
+ function pickWindowToKeep(allWindows) {
232
+ var _a, _b;
233
+ if (allWindows.length === 0)
234
+ return null;
235
+ return ((_b = (_a = electron_1.BrowserWindow.getFocusedWindow()) !== null && _a !== void 0 ? _a : (mainWindow && !mainWindow.isDestroyed() ? mainWindow : null)) !== null && _b !== void 0 ? _b : allWindows[0]);
236
+ }
237
+ function resetWindowToEmptySession(win) {
238
+ const webContentsId = win.webContents.id;
239
+ const oldWorkspaceId = windowWorkspaces.get(webContentsId);
240
+ if (oldWorkspaceId) {
241
+ agentControlledSessions.delete(oldWorkspaceId);
242
+ closedWorkspaceIds.add(oldWorkspaceId);
243
+ removeWorkspaceSnapshot(oldWorkspaceId);
244
+ }
245
+ cleanupWindowFolder(webContentsId);
246
+ const newWorkspaceId = createWorkspaceId();
247
+ closedWorkspaceIds.delete(newWorkspaceId);
248
+ windowWorkspaces.set(webContentsId, newWorkspaceId);
249
+ windowTitles.set(webContentsId, 'Новый оффер');
250
+ syncWindowTitleForAgentControl(win, newWorkspaceId);
251
+ mainWindow = win;
252
+ win.loadFile(path.join(__dirname, 'index.html'), {
253
+ query: {
254
+ workspaceId: newWorkspaceId,
255
+ agentControlled: '0',
256
+ },
257
+ });
258
+ }
259
+ function closeAllWorkspaceSessions() {
260
+ const allWindows = electron_1.BrowserWindow.getAllWindows().filter(win => !win.isDestroyed());
261
+ const keepWin = pickWindowToKeep(allWindows);
262
+ if (!keepWin) {
263
+ createWindow();
264
+ return { ok: true };
265
+ }
266
+ const keepId = keepWin.webContents.id;
267
+ clearAllWorkspaceSnapshots();
268
+ for (const win of allWindows) {
269
+ if (win.webContents.id === keepId)
270
+ continue;
271
+ const workspaceId = windowWorkspaces.get(win.webContents.id);
272
+ if (workspaceId) {
273
+ closeSessionByWorkspaceId(workspaceId);
274
+ continue;
275
+ }
276
+ const webContentsId = win.webContents.id;
277
+ closingWindowIds.add(webContentsId);
278
+ broadcastWorkspaceWindowsUpdated();
279
+ win.destroy();
280
+ setImmediate(() => broadcastWorkspaceWindowsUpdated());
281
+ }
282
+ resetWindowToEmptySession(keepWin);
283
+ broadcastWorkspaceWindowsUpdated();
284
+ return { ok: true };
285
+ }
286
+ function closeOtherWorkspaceSessions(exceptWebContentsId) {
287
+ const keepId = Number(exceptWebContentsId);
288
+ const allWindows = electron_1.BrowserWindow.getAllWindows().filter(win => !win.isDestroyed());
289
+ for (const win of allWindows) {
290
+ const webContentsId = win.webContents.id;
291
+ if (webContentsId === keepId)
292
+ continue;
293
+ const workspaceId = windowWorkspaces.get(webContentsId);
294
+ if (workspaceId) {
295
+ closeSessionByWorkspaceId(workspaceId);
296
+ }
297
+ else {
298
+ closingWindowIds.add(webContentsId);
299
+ broadcastWorkspaceWindowsUpdated();
300
+ win.destroy();
301
+ setImmediate(() => broadcastWorkspaceWindowsUpdated());
302
+ }
303
+ }
304
+ return { ok: true };
305
+ }
306
+ function closeSessionByWorkspaceId(workspaceId) {
307
+ const id = workspaceId.trim();
308
+ if (!id) {
309
+ return { ok: false, error: 'Session id is required.' };
310
+ }
311
+ const win = getWindowByWorkspaceId(id);
312
+ if (!win || win.isDestroyed()) {
313
+ return { ok: false, error: 'Session not found.' };
314
+ }
315
+ const webContentsId = win.webContents.id;
316
+ agentControlledSessions.delete(id);
317
+ closedWorkspaceIds.add(id);
318
+ removeWorkspaceSnapshot(id);
319
+ closingWindowIds.add(webContentsId);
320
+ broadcastWorkspaceWindowsUpdated();
321
+ win.destroy();
322
+ setImmediate(() => broadcastWorkspaceWindowsUpdated());
323
+ return { ok: true };
324
+ }
325
+ function callAgentRendererApi(workspaceId, method, payload, options) {
326
+ return __awaiter(this, void 0, void 0, function* () {
327
+ const win = getWindowByWorkspaceId(workspaceId);
328
+ if (!win) {
329
+ return { ok: false, status: 404, error: 'Session not found.' };
330
+ }
331
+ if ((0, agentControlClaim_1.shouldClaimAgentControl)(method, options)) {
332
+ setSessionAgentControlMode(workspaceId, 'agent');
333
+ }
334
+ try {
335
+ const script = `
336
+ (async () => {
337
+ const api = window.__docsCombinerAgentApi;
338
+ if (!api || typeof api[${JSON.stringify(method)}] !== 'function') {
339
+ return { ok: false, error: 'Agent API is not ready in this session.' };
340
+ }
341
+ try {
342
+ const result = await api[${JSON.stringify(method)}](${JSON.stringify(payload !== null && payload !== void 0 ? payload : null)});
343
+ return { ok: true, result };
344
+ } catch (err) {
345
+ return { ok: false, error: err && err.message ? err.message : String(err) };
346
+ }
347
+ })()
348
+ `;
349
+ const response = yield win.webContents.executeJavaScript(script, true);
350
+ if (!(response === null || response === void 0 ? void 0 : response.ok)) {
351
+ return {
352
+ ok: false,
353
+ status: 409,
354
+ error: String((response === null || response === void 0 ? void 0 : response.error) || 'Agent API call failed.'),
355
+ };
356
+ }
357
+ return { ok: true, result: response.result };
358
+ }
359
+ catch (err) {
360
+ return {
361
+ ok: false,
362
+ status: 500,
363
+ error: err instanceof Error ? err.message : String(err),
364
+ };
365
+ }
366
+ });
367
+ }
368
+ function callFirstReadyAgentRendererApi(method, payload, preferredSessionId) {
369
+ return __awaiter(this, void 0, void 0, function* () {
370
+ const readOnlyOptions = { claimAgentControl: false };
371
+ const preferredId = String(preferredSessionId !== null && preferredSessionId !== void 0 ? preferredSessionId : '').trim();
372
+ if (preferredId) {
373
+ const preferred = yield callAgentRendererApi(preferredId, method, payload, readOnlyOptions);
374
+ if (preferred.ok)
375
+ return preferred;
376
+ }
377
+ const focused = electron_1.BrowserWindow.getFocusedWindow();
378
+ if (focused && !focused.isDestroyed() && !focused.webContents.isDestroyed()) {
379
+ const focusedId = windowWorkspaces.get(focused.webContents.id);
380
+ if (focusedId) {
381
+ const focusedResponse = yield callAgentRendererApi(focusedId, method, payload, readOnlyOptions);
382
+ if (focusedResponse.ok)
383
+ return focusedResponse;
384
+ }
385
+ }
386
+ for (const win of electron_1.BrowserWindow.getAllWindows()) {
387
+ if (win.isDestroyed() || win.webContents.isDestroyed())
388
+ continue;
389
+ if (win === focused)
390
+ continue;
391
+ const sessionId = windowWorkspaces.get(win.webContents.id);
392
+ if (!sessionId)
393
+ continue;
394
+ const response = yield callAgentRendererApi(sessionId, method, payload, readOnlyOptions);
395
+ if (response.ok)
396
+ return response;
397
+ }
398
+ return {
399
+ ok: false,
400
+ status: 503,
401
+ error: 'No session with Agent API ready.',
402
+ };
403
+ });
404
+ }
405
+ function waitForAgentSessionReady(sessionId_1) {
406
+ return __awaiter(this, arguments, void 0, function* (sessionId, timeoutMs = 15000) {
407
+ const startedAt = Date.now();
408
+ while (Date.now() - startedAt < timeoutMs) {
409
+ const response = yield callAgentRendererApi(sessionId, 'getSessionInfo');
410
+ if (response.ok)
411
+ return response.result;
412
+ yield new Promise(resolve => setTimeout(resolve, 250));
413
+ }
414
+ return null;
415
+ });
416
+ }
417
+ function startAgentApiServer() {
418
+ if (agentApiServer)
419
+ return;
420
+ const api = (0, express_1.default)();
421
+ api.disable('x-powered-by');
422
+ api.use(express_1.default.json({ limit: '1mb' }));
423
+ (0, routes_1.registerAgentApiRoutes)(api, {
424
+ host: AGENT_API_HOST,
425
+ port: AGENT_API_PORT,
426
+ version: electron_1.app.getVersion(),
427
+ getAgentWorkSessions,
428
+ isAgentApiReadyInSession,
429
+ agentControlledSessionCount: () => agentControlledSessions.size,
430
+ createWorkspaceId,
431
+ createWindow,
432
+ broadcastWorkspaceWindowsUpdated,
433
+ waitForAgentSessionReady,
434
+ getAgentApiBaseUrl,
435
+ callFirstReadyAgentRendererApi,
436
+ callAgentRendererApi,
437
+ getWindowByWorkspaceId,
438
+ setSessionAgentControlMode,
439
+ closeSession: closeSessionByWorkspaceId,
440
+ });
441
+ agentApiServer = api.listen(AGENT_API_PORT, AGENT_API_HOST, () => {
442
+ console.log(`[Agent API] Listening on http://${AGENT_API_HOST}:${AGENT_API_PORT}`);
443
+ });
444
+ agentApiServer.on('error', (err) => {
445
+ console.error('[Agent API] Failed to start:', err);
446
+ });
447
+ }
448
+ function broadcastWorkspaceWindowsUpdated() {
449
+ const list = getWorkspaceWindows();
450
+ for (const win of electron_1.BrowserWindow.getAllWindows()) {
451
+ if (!win.isDestroyed() && !win.webContents.isDestroyed()) {
452
+ win.webContents.send('workspace-windows-updated', list);
453
+ }
454
+ }
455
+ }
456
+ function focusWindowByWebContentsId(webContentsId) {
457
+ const win = electron_1.BrowserWindow.getAllWindows().find(w => w.webContents.id === webContentsId);
458
+ if (!win || win.isDestroyed())
459
+ return false;
460
+ if (win.isMinimized())
461
+ win.restore();
462
+ win.show();
463
+ win.focus();
464
+ return true;
465
+ }
466
+ function cleanupWindowFolder(webContentsId) {
467
+ const folderId = windowFolders.get(webContentsId);
468
+ windowFolderPaths.delete(webContentsId);
469
+ if (!folderId)
470
+ return;
471
+ windowFolders.delete(webContentsId);
472
+ const owners = folderOwners.get(folderId);
473
+ if (!owners)
474
+ return;
475
+ owners.delete(webContentsId);
476
+ if (owners.size === 0) {
477
+ folderOwners.delete(folderId);
478
+ }
479
+ }
480
+ function pruneStaleFolderOwners(folderId) {
481
+ const owners = folderOwners.get(folderId);
482
+ if (!owners)
483
+ return;
484
+ for (const ownerId of [...owners]) {
485
+ const ownerWin = electron_1.BrowserWindow.getAllWindows().find(w => w.webContents.id === ownerId);
486
+ const ownerAlive = Boolean(ownerWin) && !ownerWin.isDestroyed() && !ownerWin.webContents.isDestroyed();
487
+ if (!ownerAlive) {
488
+ owners.delete(ownerId);
489
+ windowFolders.delete(ownerId);
490
+ }
491
+ }
492
+ if (owners.size === 0) {
493
+ folderOwners.delete(folderId);
494
+ }
495
+ }
496
+ function broadcastConfigUpdated(config) {
497
+ for (const win of electron_1.BrowserWindow.getAllWindows()) {
498
+ if (!win.isDestroyed() && !win.webContents.isDestroyed()) {
499
+ win.webContents.send('config-updated', config);
500
+ }
501
+ }
502
+ }
59
503
  function toggleDevToolsForWindow(win) {
60
504
  if (!win || win.isDestroyed()) {
61
505
  console.warn('[DevTools] No BrowserWindow available');
@@ -82,6 +526,8 @@ function telegramSendMessageViaBot(botToken, chatId, text) {
82
526
  return __awaiter(this, void 0, void 0, function* () {
83
527
  const apiUrl = `https://api.telegram.org/bot${encodeURIComponent(botToken)}/sendMessage`;
84
528
  const safeText = text.length > 4096 ? `${text.slice(0, 4090)}…` : text;
529
+ const controller = new AbortController();
530
+ const timeoutId = setTimeout(() => controller.abort(), 30000);
85
531
  try {
86
532
  const res = yield fetch(apiUrl, {
87
533
  method: 'POST',
@@ -90,6 +536,7 @@ function telegramSendMessageViaBot(botToken, chatId, text) {
90
536
  chat_id: chatId,
91
537
  text: safeText,
92
538
  }),
539
+ signal: controller.signal,
93
540
  });
94
541
  const data = (yield res.json());
95
542
  if (!data.ok) {
@@ -106,6 +553,9 @@ function telegramSendMessageViaBot(botToken, chatId, text) {
106
553
  error: e instanceof Error ? e.message : String(e),
107
554
  };
108
555
  }
556
+ finally {
557
+ clearTimeout(timeoutId);
558
+ }
109
559
  });
110
560
  }
111
561
  function abortPendingGoogleAuth(reason) {
@@ -128,11 +578,31 @@ function buildAppMenu() {
128
578
  template.push({ role: 'appMenu' });
129
579
  // Cut/Copy/Paste/Select All — без этого Cmd+V в полях иногда не работает (Chromium + системное меню).
130
580
  template.push({ role: 'editMenu' });
581
+ template.push({
582
+ label: 'File',
583
+ submenu: [
584
+ {
585
+ label: 'New Window',
586
+ accelerator: 'CmdOrCtrl+N',
587
+ click: () => createWindow(),
588
+ },
589
+ { type: 'separator' },
590
+ { role: 'close' },
591
+ ],
592
+ });
131
593
  }
132
594
  else {
133
595
  template.push({
134
596
  label: 'File',
135
- submenu: [{ role: 'quit' }],
597
+ submenu: [
598
+ {
599
+ label: 'New Window',
600
+ accelerator: 'Ctrl+N',
601
+ click: () => createWindow(),
602
+ },
603
+ { type: 'separator' },
604
+ { role: 'quit' },
605
+ ],
136
606
  });
137
607
  template.push({
138
608
  label: 'Edit',
@@ -167,10 +637,15 @@ function buildAppMenu() {
167
637
  });
168
638
  electron_1.Menu.setApplicationMenu(electron_1.Menu.buildFromTemplate(template));
169
639
  }
170
- function createWindow() {
171
- mainWindow = new electron_1.BrowserWindow({
640
+ function createWindow(workspaceId = createWorkspaceId(), options) {
641
+ closedWorkspaceIds.delete(workspaceId);
642
+ if (options === null || options === void 0 ? void 0 : options.agentControlled) {
643
+ agentControlledSessions.add(workspaceId);
644
+ }
645
+ const win = new electron_1.BrowserWindow({
172
646
  width: 1000,
173
647
  height: 800,
648
+ title: APP_WINDOW_TITLE,
174
649
  backgroundColor: '#121212', // Set to dark by default, will be corrected by preload if light
175
650
  show: false, // Don't show until ready to prevent flash
176
651
  webPreferences: {
@@ -180,7 +655,29 @@ function createWindow() {
180
655
  sandbox: false
181
656
  },
182
657
  });
183
- const win = mainWindow;
658
+ mainWindow = win;
659
+ windows.add(win);
660
+ windowWorkspaces.set(win.webContents.id, workspaceId);
661
+ windowTitles.set(win.webContents.id, 'Новый оффер');
662
+ broadcastWorkspaceWindowsUpdated();
663
+ win.on('closed', () => {
664
+ var _a;
665
+ closingWindowIds.delete(win.webContents.id);
666
+ const closedWorkspaceId = windowWorkspaces.get(win.webContents.id);
667
+ if (closedWorkspaceId) {
668
+ agentControlledSessions.delete(closedWorkspaceId);
669
+ }
670
+ windows.delete(win);
671
+ cleanupWindowFolder(win.webContents.id);
672
+ windowWorkspaces.delete(win.webContents.id);
673
+ windowTitles.delete(win.webContents.id);
674
+ if (mainWindow === win) {
675
+ mainWindow = (_a = electron_1.BrowserWindow.getAllWindows()[0]) !== null && _a !== void 0 ? _a : null;
676
+ }
677
+ broadcastWorkspaceWindowsUpdated();
678
+ });
679
+ win.on('focus', () => broadcastWorkspaceWindowsUpdated());
680
+ win.on('show', () => broadcastWorkspaceWindowsUpdated());
184
681
  // Toggles from main: works when the page/React failed — handled before the renderer consumes the key.
185
682
  // F12, Cmd+Shift+D (mac) / Ctrl+Shift+D (win/linux); also View → Toggle Developer Tools / Cmd+Option+I (see menu).
186
683
  win.webContents.on('before-input-event', (event, input) => {
@@ -237,201 +734,369 @@ function createWindow() {
237
734
  });
238
735
  });
239
736
  // We need to point to the webpack output html
240
- win.loadFile(path.join(__dirname, 'index.html'));
737
+ win.loadFile(path.join(__dirname, 'index.html'), {
738
+ query: {
739
+ workspaceId,
740
+ agentControlled: (options === null || options === void 0 ? void 0 : options.agentControlled) ? '1' : '0',
741
+ },
742
+ });
241
743
  // DevTools disabled by default as requested
242
- // mainWindow.webContents.openDevTools();
744
+ // win.webContents.openDevTools();
243
745
  }
244
- electron_1.app.whenReady().then(() => {
245
- buildAppMenu();
246
- electron_1.app.on('child-process-gone', (_event, details) => {
247
- console.error(`[Electron] child-process-gone: type=${details.type}, reason=${details.reason}, exitCode=${details.exitCode}`);
248
- });
249
- const userDataPath = electron_1.app.getPath('userData');
250
- if (!fs.existsSync(userDataPath)) {
251
- fs.mkdirSync(userDataPath, { recursive: true });
252
- }
253
- const configPath = path.join(userDataPath, 'config.json');
254
- electron_1.ipcMain.handle('save-config', (event, config) => __awaiter(void 0, void 0, void 0, function* () {
255
- try {
256
- fs.writeFileSync(configPath, JSON.stringify(config));
257
- return true;
746
+ if (gotSingleInstanceLock) {
747
+ electron_1.app.on('second-instance', () => {
748
+ if (electron_1.app.isReady()) {
749
+ createWindow();
750
+ return;
258
751
  }
259
- catch (e) {
260
- return false;
752
+ electron_1.app.whenReady().then(() => createWindow()).catch(err => {
753
+ console.error('[Main] Failed to open window for second instance:', err);
754
+ });
755
+ });
756
+ }
757
+ if (gotSingleInstanceLock)
758
+ electron_1.app.whenReady().then(() => {
759
+ buildAppMenu();
760
+ startAgentApiServer();
761
+ electron_1.app.on('child-process-gone', (_event, details) => {
762
+ console.error(`[Electron] child-process-gone: type=${details.type}, reason=${details.reason}, exitCode=${details.exitCode}`);
763
+ });
764
+ const userDataPath = electron_1.app.getPath('userData');
765
+ if (!fs.existsSync(userDataPath)) {
766
+ fs.mkdirSync(userDataPath, { recursive: true });
261
767
  }
262
- }));
263
- electron_1.ipcMain.handle('load-config', () => __awaiter(void 0, void 0, void 0, function* () {
264
- try {
265
- if (fs.existsSync(configPath)) {
768
+ const configPath = path.join(userDataPath, 'config.json');
769
+ const readConfig = () => {
770
+ try {
771
+ if (!fs.existsSync(configPath))
772
+ return {};
266
773
  const content = fs.readFileSync(configPath, 'utf-8');
267
774
  return JSON.parse(content);
268
775
  }
269
- return {};
270
- }
271
- catch (e) {
272
- return {};
273
- }
274
- }));
275
- electron_1.ipcMain.handle('telegram-send-test', (_event, payload) => __awaiter(void 0, void 0, void 0, function* () {
276
- var _a, _b;
277
- const botToken = String((_a = payload === null || payload === void 0 ? void 0 : payload.botToken) !== null && _a !== void 0 ? _a : '').trim();
278
- const chatId = String((_b = payload === null || payload === void 0 ? void 0 : payload.chatId) !== null && _b !== void 0 ? _b : '').trim();
279
- if (!botToken || !chatId) {
280
- return { ok: false, error: 'Укажите токен бота и ID чата.' };
281
- }
282
- return telegramSendMessageViaBot(botToken, chatId, 'Docs Combiner — тест оповещений.');
283
- }));
284
- electron_1.ipcMain.handle('telegram-send-message', (_event, payload) => __awaiter(void 0, void 0, void 0, function* () {
285
- var _a, _b, _c;
286
- const botToken = String((_a = payload === null || payload === void 0 ? void 0 : payload.botToken) !== null && _a !== void 0 ? _a : '').trim();
287
- const chatId = String((_b = payload === null || payload === void 0 ? void 0 : payload.chatId) !== null && _b !== void 0 ? _b : '').trim();
288
- const text = String((_c = payload === null || payload === void 0 ? void 0 : payload.text) !== null && _c !== void 0 ? _c : '');
289
- if (!botToken || !chatId) {
290
- return { ok: false, error: 'Укажите токен бота и ID чата.' };
291
- }
292
- if (!text.trim()) {
293
- return { ok: false, error: 'Пустой текст сообщения.' };
294
- }
295
- return telegramSendMessageViaBot(botToken, chatId, text);
296
- }));
297
- electron_1.ipcMain.handle('save-file', (event, content, filename) => __awaiter(void 0, void 0, void 0, function* () {
298
- const { canceled, filePath } = yield electron_1.dialog.showSaveDialog({
299
- defaultPath: filename,
300
- });
301
- if (canceled || !filePath)
302
- return false;
303
- fs.writeFileSync(filePath, content);
304
- return true;
305
- }));
306
- electron_1.ipcMain.handle('start-auth', (event, clientId) => __awaiter(void 0, void 0, void 0, function* () {
307
- abortPendingGoogleAuth('AUTH_SUPERSEDED');
308
- return new Promise((resolve, reject) => {
309
- const server = http.createServer((req, res) => __awaiter(void 0, void 0, void 0, function* () {
310
- try {
311
- if (!req.url)
312
- return;
313
- const parsedUrl = url.parse(req.url, true);
314
- // Allow any path for flexibility
315
- const query = parsedUrl.query;
316
- if (query.code) {
317
- const address = server.address();
318
- const port = address && typeof address !== 'string' ? address.port : 0;
319
- res.writeHead(200, { 'Content-Type': 'text/html' });
320
- res.end('<h1>Authentication successful!</h1><p>You can close this window and return to the app.</p><script>window.close();</script>');
776
+ catch (_a) {
777
+ return {};
778
+ }
779
+ };
780
+ const writeConfig = (config) => {
781
+ try {
782
+ const sanitizedConfig = Object.assign({}, config);
783
+ if (Array.isArray(sanitizedConfig.workspaceSnapshots) && closedWorkspaceIds.size > 0) {
784
+ sanitizedConfig.workspaceSnapshots = sanitizedConfig.workspaceSnapshots.filter((item) => { var _a; return !closedWorkspaceIds.has(String((_a = item === null || item === void 0 ? void 0 : item.id) !== null && _a !== void 0 ? _a : '')); });
785
+ }
786
+ fs.writeFileSync(configPath, JSON.stringify(sanitizedConfig));
787
+ broadcastConfigUpdated(sanitizedConfig);
788
+ return true;
789
+ }
790
+ catch (_a) {
791
+ return false;
792
+ }
793
+ };
794
+ const getWorkspaceSnapshots = () => {
795
+ const config = readConfig();
796
+ return Array.isArray(config.workspaceSnapshots)
797
+ ? config.workspaceSnapshots.filter((item) => Boolean(item && typeof item === 'object' && typeof item.id === 'string'))
798
+ : [];
799
+ };
800
+ electron_1.ipcMain.handle('save-config', (event, config) => __awaiter(void 0, void 0, void 0, function* () {
801
+ return writeConfig(config);
802
+ }));
803
+ electron_1.ipcMain.handle('load-config', () => __awaiter(void 0, void 0, void 0, function* () {
804
+ return readConfig();
805
+ }));
806
+ electron_1.ipcMain.handle('load-workspace-snapshot', (_event, workspaceId) => __awaiter(void 0, void 0, void 0, function* () {
807
+ var _a;
808
+ const id = String(workspaceId !== null && workspaceId !== void 0 ? workspaceId : '').trim();
809
+ if (!id)
810
+ return null;
811
+ return (_a = getWorkspaceSnapshots().find(snapshot => snapshot.id === id)) !== null && _a !== void 0 ? _a : null;
812
+ }));
813
+ electron_1.ipcMain.handle('save-workspace-snapshot', (_event, snapshot) => __awaiter(void 0, void 0, void 0, function* () {
814
+ var _a;
815
+ const id = String((_a = snapshot === null || snapshot === void 0 ? void 0 : snapshot.id) !== null && _a !== void 0 ? _a : '').trim();
816
+ if (!id)
817
+ return { ok: false };
818
+ if (closedWorkspaceIds.has(id))
819
+ return { ok: false, error: 'Workspace is closed.' };
820
+ const config = readConfig();
821
+ const current = Array.isArray(config.workspaceSnapshots) ? config.workspaceSnapshots : [];
822
+ const nextSnapshot = Object.assign(Object.assign({}, snapshot), { id, updatedAt: typeof snapshot.updatedAt === 'string' ? snapshot.updatedAt : new Date().toISOString() });
823
+ const next = [
824
+ nextSnapshot,
825
+ ...current.filter((item) => (item === null || item === void 0 ? void 0 : item.id) !== id),
826
+ ].slice(0, 12);
827
+ const ok = writeConfig(Object.assign(Object.assign({}, config), { workspaceSnapshots: next }));
828
+ return { ok };
829
+ }));
830
+ electron_1.ipcMain.handle('register-workspace-folder', (event, rawFolderId) => __awaiter(void 0, void 0, void 0, function* () {
831
+ const webContentsId = event.sender.id;
832
+ const folderId = String(rawFolderId !== null && rawFolderId !== void 0 ? rawFolderId : '').trim();
833
+ if (!folderId) {
834
+ cleanupWindowFolder(webContentsId);
835
+ return { ok: true };
836
+ }
837
+ pruneStaleFolderOwners(folderId);
838
+ cleanupWindowFolder(webContentsId);
839
+ let owners = folderOwners.get(folderId);
840
+ if (!owners) {
841
+ owners = new Set();
842
+ folderOwners.set(folderId, owners);
843
+ }
844
+ owners.add(webContentsId);
845
+ windowFolders.set(webContentsId, folderId);
846
+ broadcastWorkspaceWindowsUpdated();
847
+ return { ok: true };
848
+ }));
849
+ electron_1.ipcMain.handle('unregister-workspace-folder', (event, rawFolderId) => __awaiter(void 0, void 0, void 0, function* () {
850
+ const webContentsId = event.sender.id;
851
+ const folderId = rawFolderId == null ? null : String(rawFolderId).trim();
852
+ if (!folderId || windowFolders.get(webContentsId) === folderId) {
853
+ cleanupWindowFolder(webContentsId);
854
+ }
855
+ broadcastWorkspaceWindowsUpdated();
856
+ return { ok: true };
857
+ }));
858
+ electron_1.ipcMain.handle('new-window', (_event, workspaceId) => __awaiter(void 0, void 0, void 0, function* () {
859
+ createWindow(String(workspaceId !== null && workspaceId !== void 0 ? workspaceId : '').trim() || createWorkspaceId());
860
+ return { ok: true };
861
+ }));
862
+ electron_1.ipcMain.handle('set-workspace-window-meta', (event, payload) => __awaiter(void 0, void 0, void 0, function* () {
863
+ var _a, _b, _c, _d;
864
+ const webContentsId = event.sender.id;
865
+ const title = String((_a = payload === null || payload === void 0 ? void 0 : payload.title) !== null && _a !== void 0 ? _a : '').trim();
866
+ if (title) {
867
+ windowTitles.set(webContentsId, title);
868
+ const win = electron_1.BrowserWindow.fromWebContents(event.sender);
869
+ const workspaceId = (_b = windowWorkspaces.get(webContentsId)) !== null && _b !== void 0 ? _b : '';
870
+ if (win && !win.isDestroyed() && workspaceId) {
871
+ syncWindowTitleForAgentControl(win, workspaceId);
872
+ }
873
+ }
874
+ const folderId = String((_c = payload === null || payload === void 0 ? void 0 : payload.folderId) !== null && _c !== void 0 ? _c : '').trim();
875
+ if (folderId) {
876
+ windowFolders.set(webContentsId, folderId);
877
+ }
878
+ const folderPath = String((_d = payload === null || payload === void 0 ? void 0 : payload.folderPath) !== null && _d !== void 0 ? _d : '').trim();
879
+ if (folderPath) {
880
+ windowFolderPaths.set(webContentsId, folderPath);
881
+ }
882
+ else {
883
+ windowFolderPaths.delete(webContentsId);
884
+ }
885
+ broadcastWorkspaceWindowsUpdated();
886
+ return { ok: true };
887
+ }));
888
+ electron_1.ipcMain.handle('list-workspace-windows', () => __awaiter(void 0, void 0, void 0, function* () { return getWorkspaceWindows(); }));
889
+ electron_1.ipcMain.handle('focus-workspace-window', (_event, webContentsId) => __awaiter(void 0, void 0, void 0, function* () {
890
+ return ({
891
+ ok: focusWindowByWebContentsId(Number(webContentsId)),
892
+ });
893
+ }));
894
+ electron_1.ipcMain.handle('close-workspace-window', (_event, webContentsId) => __awaiter(void 0, void 0, void 0, function* () {
895
+ const id = Number(webContentsId);
896
+ const win = electron_1.BrowserWindow.getAllWindows().find(w => w.webContents.id === id);
897
+ if (!win || win.isDestroyed())
898
+ return { ok: false };
899
+ const workspaceId = windowWorkspaces.get(id);
900
+ if (!workspaceId) {
901
+ closingWindowIds.add(id);
902
+ broadcastWorkspaceWindowsUpdated();
903
+ win.destroy();
904
+ setImmediate(() => broadcastWorkspaceWindowsUpdated());
905
+ return { ok: true };
906
+ }
907
+ return closeSessionByWorkspaceId(workspaceId);
908
+ }));
909
+ electron_1.ipcMain.handle('close-all-workspace-windows', () => __awaiter(void 0, void 0, void 0, function* () { return closeAllWorkspaceSessions(); }));
910
+ electron_1.ipcMain.handle('close-other-workspace-windows', (event) => __awaiter(void 0, void 0, void 0, function* () { return closeOtherWorkspaceSessions(event.sender.id); }));
911
+ electron_1.ipcMain.handle('get-agent-control-mode', (event) => __awaiter(void 0, void 0, void 0, function* () {
912
+ var _a;
913
+ const workspaceId = (_a = windowWorkspaces.get(event.sender.id)) !== null && _a !== void 0 ? _a : '';
914
+ return {
915
+ mode: workspaceId && agentControlledSessions.has(workspaceId) ? 'agent' : 'manual',
916
+ };
917
+ }));
918
+ electron_1.ipcMain.handle('release-agent-control', (event) => __awaiter(void 0, void 0, void 0, function* () {
919
+ var _a;
920
+ const workspaceId = (_a = windowWorkspaces.get(event.sender.id)) !== null && _a !== void 0 ? _a : '';
921
+ if (workspaceId) {
922
+ setSessionAgentControlMode(workspaceId, 'manual');
923
+ }
924
+ return { ok: true, mode: 'manual' };
925
+ }));
926
+ electron_1.ipcMain.handle('telegram-send-test', (_event, payload) => __awaiter(void 0, void 0, void 0, function* () {
927
+ var _a, _b;
928
+ const botToken = String((_a = payload === null || payload === void 0 ? void 0 : payload.botToken) !== null && _a !== void 0 ? _a : '').trim();
929
+ const chatId = String((_b = payload === null || payload === void 0 ? void 0 : payload.chatId) !== null && _b !== void 0 ? _b : '').trim();
930
+ if (!botToken || !chatId) {
931
+ return { ok: false, error: 'Укажите токен бота и ID чата.' };
932
+ }
933
+ return telegramSendMessageViaBot(botToken, chatId, 'Docs Combiner — тест оповещений.');
934
+ }));
935
+ electron_1.ipcMain.handle('telegram-send-message', (_event, payload) => __awaiter(void 0, void 0, void 0, function* () {
936
+ var _a, _b, _c;
937
+ const botToken = String((_a = payload === null || payload === void 0 ? void 0 : payload.botToken) !== null && _a !== void 0 ? _a : '').trim();
938
+ const chatId = String((_b = payload === null || payload === void 0 ? void 0 : payload.chatId) !== null && _b !== void 0 ? _b : '').trim();
939
+ const text = String((_c = payload === null || payload === void 0 ? void 0 : payload.text) !== null && _c !== void 0 ? _c : '');
940
+ if (!botToken || !chatId) {
941
+ return { ok: false, error: 'Укажите токен бота и ID чата.' };
942
+ }
943
+ if (!text.trim()) {
944
+ return { ok: false, error: 'Пустой текст сообщения.' };
945
+ }
946
+ return telegramSendMessageViaBot(botToken, chatId, text);
947
+ }));
948
+ electron_1.ipcMain.handle('save-file', (event, content, filename) => __awaiter(void 0, void 0, void 0, function* () {
949
+ const { canceled, filePath } = yield electron_1.dialog.showSaveDialog({
950
+ defaultPath: filename,
951
+ });
952
+ if (canceled || !filePath)
953
+ return false;
954
+ fs.writeFileSync(filePath, content);
955
+ return true;
956
+ }));
957
+ electron_1.ipcMain.handle('start-auth', (event, clientId) => __awaiter(void 0, void 0, void 0, function* () {
958
+ abortPendingGoogleAuth('AUTH_SUPERSEDED');
959
+ return new Promise((resolve, reject) => {
960
+ const server = http.createServer((req, res) => __awaiter(void 0, void 0, void 0, function* () {
961
+ try {
962
+ if (!req.url)
963
+ return;
964
+ const parsedUrl = url.parse(req.url, true);
965
+ // Allow any path for flexibility
966
+ const query = parsedUrl.query;
967
+ if (query.code) {
968
+ const address = server.address();
969
+ const port = address && typeof address !== 'string' ? address.port : 0;
970
+ res.writeHead(200, { 'Content-Type': 'text/html' });
971
+ res.end('<h1>Authentication successful!</h1><p>You can close this window and return to the app.</p><script>window.close();</script>');
972
+ pendingGoogleAuth = null;
973
+ try {
974
+ server.close();
975
+ }
976
+ catch (_a) {
977
+ /* ignore */
978
+ }
979
+ resolve({ code: query.code, redirectUri: `http://127.0.0.1:${port}` });
980
+ }
981
+ else if (query.error) {
982
+ res.writeHead(400, { 'Content-Type': 'text/html' });
983
+ res.end('Authentication failed.');
984
+ pendingGoogleAuth = null;
985
+ try {
986
+ server.close();
987
+ }
988
+ catch (_b) {
989
+ /* ignore */
990
+ }
991
+ reject(new Error(query.error));
992
+ }
993
+ }
994
+ catch (e) {
321
995
  pendingGoogleAuth = null;
322
996
  try {
323
997
  server.close();
324
998
  }
325
- catch (_a) {
999
+ catch (_c) {
326
1000
  /* ignore */
327
1001
  }
328
- resolve({ code: query.code, redirectUri: `http://127.0.0.1:${port}` });
1002
+ reject(e instanceof Error ? e : new Error(String(e)));
329
1003
  }
330
- else if (query.error) {
331
- res.writeHead(400, { 'Content-Type': 'text/html' });
332
- res.end('Authentication failed.');
1004
+ }));
1005
+ server.on('error', (err) => {
1006
+ if ((pendingGoogleAuth === null || pendingGoogleAuth === void 0 ? void 0 : pendingGoogleAuth.server) === server) {
333
1007
  pendingGoogleAuth = null;
1008
+ }
1009
+ reject(err);
1010
+ });
1011
+ server.listen(0, '127.0.0.1', () => {
1012
+ const address = server.address();
1013
+ if (!address || typeof address === 'string') {
334
1014
  try {
335
1015
  server.close();
336
1016
  }
337
- catch (_b) {
1017
+ catch (_a) {
338
1018
  /* ignore */
339
1019
  }
340
- reject(new Error(query.error));
341
- }
342
- }
343
- catch (e) {
344
- pendingGoogleAuth = null;
345
- try {
346
- server.close();
347
- }
348
- catch (_c) {
349
- /* ignore */
1020
+ reject(new Error('Failed to get server port'));
1021
+ return;
350
1022
  }
351
- reject(e instanceof Error ? e : new Error(String(e)));
352
- }
353
- }));
354
- server.on('error', (err) => {
355
- if ((pendingGoogleAuth === null || pendingGoogleAuth === void 0 ? void 0 : pendingGoogleAuth.server) === server) {
356
- pendingGoogleAuth = null;
357
- }
358
- reject(err);
1023
+ const port = address.port;
1024
+ const redirectUri = `http://127.0.0.1:${port}`;
1025
+ // Google Drive Scope: https://www.googleapis.com/auth/drive (Full Access)
1026
+ // Added prompt=consent to force new refresh token with correct scopes
1027
+ const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&prompt=consent`;
1028
+ pendingGoogleAuth = { authUrl, server, resolve, reject };
1029
+ electron_1.shell.openExternal(authUrl);
1030
+ });
359
1031
  });
360
- server.listen(0, '127.0.0.1', () => {
361
- const address = server.address();
362
- if (!address || typeof address === 'string') {
1032
+ }));
1033
+ electron_1.ipcMain.handle('reopen-pending-auth-browser', () => __awaiter(void 0, void 0, void 0, function* () {
1034
+ if (!pendingGoogleAuth) {
1035
+ return { ok: false };
1036
+ }
1037
+ yield electron_1.shell.openExternal(pendingGoogleAuth.authUrl);
1038
+ return { ok: true };
1039
+ }));
1040
+ electron_1.ipcMain.handle('cancel-pending-auth', () => __awaiter(void 0, void 0, void 0, function* () {
1041
+ if (!pendingGoogleAuth) {
1042
+ return { ok: false };
1043
+ }
1044
+ abortPendingGoogleAuth('LOGIN_CANCELLED');
1045
+ return { ok: true };
1046
+ }));
1047
+ electron_1.ipcMain.handle('open-external', (event, url) => __awaiter(void 0, void 0, void 0, function* () {
1048
+ yield electron_1.shell.openExternal(url);
1049
+ }));
1050
+ electron_1.ipcMain.handle('log', (event, level, ...args) => __awaiter(void 0, void 0, void 0, function* () {
1051
+ const timestamp = new Date().toISOString();
1052
+ const message = args.map(arg => {
1053
+ if (typeof arg === 'object') {
363
1054
  try {
364
- server.close();
1055
+ return JSON.stringify(arg, null, 2);
365
1056
  }
366
1057
  catch (_a) {
367
- /* ignore */
1058
+ return String(arg);
368
1059
  }
369
- reject(new Error('Failed to get server port'));
370
- return;
371
1060
  }
372
- const port = address.port;
373
- const redirectUri = `http://127.0.0.1:${port}`;
374
- // Google Drive Scope: https://www.googleapis.com/auth/drive (Full Access)
375
- // Added prompt=consent to force new refresh token with correct scopes
376
- const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&prompt=consent`;
377
- pendingGoogleAuth = { authUrl, server, resolve, reject };
378
- electron_1.shell.openExternal(authUrl);
379
- });
380
- });
381
- }));
382
- electron_1.ipcMain.handle('reopen-pending-auth-browser', () => __awaiter(void 0, void 0, void 0, function* () {
383
- if (!pendingGoogleAuth) {
384
- return { ok: false };
1061
+ return String(arg);
1062
+ }).join(' ');
1063
+ const formattedMessage = `[${timestamp}] ${message}`;
1064
+ switch (level) {
1065
+ case 'log':
1066
+ case 'info':
1067
+ console.log(formattedMessage);
1068
+ break;
1069
+ case 'warn':
1070
+ console.warn(formattedMessage);
1071
+ break;
1072
+ case 'error':
1073
+ console.error(formattedMessage);
1074
+ break;
1075
+ }
1076
+ }));
1077
+ const snapshotsToRestore = getWorkspaceSnapshots();
1078
+ if (snapshotsToRestore.length > 0) {
1079
+ snapshotsToRestore.forEach(snapshot => createWindow(snapshot.id));
385
1080
  }
386
- yield electron_1.shell.openExternal(pendingGoogleAuth.authUrl);
387
- return { ok: true };
388
- }));
389
- electron_1.ipcMain.handle('cancel-pending-auth', () => __awaiter(void 0, void 0, void 0, function* () {
390
- if (!pendingGoogleAuth) {
391
- return { ok: false };
1081
+ else {
1082
+ createWindow();
392
1083
  }
393
- abortPendingGoogleAuth('LOGIN_CANCELLED');
394
- return { ok: true };
395
- }));
396
- electron_1.ipcMain.handle('open-external', (event, url) => __awaiter(void 0, void 0, void 0, function* () {
397
- yield electron_1.shell.openExternal(url);
398
- }));
399
- electron_1.ipcMain.handle('log', (event, level, ...args) => __awaiter(void 0, void 0, void 0, function* () {
400
- const timestamp = new Date().toISOString();
401
- const message = args.map(arg => {
402
- if (typeof arg === 'object') {
403
- try {
404
- return JSON.stringify(arg, null, 2);
405
- }
406
- catch (_a) {
407
- return String(arg);
408
- }
1084
+ electron_1.app.on('activate', function () {
1085
+ if (electron_1.BrowserWindow.getAllWindows().length === 0) {
1086
+ const snapshot = getWorkspaceSnapshots()[0];
1087
+ createWindow(snapshot === null || snapshot === void 0 ? void 0 : snapshot.id);
409
1088
  }
410
- return String(arg);
411
- }).join(' ');
412
- const formattedMessage = `[${timestamp}] ${message}`;
413
- switch (level) {
414
- case 'log':
415
- case 'info':
416
- console.log(formattedMessage);
417
- break;
418
- case 'warn':
419
- console.warn(formattedMessage);
420
- break;
421
- case 'error':
422
- console.error(formattedMessage);
423
- break;
424
- }
425
- }));
426
- createWindow();
427
- electron_1.app.on('activate', function () {
428
- if (electron_1.BrowserWindow.getAllWindows().length === 0)
429
- createWindow();
1089
+ });
430
1090
  });
431
- });
432
1091
  electron_1.app.on('window-all-closed', function () {
433
1092
  electron_1.app.quit();
434
1093
  });
1094
+ electron_1.app.on('before-quit', () => {
1095
+ if (agentApiServer) {
1096
+ agentApiServer.close();
1097
+ agentApiServer = null;
1098
+ }
1099
+ });
435
1100
  process.on('uncaughtException', (err) => {
436
1101
  console.error('[Main] uncaughtException:', err);
437
1102
  });