openwork 0.1.1 → 0.1.3
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/README.md +1 -0
- package/out/main/index.js +216 -40
- package/out/preload/index.js +3 -0
- package/out/renderer/assets/{index-CK8V1Wgb.css → index-DQkbeNKF.css} +174 -32
- package/out/renderer/assets/{index-BCFQ1MZ2.js → index-nnGibHq-.js} +43387 -42390
- package/out/renderer/index.html +2 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -43,6 +43,7 @@ Or configure them in-app via the settings panel.
|
|
|
43
43
|
| --------- | ----------------------------------------------------------------- |
|
|
44
44
|
| Anthropic | Claude Opus 4.5, Claude Sonnet 4.5, Claude Haiku 4.5, Claude Opus 4.1, Claude Sonnet 4 |
|
|
45
45
|
| OpenAI | GPT-5.2, GPT-5.1, o3, o3 Mini, o4 Mini, o1, GPT-4.1, GPT-4o |
|
|
46
|
+
| Google | Gemini 3 Pro Preview, Gemini 2.5 Pro, Gemini 2.5 Flash, Gemini 2.5 Flash Lite |
|
|
46
47
|
|
|
47
48
|
## Contributing
|
|
48
49
|
|
package/out/main/index.js
CHANGED
|
@@ -10,6 +10,7 @@ const fs = require("fs");
|
|
|
10
10
|
const os = require("os");
|
|
11
11
|
const anthropic = require("@langchain/anthropic");
|
|
12
12
|
const openai = require("@langchain/openai");
|
|
13
|
+
const googleGenai = require("@langchain/google-genai");
|
|
13
14
|
const initSqlJs = require("sql.js");
|
|
14
15
|
const langgraphCheckpoint = require("@langchain/langgraph-checkpoint");
|
|
15
16
|
const node_child_process = require("node:child_process");
|
|
@@ -104,7 +105,8 @@ const OPENWORK_DIR = path.join(os.homedir(), ".openwork");
|
|
|
104
105
|
const ENV_FILE = path.join(OPENWORK_DIR, ".env");
|
|
105
106
|
const ENV_VAR_NAMES = {
|
|
106
107
|
anthropic: "ANTHROPIC_API_KEY",
|
|
107
|
-
openai: "OPENAI_API_KEY"
|
|
108
|
+
openai: "OPENAI_API_KEY",
|
|
109
|
+
google: "GOOGLE_API_KEY"
|
|
108
110
|
};
|
|
109
111
|
function getOpenworkDir() {
|
|
110
112
|
if (!fs.existsSync(OPENWORK_DIR)) {
|
|
@@ -115,8 +117,21 @@ function getOpenworkDir() {
|
|
|
115
117
|
function getDbPath() {
|
|
116
118
|
return path.join(getOpenworkDir(), "openwork.sqlite");
|
|
117
119
|
}
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
+
function getThreadCheckpointDir() {
|
|
121
|
+
const dir = path.join(getOpenworkDir(), "threads");
|
|
122
|
+
if (!fs.existsSync(dir)) {
|
|
123
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
124
|
+
}
|
|
125
|
+
return dir;
|
|
126
|
+
}
|
|
127
|
+
function getThreadCheckpointPath(threadId) {
|
|
128
|
+
return path.join(getThreadCheckpointDir(), `${threadId}.sqlite`);
|
|
129
|
+
}
|
|
130
|
+
function deleteThreadCheckpoint(threadId) {
|
|
131
|
+
const path2 = getThreadCheckpointPath(threadId);
|
|
132
|
+
if (fs.existsSync(path2)) {
|
|
133
|
+
fs.unlinkSync(path2);
|
|
134
|
+
}
|
|
120
135
|
}
|
|
121
136
|
function getEnvFilePath() {
|
|
122
137
|
return ENV_FILE;
|
|
@@ -175,7 +190,8 @@ const store = new Store({
|
|
|
175
190
|
});
|
|
176
191
|
const PROVIDERS = [
|
|
177
192
|
{ id: "anthropic", name: "Anthropic" },
|
|
178
|
-
{ id: "openai", name: "OpenAI" }
|
|
193
|
+
{ id: "openai", name: "OpenAI" },
|
|
194
|
+
{ id: "google", name: "Google" }
|
|
179
195
|
];
|
|
180
196
|
const AVAILABLE_MODELS = [
|
|
181
197
|
// Anthropic Claude 4.5 series (latest as of Jan 2026)
|
|
@@ -310,6 +326,39 @@ const AVAILABLE_MODELS = [
|
|
|
310
326
|
model: "gpt-4o-mini",
|
|
311
327
|
description: "Cost-efficient variant with faster response times",
|
|
312
328
|
available: true
|
|
329
|
+
},
|
|
330
|
+
// Google Gemini models
|
|
331
|
+
{
|
|
332
|
+
id: "gemini-3-pro-preview",
|
|
333
|
+
name: "Gemini 3 Pro Preview",
|
|
334
|
+
provider: "google",
|
|
335
|
+
model: "gemini-3-pro-preview",
|
|
336
|
+
description: "State-of-the-art reasoning and multimodal understanding",
|
|
337
|
+
available: true
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
id: "gemini-2.5-pro",
|
|
341
|
+
name: "Gemini 2.5 Pro",
|
|
342
|
+
provider: "google",
|
|
343
|
+
model: "gemini-2.5-pro",
|
|
344
|
+
description: "High-capability model for complex reasoning and coding",
|
|
345
|
+
available: true
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
id: "gemini-2.5-flash",
|
|
349
|
+
name: "Gemini 2.5 Flash",
|
|
350
|
+
provider: "google",
|
|
351
|
+
model: "gemini-2.5-flash",
|
|
352
|
+
description: "Lightning-fast with balance of intelligence and latency",
|
|
353
|
+
available: true
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
id: "gemini-2.5-flash-lite",
|
|
357
|
+
name: "Gemini 2.5 Flash Lite",
|
|
358
|
+
provider: "google",
|
|
359
|
+
model: "gemini-2.5-flash-lite",
|
|
360
|
+
description: "Fast, low-cost, high-performance model",
|
|
361
|
+
available: true
|
|
313
362
|
}
|
|
314
363
|
];
|
|
315
364
|
function registerModelHandlers(ipcMain) {
|
|
@@ -495,6 +544,47 @@ function registerModelHandlers(ipcMain) {
|
|
|
495
544
|
}
|
|
496
545
|
}
|
|
497
546
|
);
|
|
547
|
+
ipcMain.handle(
|
|
548
|
+
"workspace:readBinaryFile",
|
|
549
|
+
async (_event, { threadId, filePath }) => {
|
|
550
|
+
const { getThread: getThread2 } = await Promise.resolve().then(() => index);
|
|
551
|
+
const thread = getThread2(threadId);
|
|
552
|
+
const metadata = thread?.metadata ? JSON.parse(thread.metadata) : {};
|
|
553
|
+
const workspacePath = metadata.workspacePath;
|
|
554
|
+
if (!workspacePath) {
|
|
555
|
+
return {
|
|
556
|
+
success: false,
|
|
557
|
+
error: "No workspace folder linked"
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
const relativePath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
562
|
+
const fullPath = path__namespace.join(workspacePath, relativePath);
|
|
563
|
+
const resolvedPath = path__namespace.resolve(fullPath);
|
|
564
|
+
const resolvedWorkspace = path__namespace.resolve(workspacePath);
|
|
565
|
+
if (!resolvedPath.startsWith(resolvedWorkspace)) {
|
|
566
|
+
return { success: false, error: "Access denied: path outside workspace" };
|
|
567
|
+
}
|
|
568
|
+
const stat = await fs__namespace$1.stat(fullPath);
|
|
569
|
+
if (stat.isDirectory()) {
|
|
570
|
+
return { success: false, error: "Cannot read directory as file" };
|
|
571
|
+
}
|
|
572
|
+
const buffer = await fs__namespace$1.readFile(fullPath);
|
|
573
|
+
const base64 = buffer.toString("base64");
|
|
574
|
+
return {
|
|
575
|
+
success: true,
|
|
576
|
+
content: base64,
|
|
577
|
+
size: stat.size,
|
|
578
|
+
modified_at: stat.mtime.toISOString()
|
|
579
|
+
};
|
|
580
|
+
} catch (e) {
|
|
581
|
+
return {
|
|
582
|
+
success: false,
|
|
583
|
+
error: e instanceof Error ? e.message : "Unknown error"
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
);
|
|
498
588
|
}
|
|
499
589
|
function getDefaultModel() {
|
|
500
590
|
return store.get("defaultModel", "claude-sonnet-4-5-20250929");
|
|
@@ -516,8 +606,29 @@ class SqlJsSaver extends langgraphCheckpoint.BaseCheckpointSaver {
|
|
|
516
606
|
if (this.db) return;
|
|
517
607
|
const SQL = await initSqlJs();
|
|
518
608
|
if (fs.existsSync(this.dbPath)) {
|
|
519
|
-
const
|
|
520
|
-
|
|
609
|
+
const stats = fs.statSync(this.dbPath);
|
|
610
|
+
const MAX_DB_SIZE = 100 * 1024 * 1024;
|
|
611
|
+
if (stats.size > MAX_DB_SIZE) {
|
|
612
|
+
console.warn(
|
|
613
|
+
`[SqlJsSaver] Database file is too large (${Math.round(stats.size / 1024 / 1024)}MB). Creating fresh database to prevent memory issues.`
|
|
614
|
+
);
|
|
615
|
+
const backupPath = this.dbPath + ".bak." + Date.now();
|
|
616
|
+
try {
|
|
617
|
+
fs.renameSync(this.dbPath, backupPath);
|
|
618
|
+
console.log(`[SqlJsSaver] Old database backed up to: ${backupPath}`);
|
|
619
|
+
} catch (e) {
|
|
620
|
+
console.warn("[SqlJsSaver] Could not backup old database:", e);
|
|
621
|
+
try {
|
|
622
|
+
fs.unlinkSync(this.dbPath);
|
|
623
|
+
} catch (e2) {
|
|
624
|
+
console.error("[SqlJsSaver] Could not delete old database:", e2);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
this.db = new SQL.Database();
|
|
628
|
+
} else {
|
|
629
|
+
const buffer = fs.readFileSync(this.dbPath);
|
|
630
|
+
this.db = new SQL.Database(buffer);
|
|
631
|
+
}
|
|
521
632
|
} else {
|
|
522
633
|
const dir = path.dirname(this.dbPath);
|
|
523
634
|
if (!fs.existsSync(dir)) {
|
|
@@ -1075,14 +1186,24 @@ function getSystemPrompt(workspacePath) {
|
|
|
1075
1186
|
`;
|
|
1076
1187
|
return workingDirSection + BASE_SYSTEM_PROMPT;
|
|
1077
1188
|
}
|
|
1078
|
-
|
|
1079
|
-
async function getCheckpointer() {
|
|
1189
|
+
const checkpointers = /* @__PURE__ */ new Map();
|
|
1190
|
+
async function getCheckpointer(threadId) {
|
|
1191
|
+
let checkpointer = checkpointers.get(threadId);
|
|
1080
1192
|
if (!checkpointer) {
|
|
1081
|
-
|
|
1193
|
+
const dbPath = getThreadCheckpointPath(threadId);
|
|
1194
|
+
checkpointer = new SqlJsSaver(dbPath);
|
|
1082
1195
|
await checkpointer.initialize();
|
|
1196
|
+
checkpointers.set(threadId, checkpointer);
|
|
1083
1197
|
}
|
|
1084
1198
|
return checkpointer;
|
|
1085
1199
|
}
|
|
1200
|
+
async function closeCheckpointer(threadId) {
|
|
1201
|
+
const checkpointer = checkpointers.get(threadId);
|
|
1202
|
+
if (checkpointer) {
|
|
1203
|
+
await checkpointer.close();
|
|
1204
|
+
checkpointers.delete(threadId);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1086
1207
|
function getModelInstance(modelId) {
|
|
1087
1208
|
const model = modelId || getDefaultModel();
|
|
1088
1209
|
console.log("[Runtime] Using model:", model);
|
|
@@ -1106,22 +1227,36 @@ function getModelInstance(modelId) {
|
|
|
1106
1227
|
model,
|
|
1107
1228
|
openAIApiKey: apiKey
|
|
1108
1229
|
});
|
|
1230
|
+
} else if (model.startsWith("gemini")) {
|
|
1231
|
+
const apiKey = getApiKey("google");
|
|
1232
|
+
console.log("[Runtime] Google API key present:", !!apiKey);
|
|
1233
|
+
if (!apiKey) {
|
|
1234
|
+
throw new Error("Google API key not configured");
|
|
1235
|
+
}
|
|
1236
|
+
return new googleGenai.ChatGoogleGenerativeAI({
|
|
1237
|
+
model,
|
|
1238
|
+
apiKey
|
|
1239
|
+
});
|
|
1109
1240
|
}
|
|
1110
1241
|
return model;
|
|
1111
1242
|
}
|
|
1112
1243
|
async function createAgentRuntime(options) {
|
|
1113
|
-
const { modelId, workspacePath } = options;
|
|
1244
|
+
const { threadId, modelId, workspacePath } = options;
|
|
1245
|
+
if (!threadId) {
|
|
1246
|
+
throw new Error("Thread ID is required for checkpointing.");
|
|
1247
|
+
}
|
|
1114
1248
|
if (!workspacePath) {
|
|
1115
1249
|
throw new Error(
|
|
1116
1250
|
"Workspace path is required. Please select a workspace folder before running the agent."
|
|
1117
1251
|
);
|
|
1118
1252
|
}
|
|
1119
1253
|
console.log("[Runtime] Creating agent runtime...");
|
|
1254
|
+
console.log("[Runtime] Thread ID:", threadId);
|
|
1120
1255
|
console.log("[Runtime] Workspace path:", workspacePath);
|
|
1121
1256
|
const model = getModelInstance(modelId);
|
|
1122
1257
|
console.log("[Runtime] Model instance created:", typeof model);
|
|
1123
|
-
const
|
|
1124
|
-
console.log("[Runtime] Checkpointer ready");
|
|
1258
|
+
const checkpointer = await getCheckpointer(threadId);
|
|
1259
|
+
console.log("[Runtime] Checkpointer ready for thread:", threadId);
|
|
1125
1260
|
const backend = new LocalSandbox({
|
|
1126
1261
|
rootDir: workspacePath,
|
|
1127
1262
|
virtualMode: false,
|
|
@@ -1144,7 +1279,7 @@ async function createAgentRuntime(options) {
|
|
|
1144
1279
|
The workspace root is: ${workspacePath}`;
|
|
1145
1280
|
const agent = deepagents.createDeepAgent({
|
|
1146
1281
|
model,
|
|
1147
|
-
checkpointer
|
|
1282
|
+
checkpointer,
|
|
1148
1283
|
backend,
|
|
1149
1284
|
systemPrompt,
|
|
1150
1285
|
// Custom filesystem prompt for absolute paths (requires deepagents update)
|
|
@@ -1360,7 +1495,7 @@ function registerAgentHandlers(ipcMain) {
|
|
|
1360
1495
|
});
|
|
1361
1496
|
return;
|
|
1362
1497
|
}
|
|
1363
|
-
const agent = await createAgentRuntime({ workspacePath });
|
|
1498
|
+
const agent = await createAgentRuntime({ threadId, workspacePath });
|
|
1364
1499
|
const humanMessage = new messages.HumanMessage(message);
|
|
1365
1500
|
const stream = await agent.stream(
|
|
1366
1501
|
{ messages: [humanMessage] },
|
|
@@ -1380,13 +1515,18 @@ function registerAgentHandlers(ipcMain) {
|
|
|
1380
1515
|
data: JSON.parse(JSON.stringify(data))
|
|
1381
1516
|
});
|
|
1382
1517
|
}
|
|
1383
|
-
|
|
1518
|
+
if (!abortController.signal.aborted) {
|
|
1519
|
+
window.webContents.send(channel, { type: "done" });
|
|
1520
|
+
}
|
|
1384
1521
|
} catch (error) {
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1522
|
+
const isAbortError = error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted") || error.message.includes("Controller is already closed"));
|
|
1523
|
+
if (!isAbortError) {
|
|
1524
|
+
console.error("[Agent] Error:", error);
|
|
1525
|
+
window.webContents.send(channel, {
|
|
1526
|
+
type: "error",
|
|
1527
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1390
1530
|
} finally {
|
|
1391
1531
|
window.removeListener("closed", onWindowClosed);
|
|
1392
1532
|
activeRuns.delete(threadId);
|
|
@@ -1424,7 +1564,7 @@ function registerAgentHandlers(ipcMain) {
|
|
|
1424
1564
|
const abortController = new AbortController();
|
|
1425
1565
|
activeRuns.set(threadId, abortController);
|
|
1426
1566
|
try {
|
|
1427
|
-
const agent = await createAgentRuntime({ workspacePath });
|
|
1567
|
+
const agent = await createAgentRuntime({ threadId, workspacePath });
|
|
1428
1568
|
const config = {
|
|
1429
1569
|
configurable: { thread_id: threadId },
|
|
1430
1570
|
signal: abortController.signal,
|
|
@@ -1443,13 +1583,18 @@ function registerAgentHandlers(ipcMain) {
|
|
|
1443
1583
|
data: JSON.parse(JSON.stringify(data))
|
|
1444
1584
|
});
|
|
1445
1585
|
}
|
|
1446
|
-
|
|
1586
|
+
if (!abortController.signal.aborted) {
|
|
1587
|
+
window.webContents.send(channel, { type: "done" });
|
|
1588
|
+
}
|
|
1447
1589
|
} catch (error) {
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1590
|
+
const isAbortError = error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted") || error.message.includes("Controller is already closed"));
|
|
1591
|
+
if (!isAbortError) {
|
|
1592
|
+
console.error("[Agent] Resume error:", error);
|
|
1593
|
+
window.webContents.send(channel, {
|
|
1594
|
+
type: "error",
|
|
1595
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1453
1598
|
} finally {
|
|
1454
1599
|
activeRuns.delete(threadId);
|
|
1455
1600
|
}
|
|
@@ -1482,7 +1627,7 @@ function registerAgentHandlers(ipcMain) {
|
|
|
1482
1627
|
const abortController = new AbortController();
|
|
1483
1628
|
activeRuns.set(threadId, abortController);
|
|
1484
1629
|
try {
|
|
1485
|
-
const agent = await createAgentRuntime({ workspacePath });
|
|
1630
|
+
const agent = await createAgentRuntime({ threadId, workspacePath });
|
|
1486
1631
|
const config = {
|
|
1487
1632
|
configurable: { thread_id: threadId },
|
|
1488
1633
|
signal: abortController.signal,
|
|
@@ -1500,16 +1645,21 @@ function registerAgentHandlers(ipcMain) {
|
|
|
1500
1645
|
data: JSON.parse(JSON.stringify(data))
|
|
1501
1646
|
});
|
|
1502
1647
|
}
|
|
1503
|
-
|
|
1648
|
+
if (!abortController.signal.aborted) {
|
|
1649
|
+
window.webContents.send(channel, { type: "done" });
|
|
1650
|
+
}
|
|
1504
1651
|
} else if (decision.type === "reject") {
|
|
1505
1652
|
window.webContents.send(channel, { type: "done" });
|
|
1506
1653
|
}
|
|
1507
1654
|
} catch (error) {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1655
|
+
const isAbortError = error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted") || error.message.includes("Controller is already closed"));
|
|
1656
|
+
if (!isAbortError) {
|
|
1657
|
+
console.error("[Agent] Interrupt error:", error);
|
|
1658
|
+
window.webContents.send(channel, {
|
|
1659
|
+
type: "error",
|
|
1660
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1513
1663
|
} finally {
|
|
1514
1664
|
activeRuns.delete(threadId);
|
|
1515
1665
|
}
|
|
@@ -1612,19 +1762,24 @@ function registerThreadHandlers(ipcMain) {
|
|
|
1612
1762
|
deleteThread(threadId);
|
|
1613
1763
|
console.log("[Threads] Deleted from metadata store");
|
|
1614
1764
|
try {
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1765
|
+
await closeCheckpointer(threadId);
|
|
1766
|
+
console.log("[Threads] Closed checkpointer");
|
|
1767
|
+
} catch (e) {
|
|
1768
|
+
console.warn("[Threads] Failed to close checkpointer:", e);
|
|
1769
|
+
}
|
|
1770
|
+
try {
|
|
1771
|
+
deleteThreadCheckpoint(threadId);
|
|
1772
|
+
console.log("[Threads] Deleted checkpoint file");
|
|
1618
1773
|
} catch (e) {
|
|
1619
|
-
console.warn("[Threads] Failed to delete
|
|
1774
|
+
console.warn("[Threads] Failed to delete checkpoint file:", e);
|
|
1620
1775
|
}
|
|
1621
1776
|
});
|
|
1622
1777
|
ipcMain.handle("threads:history", async (_event, threadId) => {
|
|
1623
1778
|
try {
|
|
1624
|
-
const
|
|
1779
|
+
const checkpointer = await getCheckpointer(threadId);
|
|
1625
1780
|
const history = [];
|
|
1626
1781
|
const config = { configurable: { thread_id: threadId } };
|
|
1627
|
-
for await (const checkpoint of
|
|
1782
|
+
for await (const checkpoint of checkpointer.list(config, { limit: 50 })) {
|
|
1628
1783
|
history.push(checkpoint);
|
|
1629
1784
|
}
|
|
1630
1785
|
return history;
|
|
@@ -1637,6 +1792,27 @@ function registerThreadHandlers(ipcMain) {
|
|
|
1637
1792
|
return generateTitle(message);
|
|
1638
1793
|
});
|
|
1639
1794
|
}
|
|
1795
|
+
const originalConsoleError = console.error;
|
|
1796
|
+
console.error = (...args) => {
|
|
1797
|
+
const message = args.map((a) => String(a)).join(" ");
|
|
1798
|
+
if (message.includes("Controller is already closed") || message.includes("ERR_INVALID_STATE") || message.includes("StreamMessagesHandler") && message.includes("aborted")) {
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
originalConsoleError.apply(console, args);
|
|
1802
|
+
};
|
|
1803
|
+
process.on("uncaughtException", (error) => {
|
|
1804
|
+
if (error.message?.includes("Controller is already closed") || error.message?.includes("aborted")) {
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
originalConsoleError("Uncaught exception:", error);
|
|
1808
|
+
});
|
|
1809
|
+
process.on("unhandledRejection", (reason) => {
|
|
1810
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
1811
|
+
if (message?.includes("Controller is already closed") || message?.includes("aborted")) {
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
originalConsoleError("Unhandled rejection:", reason);
|
|
1815
|
+
});
|
|
1640
1816
|
let mainWindow = null;
|
|
1641
1817
|
const isDev = !electron.app.isPackaged;
|
|
1642
1818
|
function createWindow() {
|
package/out/preload/index.js
CHANGED
|
@@ -133,6 +133,9 @@ const api = {
|
|
|
133
133
|
readFile: (threadId, filePath) => {
|
|
134
134
|
return electron.ipcRenderer.invoke("workspace:readFile", { threadId, filePath });
|
|
135
135
|
},
|
|
136
|
+
readBinaryFile: (threadId, filePath) => {
|
|
137
|
+
return electron.ipcRenderer.invoke("workspace:readBinaryFile", { threadId, filePath });
|
|
138
|
+
},
|
|
136
139
|
// Listen for file changes in the workspace
|
|
137
140
|
onFilesChanged: (callback) => {
|
|
138
141
|
const handler = (_, data) => {
|