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.
- package/dist/agentApi/agentControlClaim.js +26 -0
- package/dist/agentApi/approachMatrixView.js +19 -0
- package/dist/agentApi/capabilitiesMeta.js +78 -0
- package/dist/agentApi/catalogUrlPresets.js +26 -0
- package/dist/agentApi/constants.js +11 -0
- package/dist/agentApi/creativeSelections.js +25 -0
- package/dist/agentApi/escalation.js +221 -0
- package/dist/agentApi/generatedPairsGuard.js +85 -0
- package/dist/agentApi/httpHelpers.js +77 -0
- package/dist/agentApi/imagesReadiness.js +60 -0
- package/dist/agentApi/jobStatus.js +113 -0
- package/dist/agentApi/productImageDrive.js +39 -0
- package/dist/agentApi/regenerateImages.js +125 -0
- package/dist/agentApi/rendererTypes.js +3 -0
- package/dist/agentApi/routes.js +440 -0
- package/dist/agentApi/sessionSnapshot.js +82 -0
- package/dist/agentApi/sessionTypes.js +2 -0
- package/dist/agentApi/spec.js +1045 -0
- package/dist/agentApi/types.js +2 -0
- package/dist/agentApi/validation.js +32 -0
- package/dist/campaignsCsv.js +420 -0
- package/dist/contentPairs.js +62 -0
- package/dist/flexcardBalance.js +88 -0
- package/dist/integrations/driveApi.js +420 -0
- package/dist/integrations/httpTimeout.js +116 -0
- package/dist/integrations/openrouter.js +532 -0
- package/dist/main.js +830 -165
- package/dist/models.js +17 -0
- package/dist/offerSettings.js +19 -0
- package/dist/preload.js +28 -0
- package/dist/promptOverrides.js +437 -0
- package/dist/prompts.js +1243 -0
- package/dist/renderer.js +19 -19
- package/dist/renderer.js.LICENSE.txt +4 -0
- package/dist/renderer.js.map +1 -1
- package/dist/server/app.js +378 -0
- package/dist/server/auth.js +74 -0
- package/dist/server/contentJobs.js +367 -0
- package/dist/server/driveCatalog.js +122 -0
- package/dist/server/driveProxy.js +220 -0
- package/dist/server/flexcardMeta.js +29 -0
- package/dist/server/googleAuth.js +84 -0
- package/dist/server/imageAssets.js +87 -0
- package/dist/server/imageJobs.js +776 -0
- package/dist/server/index.js +38 -0
- package/dist/server/jobEvents.js +116 -0
- package/dist/server/jobs.js +358 -0
- package/dist/server/openRouterMeta.js +58 -0
- package/dist/server/spec.js +544 -0
- package/dist/server/storage.js +133 -0
- package/dist/server/telegram.js +53 -0
- package/dist/server/workspaceSnapshot.js +273 -0
- 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: [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
744
|
+
// win.webContents.openDevTools();
|
|
243
745
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
260
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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 (
|
|
999
|
+
catch (_c) {
|
|
326
1000
|
/* ignore */
|
|
327
1001
|
}
|
|
328
|
-
|
|
1002
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
329
1003
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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 (
|
|
1017
|
+
catch (_a) {
|
|
338
1018
|
/* ignore */
|
|
339
1019
|
}
|
|
340
|
-
reject(new 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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
pendingGoogleAuth =
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
1055
|
+
return JSON.stringify(arg, null, 2);
|
|
365
1056
|
}
|
|
366
1057
|
catch (_a) {
|
|
367
|
-
|
|
1058
|
+
return String(arg);
|
|
368
1059
|
}
|
|
369
|
-
reject(new Error('Failed to get server port'));
|
|
370
|
-
return;
|
|
371
1060
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
387
|
-
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
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
|
});
|