@uniqueli/openwork 0.2.1 → 0.2.2

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/out/main/index.js CHANGED
@@ -107,7 +107,9 @@ const ENV_VAR_NAMES = {
107
107
  anthropic: "ANTHROPIC_API_KEY",
108
108
  openai: "OPENAI_API_KEY",
109
109
  google: "GOOGLE_API_KEY",
110
- custom: "CUSTOM_API_KEY"
110
+ ollama: ""
111
+ // Ollama doesn't require an API key
112
+ // Custom providers have their own env var pattern
111
113
  };
112
114
  function getOpenworkDir() {
113
115
  if (!fs.existsSync(OPENWORK_DIR)) {
@@ -156,7 +158,7 @@ function parseEnvFile() {
156
158
  }
157
159
  function writeEnvFile(env) {
158
160
  getOpenworkDir();
159
- const lines = Object.entries(env).filter(([_, v]) => v).map(([k, v]) => `${k}=${v}`);
161
+ const lines = Object.entries(env).filter((entry) => entry[1]).map(([k, v]) => `${k}=${v}`);
160
162
  fs.writeFileSync(getEnvFilePath(), lines.join("\n") + "\n");
161
163
  }
162
164
  function getApiKey(provider) {
@@ -271,8 +273,7 @@ const store = new Store({
271
273
  const PROVIDERS = [
272
274
  { id: "anthropic", name: "Anthropic" },
273
275
  { id: "openai", name: "OpenAI" },
274
- { id: "google", name: "Google" },
275
- { id: "custom", name: "Custom API" }
276
+ { id: "google", name: "Google" }
276
277
  ];
277
278
  const AVAILABLE_MODELS = [
278
279
  // Anthropic Claude 4.5 series (latest as of Jan 2026)
@@ -417,6 +418,14 @@ const AVAILABLE_MODELS = [
417
418
  description: "State-of-the-art reasoning and multimodal understanding",
418
419
  available: true
419
420
  },
421
+ {
422
+ id: "gemini-3-flash-preview",
423
+ name: "Gemini 3 Flash Preview",
424
+ provider: "google",
425
+ model: "gemini-3-flash-preview",
426
+ description: "Fast frontier-class model with low latency and cost",
427
+ available: true
428
+ },
420
429
  {
421
430
  id: "gemini-2.5-pro",
422
431
  name: "Gemini 2.5 Pro",
@@ -440,41 +449,27 @@ const AVAILABLE_MODELS = [
440
449
  model: "gemini-2.5-flash-lite",
441
450
  description: "Fast, low-cost, high-performance model",
442
451
  available: true
443
- },
444
- // Custom API
445
- {
446
- id: "custom",
447
- name: "Custom API",
448
- provider: "custom",
449
- model: "custom",
450
- description: "Use your own OpenAI-compatible API endpoint",
451
- available: true
452
452
  }
453
453
  ];
454
454
  function registerModelHandlers(ipcMain) {
455
455
  ipcMain.handle("models:list", async () => {
456
456
  const customConfigs = getCustomApiConfigs();
457
- const models = AVAILABLE_MODELS.filter((m) => m.id !== "custom");
457
+ const models = AVAILABLE_MODELS.map((model) => ({
458
+ ...model,
459
+ available: hasApiKey(model.provider)
460
+ }));
458
461
  for (const config of customConfigs) {
459
462
  const modelId = config.model || `custom-${config.id}`;
460
463
  models.push({
461
464
  id: modelId,
462
465
  name: config.model || config.name,
463
- // Display the model name or config name
464
466
  provider: config.id,
465
- // Use config ID as provider ID (dynamic)
466
467
  model: modelId,
467
468
  description: `${config.name} - ${config.baseUrl}`,
468
469
  available: true
469
470
  });
470
471
  }
471
- return models.map((model) => {
472
- const isCustom = customConfigs.some((c) => c.id === model.provider);
473
- return {
474
- ...model,
475
- available: isCustom ? true : hasApiKey(model.provider)
476
- };
477
- });
472
+ return models;
478
473
  });
479
474
  ipcMain.handle("models:getDefault", async () => {
480
475
  return store.get("defaultModel", "claude-sonnet-4-5-20250929");
@@ -482,12 +477,9 @@ function registerModelHandlers(ipcMain) {
482
477
  ipcMain.handle("models:setDefault", async (_event, modelId) => {
483
478
  store.set("defaultModel", modelId);
484
479
  });
485
- ipcMain.handle(
486
- "models:setApiKey",
487
- async (_event, { provider, apiKey }) => {
488
- setApiKey(provider, apiKey);
489
- }
490
- );
480
+ ipcMain.handle("models:setApiKey", async (_event, { provider, apiKey }) => {
481
+ setApiKey(provider, apiKey);
482
+ });
491
483
  ipcMain.handle("models:getApiKey", async (_event, provider) => {
492
484
  return getApiKey(provider) ?? null;
493
485
  });
@@ -495,17 +487,15 @@ function registerModelHandlers(ipcMain) {
495
487
  deleteApiKey(provider);
496
488
  });
497
489
  ipcMain.handle("models:listProviders", async () => {
498
- const standardProviders = PROVIDERS.filter((p) => p.id !== "custom").map((provider) => ({
490
+ const standardProviders = PROVIDERS.map((provider) => ({
499
491
  ...provider,
500
492
  hasApiKey: hasApiKey(provider.id)
501
493
  }));
502
494
  const customConfigs = getCustomApiConfigs();
503
495
  const customProviders = customConfigs.map((config) => ({
504
496
  id: config.id,
505
- // Dynamic provider ID
506
497
  name: config.name,
507
498
  hasApiKey: true
508
- // Custom configs always have their API key
509
499
  }));
510
500
  return [...standardProviders, ...customProviders];
511
501
  });
@@ -1367,9 +1357,10 @@ function getModelInstance(modelId) {
1367
1357
  configuration: {
1368
1358
  baseURL: matchingConfig.baseUrl,
1369
1359
  defaultHeaders: {
1370
- "Authorization": `Bearer ${cleanApiKey}`
1360
+ Authorization: `Bearer ${cleanApiKey}`
1371
1361
  }
1372
1362
  },
1363
+ temperature: 0.3,
1373
1364
  timeout: 6e4,
1374
1365
  maxRetries: 2
1375
1366
  });
@@ -1388,7 +1379,8 @@ function getModelInstance(modelId) {
1388
1379
  }
1389
1380
  return new anthropic.ChatAnthropic({
1390
1381
  model,
1391
- anthropicApiKey: apiKey
1382
+ anthropicApiKey: apiKey,
1383
+ temperature: 0.3
1392
1384
  });
1393
1385
  } else if (model.startsWith("gpt") || model.startsWith("o1") || model.startsWith("o3") || model.startsWith("o4")) {
1394
1386
  const apiKey = getApiKey("openai");
@@ -1398,7 +1390,8 @@ function getModelInstance(modelId) {
1398
1390
  }
1399
1391
  return new openai.ChatOpenAI({
1400
1392
  model,
1401
- openAIApiKey: apiKey
1393
+ openAIApiKey: apiKey,
1394
+ temperature: 0.3
1402
1395
  });
1403
1396
  } else if (model.startsWith("gemini")) {
1404
1397
  const apiKey = getApiKey("google");
@@ -1408,7 +1401,8 @@ function getModelInstance(modelId) {
1408
1401
  }
1409
1402
  return new googleGenai.ChatGoogleGenerativeAI({
1410
1403
  model,
1411
- apiKey
1404
+ apiKey,
1405
+ temperature: 0.3
1412
1406
  });
1413
1407
  }
1414
1408
  return model;
@@ -1630,100 +1624,32 @@ const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
1630
1624
  const activeRuns = /* @__PURE__ */ new Map();
1631
1625
  function registerAgentHandlers(ipcMain) {
1632
1626
  console.log("[Agent] Registering agent handlers...");
1633
- ipcMain.on(
1634
- "agent:invoke",
1635
- async (event, { threadId, message }) => {
1636
- const channel = `agent:stream:${threadId}`;
1637
- const window = electron.BrowserWindow.fromWebContents(event.sender);
1638
- console.log("[Agent] Received invoke request:", {
1639
- threadId,
1640
- message: message.substring(0, 50)
1641
- });
1642
- if (!window) {
1643
- console.error("[Agent] No window found");
1644
- return;
1645
- }
1646
- const existingController = activeRuns.get(threadId);
1647
- if (existingController) {
1648
- console.log("[Agent] Aborting existing stream for thread:", threadId);
1649
- existingController.abort();
1650
- activeRuns.delete(threadId);
1651
- }
1652
- const abortController = new AbortController();
1653
- activeRuns.set(threadId, abortController);
1654
- const onWindowClosed = () => {
1655
- console.log("[Agent] Window closed, aborting stream for thread:", threadId);
1656
- abortController.abort();
1657
- };
1658
- window.once("closed", onWindowClosed);
1659
- try {
1660
- const thread = getThread(threadId);
1661
- const metadata = thread?.metadata ? JSON.parse(thread.metadata) : {};
1662
- const workspacePath = metadata.workspacePath;
1663
- const currentModel = metadata.currentModel;
1664
- if (!workspacePath) {
1665
- window.webContents.send(channel, {
1666
- type: "error",
1667
- error: "WORKSPACE_REQUIRED",
1668
- message: "Please select a workspace folder before sending messages."
1669
- });
1670
- return;
1671
- }
1672
- const agent = await createAgentRuntime({
1673
- threadId,
1674
- workspacePath,
1675
- modelId: currentModel
1676
- });
1677
- const humanMessage = new messages.HumanMessage(message);
1678
- const stream = await agent.stream(
1679
- { messages: [humanMessage] },
1680
- {
1681
- configurable: { thread_id: threadId },
1682
- signal: abortController.signal,
1683
- streamMode: ["messages", "values"],
1684
- recursionLimit: 1e3
1685
- }
1686
- );
1687
- for await (const chunk of stream) {
1688
- if (abortController.signal.aborted) break;
1689
- const [mode, data] = chunk;
1690
- window.webContents.send(channel, {
1691
- type: "stream",
1692
- mode,
1693
- data: JSON.parse(JSON.stringify(data))
1694
- });
1695
- }
1696
- if (!abortController.signal.aborted) {
1697
- window.webContents.send(channel, { type: "done" });
1698
- }
1699
- } catch (error) {
1700
- const isAbortError = error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted") || error.message.includes("Controller is already closed"));
1701
- if (!isAbortError) {
1702
- console.error("[Agent] Error:", error);
1703
- window.webContents.send(channel, {
1704
- type: "error",
1705
- error: error instanceof Error ? error.message : "Unknown error"
1706
- });
1707
- }
1708
- } finally {
1709
- window.removeListener("closed", onWindowClosed);
1710
- activeRuns.delete(threadId);
1711
- }
1712
- }
1713
- );
1714
- ipcMain.on(
1715
- "agent:resume",
1716
- async (event, {
1627
+ ipcMain.on("agent:invoke", async (event, { threadId, message, modelId }) => {
1628
+ const channel = `agent:stream:${threadId}`;
1629
+ const window = electron.BrowserWindow.fromWebContents(event.sender);
1630
+ console.log("[Agent] Received invoke request:", {
1717
1631
  threadId,
1718
- command
1719
- }) => {
1720
- const channel = `agent:stream:${threadId}`;
1721
- const window = electron.BrowserWindow.fromWebContents(event.sender);
1722
- console.log("[Agent] Received resume request:", { threadId, command });
1723
- if (!window) {
1724
- console.error("[Agent] No window found for resume");
1725
- return;
1726
- }
1632
+ message: message.substring(0, 50),
1633
+ modelId
1634
+ });
1635
+ if (!window) {
1636
+ console.error("[Agent] No window found");
1637
+ return;
1638
+ }
1639
+ const existingController = activeRuns.get(threadId);
1640
+ if (existingController) {
1641
+ console.log("[Agent] Aborting existing stream for thread:", threadId);
1642
+ existingController.abort();
1643
+ activeRuns.delete(threadId);
1644
+ }
1645
+ const abortController = new AbortController();
1646
+ activeRuns.set(threadId, abortController);
1647
+ const onWindowClosed = () => {
1648
+ console.log("[Agent] Window closed, aborting stream for thread:", threadId);
1649
+ abortController.abort();
1650
+ };
1651
+ window.once("closed", onWindowClosed);
1652
+ try {
1727
1653
  const thread = getThread(threadId);
1728
1654
  const metadata = thread?.metadata ? JSON.parse(thread.metadata) : {};
1729
1655
  const workspacePath = metadata.workspacePath;
@@ -1731,32 +1657,157 @@ function registerAgentHandlers(ipcMain) {
1731
1657
  if (!workspacePath) {
1732
1658
  window.webContents.send(channel, {
1733
1659
  type: "error",
1734
- error: "Workspace path is required"
1660
+ error: "WORKSPACE_REQUIRED",
1661
+ message: "Please select a workspace folder before sending messages."
1735
1662
  });
1736
1663
  return;
1737
1664
  }
1738
- const existingController = activeRuns.get(threadId);
1739
- if (existingController) {
1740
- existingController.abort();
1741
- activeRuns.delete(threadId);
1742
- }
1743
- const abortController = new AbortController();
1744
- activeRuns.set(threadId, abortController);
1745
- try {
1746
- const agent = await createAgentRuntime({
1747
- threadId,
1748
- workspacePath,
1749
- modelId: currentModel
1750
- });
1751
- const config = {
1665
+ const agent = await createAgentRuntime({
1666
+ threadId,
1667
+ workspacePath,
1668
+ modelId: currentModel || modelId
1669
+ });
1670
+ const humanMessage = new messages.HumanMessage(message);
1671
+ const stream = await agent.stream(
1672
+ { messages: [humanMessage] },
1673
+ {
1752
1674
  configurable: { thread_id: threadId },
1753
1675
  signal: abortController.signal,
1754
1676
  streamMode: ["messages", "values"],
1755
1677
  recursionLimit: 1e3
1756
- };
1757
- const decisionType = command?.resume?.decision || "approve";
1758
- const resumeValue = { decisions: [{ type: decisionType }] };
1759
- const stream = await agent.stream(new langgraph.Command({ resume: resumeValue }), config);
1678
+ }
1679
+ );
1680
+ for await (const chunk of stream) {
1681
+ if (abortController.signal.aborted) break;
1682
+ const [mode, data] = chunk;
1683
+ window.webContents.send(channel, {
1684
+ type: "stream",
1685
+ mode,
1686
+ data: JSON.parse(JSON.stringify(data))
1687
+ });
1688
+ }
1689
+ if (!abortController.signal.aborted) {
1690
+ window.webContents.send(channel, { type: "done" });
1691
+ }
1692
+ } catch (error) {
1693
+ const isAbortError = error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted") || error.message.includes("Controller is already closed"));
1694
+ if (!isAbortError) {
1695
+ console.error("[Agent] Error:", error);
1696
+ window.webContents.send(channel, {
1697
+ type: "error",
1698
+ error: error instanceof Error ? error.message : "Unknown error"
1699
+ });
1700
+ }
1701
+ } finally {
1702
+ window.removeListener("closed", onWindowClosed);
1703
+ activeRuns.delete(threadId);
1704
+ }
1705
+ });
1706
+ ipcMain.on("agent:resume", async (event, { threadId, command, modelId }) => {
1707
+ const channel = `agent:stream:${threadId}`;
1708
+ const window = electron.BrowserWindow.fromWebContents(event.sender);
1709
+ console.log("[Agent] Received resume request:", { threadId, command, modelId });
1710
+ if (!window) {
1711
+ console.error("[Agent] No window found for resume");
1712
+ return;
1713
+ }
1714
+ const thread = getThread(threadId);
1715
+ const metadata = thread?.metadata ? JSON.parse(thread.metadata) : {};
1716
+ const workspacePath = metadata.workspacePath;
1717
+ const currentModel = metadata.currentModel;
1718
+ if (!workspacePath) {
1719
+ window.webContents.send(channel, {
1720
+ type: "error",
1721
+ error: "Workspace path is required"
1722
+ });
1723
+ return;
1724
+ }
1725
+ const existingController = activeRuns.get(threadId);
1726
+ if (existingController) {
1727
+ existingController.abort();
1728
+ activeRuns.delete(threadId);
1729
+ }
1730
+ const abortController = new AbortController();
1731
+ activeRuns.set(threadId, abortController);
1732
+ try {
1733
+ const agent = await createAgentRuntime({
1734
+ threadId,
1735
+ workspacePath,
1736
+ modelId: currentModel || modelId
1737
+ });
1738
+ const config = {
1739
+ configurable: { thread_id: threadId },
1740
+ signal: abortController.signal,
1741
+ streamMode: ["messages", "values"],
1742
+ recursionLimit: 1e3
1743
+ };
1744
+ const decisionType = command?.resume?.decision || "approve";
1745
+ const resumeValue = { decisions: [{ type: decisionType }] };
1746
+ const stream = await agent.stream(new langgraph.Command({ resume: resumeValue }), config);
1747
+ for await (const chunk of stream) {
1748
+ if (abortController.signal.aborted) break;
1749
+ const [mode, data] = chunk;
1750
+ window.webContents.send(channel, {
1751
+ type: "stream",
1752
+ mode,
1753
+ data: JSON.parse(JSON.stringify(data))
1754
+ });
1755
+ }
1756
+ if (!abortController.signal.aborted) {
1757
+ window.webContents.send(channel, { type: "done" });
1758
+ }
1759
+ } catch (error) {
1760
+ const isAbortError = error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted") || error.message.includes("Controller is already closed"));
1761
+ if (!isAbortError) {
1762
+ console.error("[Agent] Resume error:", error);
1763
+ window.webContents.send(channel, {
1764
+ type: "error",
1765
+ error: error instanceof Error ? error.message : "Unknown error"
1766
+ });
1767
+ }
1768
+ } finally {
1769
+ activeRuns.delete(threadId);
1770
+ }
1771
+ });
1772
+ ipcMain.on("agent:interrupt", async (event, { threadId, decision }) => {
1773
+ const channel = `agent:stream:${threadId}`;
1774
+ const window = electron.BrowserWindow.fromWebContents(event.sender);
1775
+ if (!window) {
1776
+ console.error("[Agent] No window found for interrupt response");
1777
+ return;
1778
+ }
1779
+ const thread = getThread(threadId);
1780
+ const metadata = thread?.metadata ? JSON.parse(thread.metadata) : {};
1781
+ const workspacePath = metadata.workspacePath;
1782
+ const currentModel = metadata.currentModel;
1783
+ if (!workspacePath) {
1784
+ window.webContents.send(channel, {
1785
+ type: "error",
1786
+ error: "Workspace path is required"
1787
+ });
1788
+ return;
1789
+ }
1790
+ const existingController = activeRuns.get(threadId);
1791
+ if (existingController) {
1792
+ existingController.abort();
1793
+ activeRuns.delete(threadId);
1794
+ }
1795
+ const abortController = new AbortController();
1796
+ activeRuns.set(threadId, abortController);
1797
+ try {
1798
+ const agent = await createAgentRuntime({
1799
+ threadId,
1800
+ workspacePath,
1801
+ modelId: currentModel
1802
+ });
1803
+ const config = {
1804
+ configurable: { thread_id: threadId },
1805
+ signal: abortController.signal,
1806
+ streamMode: ["messages", "values"],
1807
+ recursionLimit: 1e3
1808
+ };
1809
+ if (decision.type === "approve") {
1810
+ const stream = await agent.stream(null, config);
1760
1811
  for await (const chunk of stream) {
1761
1812
  if (abortController.signal.aborted) break;
1762
1813
  const [mode, data] = chunk;
@@ -1769,90 +1820,22 @@ function registerAgentHandlers(ipcMain) {
1769
1820
  if (!abortController.signal.aborted) {
1770
1821
  window.webContents.send(channel, { type: "done" });
1771
1822
  }
1772
- } catch (error) {
1773
- const isAbortError = error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted") || error.message.includes("Controller is already closed"));
1774
- if (!isAbortError) {
1775
- console.error("[Agent] Resume error:", error);
1776
- window.webContents.send(channel, {
1777
- type: "error",
1778
- error: error instanceof Error ? error.message : "Unknown error"
1779
- });
1780
- }
1781
- } finally {
1782
- activeRuns.delete(threadId);
1783
- }
1784
- }
1785
- );
1786
- ipcMain.on(
1787
- "agent:interrupt",
1788
- async (event, { threadId, decision }) => {
1789
- const channel = `agent:stream:${threadId}`;
1790
- const window = electron.BrowserWindow.fromWebContents(event.sender);
1791
- if (!window) {
1792
- console.error("[Agent] No window found for interrupt response");
1793
- return;
1823
+ } else if (decision.type === "reject") {
1824
+ window.webContents.send(channel, { type: "done" });
1794
1825
  }
1795
- const thread = getThread(threadId);
1796
- const metadata = thread?.metadata ? JSON.parse(thread.metadata) : {};
1797
- const workspacePath = metadata.workspacePath;
1798
- const currentModel = metadata.currentModel;
1799
- if (!workspacePath) {
1826
+ } catch (error) {
1827
+ const isAbortError = error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted") || error.message.includes("Controller is already closed"));
1828
+ if (!isAbortError) {
1829
+ console.error("[Agent] Interrupt error:", error);
1800
1830
  window.webContents.send(channel, {
1801
1831
  type: "error",
1802
- error: "Workspace path is required"
1832
+ error: error instanceof Error ? error.message : "Unknown error"
1803
1833
  });
1804
- return;
1805
- }
1806
- const existingController = activeRuns.get(threadId);
1807
- if (existingController) {
1808
- existingController.abort();
1809
- activeRuns.delete(threadId);
1810
- }
1811
- const abortController = new AbortController();
1812
- activeRuns.set(threadId, abortController);
1813
- try {
1814
- const agent = await createAgentRuntime({
1815
- threadId,
1816
- workspacePath,
1817
- modelId: currentModel
1818
- });
1819
- const config = {
1820
- configurable: { thread_id: threadId },
1821
- signal: abortController.signal,
1822
- streamMode: ["messages", "values"],
1823
- recursionLimit: 1e3
1824
- };
1825
- if (decision.type === "approve") {
1826
- const stream = await agent.stream(null, config);
1827
- for await (const chunk of stream) {
1828
- if (abortController.signal.aborted) break;
1829
- const [mode, data] = chunk;
1830
- window.webContents.send(channel, {
1831
- type: "stream",
1832
- mode,
1833
- data: JSON.parse(JSON.stringify(data))
1834
- });
1835
- }
1836
- if (!abortController.signal.aborted) {
1837
- window.webContents.send(channel, { type: "done" });
1838
- }
1839
- } else if (decision.type === "reject") {
1840
- window.webContents.send(channel, { type: "done" });
1841
- }
1842
- } catch (error) {
1843
- const isAbortError = error instanceof Error && (error.name === "AbortError" || error.message.includes("aborted") || error.message.includes("Controller is already closed"));
1844
- if (!isAbortError) {
1845
- console.error("[Agent] Interrupt error:", error);
1846
- window.webContents.send(channel, {
1847
- type: "error",
1848
- error: error instanceof Error ? error.message : "Unknown error"
1849
- });
1850
- }
1851
- } finally {
1852
- activeRuns.delete(threadId);
1853
1834
  }
1835
+ } finally {
1836
+ activeRuns.delete(threadId);
1854
1837
  }
1855
- );
1838
+ });
1856
1839
  ipcMain.handle("agent:cancel", async (_event, { threadId }) => {
1857
1840
  const controller = activeRuns.get(threadId);
1858
1841
  if (controller) {
@@ -1923,28 +1906,25 @@ function registerThreadHandlers(ipcMain) {
1923
1906
  title
1924
1907
  };
1925
1908
  });
1926
- ipcMain.handle(
1927
- "threads:update",
1928
- async (_event, { threadId, updates }) => {
1929
- const updateData = {};
1930
- if (updates.title !== void 0) updateData.title = updates.title;
1931
- if (updates.status !== void 0) updateData.status = updates.status;
1932
- if (updates.metadata !== void 0)
1933
- updateData.metadata = JSON.stringify(updates.metadata);
1934
- if (updates.thread_values !== void 0) updateData.thread_values = JSON.stringify(updates.thread_values);
1935
- const row = updateThread(threadId, updateData);
1936
- if (!row) throw new Error("Thread not found");
1937
- return {
1938
- thread_id: row.thread_id,
1939
- created_at: new Date(row.created_at),
1940
- updated_at: new Date(row.updated_at),
1941
- metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1942
- status: row.status,
1943
- thread_values: row.thread_values ? JSON.parse(row.thread_values) : void 0,
1944
- title: row.title
1945
- };
1946
- }
1947
- );
1909
+ ipcMain.handle("threads:update", async (_event, { threadId, updates }) => {
1910
+ const updateData = {};
1911
+ if (updates.title !== void 0) updateData.title = updates.title;
1912
+ if (updates.status !== void 0) updateData.status = updates.status;
1913
+ if (updates.metadata !== void 0) updateData.metadata = JSON.stringify(updates.metadata);
1914
+ if (updates.thread_values !== void 0)
1915
+ updateData.thread_values = JSON.stringify(updates.thread_values);
1916
+ const row = updateThread(threadId, updateData);
1917
+ if (!row) throw new Error("Thread not found");
1918
+ return {
1919
+ thread_id: row.thread_id,
1920
+ created_at: new Date(row.created_at),
1921
+ updated_at: new Date(row.updated_at),
1922
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1923
+ status: row.status,
1924
+ thread_values: row.thread_values ? JSON.parse(row.thread_values) : void 0,
1925
+ title: row.title
1926
+ };
1927
+ });
1948
1928
  ipcMain.handle("threads:delete", async (_event, threadId) => {
1949
1929
  console.log("[Threads] Deleting thread:", threadId);
1950
1930
  deleteThread(threadId);
@@ -1980,27 +1960,6 @@ function registerThreadHandlers(ipcMain) {
1980
1960
  return generateTitle(message);
1981
1961
  });
1982
1962
  }
1983
- const originalConsoleError = console.error;
1984
- console.error = (...args) => {
1985
- const message = args.map((a) => String(a)).join(" ");
1986
- if (message.includes("Controller is already closed") || message.includes("ERR_INVALID_STATE") || message.includes("StreamMessagesHandler") && message.includes("aborted")) {
1987
- return;
1988
- }
1989
- originalConsoleError.apply(console, args);
1990
- };
1991
- process.on("uncaughtException", (error) => {
1992
- if (error.message?.includes("Controller is already closed") || error.message?.includes("aborted")) {
1993
- return;
1994
- }
1995
- originalConsoleError("Uncaught exception:", error);
1996
- });
1997
- process.on("unhandledRejection", (reason) => {
1998
- const message = reason instanceof Error ? reason.message : String(reason);
1999
- if (message?.includes("Controller is already closed") || message?.includes("aborted")) {
2000
- return;
2001
- }
2002
- originalConsoleError("Unhandled rejection:", reason);
2003
- });
2004
1963
  let mainWindow = null;
2005
1964
  const isDev = !electron.app.isPackaged;
2006
1965
  function createWindow() {