openwork 0.1.1-rc.3 → 0.1.1-rc.4

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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # openwork
2
2
 
3
+ [![CI](https://github.com/langchain-ai/openwork/actions/workflows/ci.yml/badge.svg)](https://github.com/langchain-ai/openwork/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/openwork.svg)](https://www.npmjs.com/package/openwork)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
3
7
  A tactical agent interface for [deepagentsjs](https://github.com/langchain-ai/deepagentsjs) - an opinionated harness for building deep agents with filesystem capabilities, planning, and subagent delegation.
4
8
 
5
9
  ![openwork screenshot](docs/screenshot.png)
package/out/main/index.js CHANGED
@@ -6,6 +6,7 @@ const deepagents = require("deepagents");
6
6
  const Store = require("electron-store");
7
7
  const fs$1 = require("fs/promises");
8
8
  const fs = require("fs");
9
+ const os = require("os");
9
10
  const anthropic = require("@langchain/anthropic");
10
11
  const openai = require("@langchain/openai");
11
12
  const initSqlJs = require("sql.js");
@@ -96,23 +97,83 @@ function notifyRenderer(threadId, workspacePath) {
96
97
  });
97
98
  }
98
99
  }
100
+ const OPENWORK_DIR = path.join(os.homedir(), ".openwork");
101
+ const ENV_FILE = path.join(OPENWORK_DIR, ".env");
102
+ const ENV_VAR_NAMES = {
103
+ anthropic: "ANTHROPIC_API_KEY",
104
+ openai: "OPENAI_API_KEY"
105
+ };
106
+ function getOpenworkDir() {
107
+ if (!fs.existsSync(OPENWORK_DIR)) {
108
+ fs.mkdirSync(OPENWORK_DIR, { recursive: true });
109
+ }
110
+ return OPENWORK_DIR;
111
+ }
112
+ function getDbPath() {
113
+ return path.join(getOpenworkDir(), "openwork.sqlite");
114
+ }
115
+ function getCheckpointDbPath() {
116
+ return path.join(getOpenworkDir(), "langgraph.sqlite");
117
+ }
118
+ function getEnvFilePath() {
119
+ return ENV_FILE;
120
+ }
121
+ function parseEnvFile() {
122
+ const envPath = getEnvFilePath();
123
+ if (!fs.existsSync(envPath)) return {};
124
+ const content = fs.readFileSync(envPath, "utf-8");
125
+ const result = {};
126
+ for (const line of content.split("\n")) {
127
+ const trimmed = line.trim();
128
+ if (!trimmed || trimmed.startsWith("#")) continue;
129
+ const eqIndex = trimmed.indexOf("=");
130
+ if (eqIndex > 0) {
131
+ const key = trimmed.slice(0, eqIndex).trim();
132
+ const value = trimmed.slice(eqIndex + 1).trim();
133
+ result[key] = value;
134
+ }
135
+ }
136
+ return result;
137
+ }
138
+ function writeEnvFile(env) {
139
+ getOpenworkDir();
140
+ const lines = Object.entries(env).filter(([_, v]) => v).map(([k, v]) => `${k}=${v}`);
141
+ fs.writeFileSync(getEnvFilePath(), lines.join("\n") + "\n");
142
+ }
143
+ function getApiKey(provider) {
144
+ const envVarName = ENV_VAR_NAMES[provider];
145
+ if (!envVarName) return void 0;
146
+ const env = parseEnvFile();
147
+ if (env[envVarName]) return env[envVarName];
148
+ return process.env[envVarName];
149
+ }
150
+ function setApiKey(provider, apiKey) {
151
+ const envVarName = ENV_VAR_NAMES[provider];
152
+ if (!envVarName) return;
153
+ const env = parseEnvFile();
154
+ env[envVarName] = apiKey;
155
+ writeEnvFile(env);
156
+ process.env[envVarName] = apiKey;
157
+ }
158
+ function deleteApiKey(provider) {
159
+ const envVarName = ENV_VAR_NAMES[provider];
160
+ if (!envVarName) return;
161
+ const env = parseEnvFile();
162
+ delete env[envVarName];
163
+ writeEnvFile(env);
164
+ delete process.env[envVarName];
165
+ }
166
+ function hasApiKey(provider) {
167
+ return !!getApiKey(provider);
168
+ }
99
169
  const store = new Store({
100
- name: "openwork-settings",
101
- encryptionKey: "openwork-encryption-key-v1"
102
- // In production, derive from machine ID
170
+ name: "settings",
171
+ cwd: getOpenworkDir()
103
172
  });
104
173
  const PROVIDERS = [
105
174
  { id: "anthropic", name: "Anthropic" },
106
- { id: "openai", name: "OpenAI" },
107
- { id: "google", name: "Google" }
175
+ { id: "openai", name: "OpenAI" }
108
176
  ];
109
- const ENV_VAR_MAP = {
110
- anthropic: "ANTHROPIC_API_KEY",
111
- openai: "OPENAI_API_KEY",
112
- google: "GOOGLE_API_KEY",
113
- ollama: ""
114
- // Ollama doesn't need API key
115
- };
116
177
  const AVAILABLE_MODELS = [
117
178
  // Anthropic Claude 4.5 series (latest as of Jan 2026)
118
179
  {
@@ -120,7 +181,7 @@ const AVAILABLE_MODELS = [
120
181
  name: "Claude Opus 4.5",
121
182
  provider: "anthropic",
122
183
  model: "claude-opus-4-5-20251101",
123
- description: "Most capable, excels at complex reasoning and coding",
184
+ description: "Premium model with maximum intelligence",
124
185
  available: true
125
186
  },
126
187
  {
@@ -128,7 +189,7 @@ const AVAILABLE_MODELS = [
128
189
  name: "Claude Sonnet 4.5",
129
190
  provider: "anthropic",
130
191
  model: "claude-sonnet-4-5-20250929",
131
- description: "Balanced performance and efficiency, great for agents",
192
+ description: "Best balance of intelligence, speed, and cost for agents",
132
193
  available: true
133
194
  },
134
195
  {
@@ -136,7 +197,24 @@ const AVAILABLE_MODELS = [
136
197
  name: "Claude Haiku 4.5",
137
198
  provider: "anthropic",
138
199
  model: "claude-haiku-4-5-20251001",
139
- description: "Fast and cost-effective for real-time tasks",
200
+ description: "Fastest model with near-frontier intelligence",
201
+ available: true
202
+ },
203
+ // Anthropic Claude legacy models
204
+ {
205
+ id: "claude-opus-4-1-20250805",
206
+ name: "Claude Opus 4.1",
207
+ provider: "anthropic",
208
+ model: "claude-opus-4-1-20250805",
209
+ description: "Previous generation premium model with extended thinking",
210
+ available: true
211
+ },
212
+ {
213
+ id: "claude-sonnet-4-20250514",
214
+ name: "Claude Sonnet 4",
215
+ provider: "anthropic",
216
+ model: "claude-sonnet-4-20250514",
217
+ description: "Fast and capable previous generation model",
140
218
  available: true
141
219
  },
142
220
  // OpenAI GPT-5 series (latest as of Jan 2026)
@@ -229,15 +307,6 @@ const AVAILABLE_MODELS = [
229
307
  model: "gpt-4o-mini",
230
308
  description: "Cost-efficient variant with faster response times",
231
309
  available: true
232
- },
233
- // Google Gemini 3 series (latest as of Jan 2026)
234
- {
235
- id: "gemini-3-flash-preview",
236
- name: "Gemini 3 Flash",
237
- provider: "google",
238
- model: "gemini-3-flash-preview",
239
- description: "Fast, 3x faster than 2.5 Pro with 1M context",
240
- available: true
241
310
  }
242
311
  ];
243
312
  function registerModelHandlers(ipcMain) {
@@ -256,22 +325,14 @@ function registerModelHandlers(ipcMain) {
256
325
  ipcMain.handle(
257
326
  "models:setApiKey",
258
327
  async (_event, { provider, apiKey }) => {
259
- store.set(`apiKeys.${provider}`, apiKey);
260
- const envVar = ENV_VAR_MAP[provider];
261
- if (envVar) {
262
- process.env[envVar] = apiKey;
263
- }
328
+ setApiKey(provider, apiKey);
264
329
  }
265
330
  );
266
331
  ipcMain.handle("models:getApiKey", async (_event, provider) => {
267
- return store.get(`apiKeys.${provider}`, null);
332
+ return getApiKey(provider) ?? null;
268
333
  });
269
334
  ipcMain.handle("models:deleteApiKey", async (_event, provider) => {
270
- store.delete(`apiKeys.${provider}`);
271
- const envVar = ENV_VAR_MAP[provider];
272
- if (envVar) {
273
- delete process.env[envVar];
274
- }
335
+ deleteApiKey(provider);
275
336
  });
276
337
  ipcMain.handle("models:listProviders", async () => {
277
338
  return PROVIDERS.map((provider) => ({
@@ -432,18 +493,6 @@ function registerModelHandlers(ipcMain) {
432
493
  }
433
494
  );
434
495
  }
435
- function hasApiKey(provider) {
436
- const storedKey = store.get(`apiKeys.${provider}`);
437
- if (storedKey) return true;
438
- const envVar = ENV_VAR_MAP[provider];
439
- return envVar ? !!process.env[envVar] : false;
440
- }
441
- function getApiKey(provider) {
442
- const storedKey = store.get(`apiKeys.${provider}`);
443
- if (storedKey) return storedKey;
444
- const envVar = ENV_VAR_MAP[provider];
445
- return envVar ? process.env[envVar] : void 0;
446
- }
447
496
  function getDefaultModel() {
448
497
  return store.get("defaultModel", "claude-sonnet-4-5-20250929");
449
498
  }
@@ -877,8 +926,7 @@ function getSystemPrompt(workspacePath) {
877
926
  let checkpointer = null;
878
927
  async function getCheckpointer() {
879
928
  if (!checkpointer) {
880
- const dbPath = path.join(electron.app.getPath("userData"), "langgraph.sqlite");
881
- checkpointer = new SqlJsSaver(dbPath);
929
+ checkpointer = new SqlJsSaver(getCheckpointDbPath());
882
930
  await checkpointer.initialize();
883
931
  }
884
932
  return checkpointer;
@@ -906,8 +954,6 @@ function getModelInstance(modelId) {
906
954
  model,
907
955
  openAIApiKey: apiKey
908
956
  });
909
- } else if (model.startsWith("gemini")) {
910
- throw new Error("Gemini support coming soon");
911
957
  }
912
958
  return model;
913
959
  }
@@ -938,7 +984,6 @@ async function createAgentRuntime(options) {
938
984
  console.log("[Runtime] Deep agent created with FilesystemBackend at:", workspacePath);
939
985
  return agent;
940
986
  }
941
- const getDbPath = () => path.join(electron.app.getPath("userData"), "openwork.sqlite");
942
987
  let db = null;
943
988
  let saveTimer = null;
944
989
  let dirty = false;
@@ -73468,20 +73468,15 @@ function AnthropicIcon({ className }) {
73468
73468
  function OpenAIIcon({ className }) {
73469
73469
  return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855l-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023l-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365l2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z" }) });
73470
73470
  }
73471
- function GoogleIcon({ className }) {
73472
- return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" }) });
73473
- }
73474
73471
  const PROVIDER_ICONS = {
73475
73472
  anthropic: AnthropicIcon,
73476
73473
  openai: OpenAIIcon,
73477
- google: GoogleIcon,
73478
73474
  ollama: () => null
73479
73475
  // No icon for ollama yet
73480
73476
  };
73481
73477
  const FALLBACK_PROVIDERS = [
73482
73478
  { id: "anthropic", name: "Anthropic", hasApiKey: false },
73483
- { id: "openai", name: "OpenAI", hasApiKey: false },
73484
- { id: "google", name: "Google AI", hasApiKey: false }
73479
+ { id: "openai", name: "OpenAI", hasApiKey: false }
73485
73480
  ];
73486
73481
  function ModelSwitcher() {
73487
73482
  const [open, setOpen] = reactExports.useState(false);
@@ -73599,29 +73594,31 @@ function ModelSwitcher() {
73599
73594
  )
73600
73595
  ] })
73601
73596
  ) : (
73602
- // Show models list
73603
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-0.5", children: [
73604
- filteredModels.map((model) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
73605
- "button",
73606
- {
73607
- onClick: () => handleModelSelect(model.id),
73608
- className: cn(
73609
- "w-full flex items-center gap-1.5 px-2 py-1 rounded-sm text-xs transition-colors text-left font-mono",
73610
- currentModel === model.id ? "bg-muted text-foreground" : "text-muted-foreground hover:text-foreground hover:bg-muted/50"
73611
- ),
73612
- children: [
73613
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate", children: model.id }),
73614
- currentModel === model.id && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5 shrink-0 text-foreground" })
73615
- ]
73616
- },
73617
- model.id
73618
- )),
73619
- filteredModels.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground px-2 py-4", children: "No models available" }),
73597
+ // Show models list with scrollable area
73598
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-[200px]", children: [
73599
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "overflow-y-auto flex-1 space-y-0.5", children: [
73600
+ filteredModels.map((model) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
73601
+ "button",
73602
+ {
73603
+ onClick: () => handleModelSelect(model.id),
73604
+ className: cn(
73605
+ "w-full flex items-center gap-1.5 px-2 py-1 rounded-sm text-xs transition-colors text-left font-mono",
73606
+ currentModel === model.id ? "bg-muted text-foreground" : "text-muted-foreground hover:text-foreground hover:bg-muted/50"
73607
+ ),
73608
+ children: [
73609
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate", children: model.id }),
73610
+ currentModel === model.id && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5 shrink-0 text-foreground" })
73611
+ ]
73612
+ },
73613
+ model.id
73614
+ )),
73615
+ filteredModels.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground px-2 py-4", children: "No models available" })
73616
+ ] }),
73620
73617
  selectedProvider?.hasApiKey && /* @__PURE__ */ jsxRuntimeExports.jsxs(
73621
73618
  "button",
73622
73619
  {
73623
73620
  onClick: () => handleConfigureApiKey(selectedProvider),
73624
- className: "w-full flex items-center gap-2 px-2 py-1.5 rounded-sm text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors mt-2",
73621
+ className: "w-full flex items-center gap-2 px-2 py-1.5 rounded-sm text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors mt-2 border-t border-border pt-2",
73625
73622
  children: [
73626
73623
  /* @__PURE__ */ jsxRuntimeExports.jsx(Key, { className: "size-3.5" }),
73627
73624
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Edit API Key" })
@@ -74688,7 +74685,7 @@ function ChatContainer({ threadId }) {
74688
74685
  "button",
74689
74686
  {
74690
74687
  type: "button",
74691
- className: "inline-flex items-center justify-center rounded-md border border-border bg-background px-2 h-7 text-xs gap-1.5 text-amber-500 hover:bg-accent/50 disabled:opacity-50 disabled:cursor-not-allowed",
74688
+ className: "inline-flex items-center justify-center rounded-md border border-border bg-background px-2 h-7 text-xs gap-1.5 text-amber-500 hover:bg-accent/50 transition-color duration-100 disabled:opacity-50 disabled:cursor-not-allowed",
74692
74689
  onClick: handleSelectWorkspaceFromEmptyState,
74693
74690
  children: [
74694
74691
  /* @__PURE__ */ jsxRuntimeExports.jsx(Folder, { className: "size-3.5" }),
@@ -75560,7 +75557,7 @@ function App() {
75560
75557
  },
75561
75558
  children: [
75562
75559
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "app-badge-name", children: "OPENWORK" }),
75563
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "app-badge-version", children: "0.1.1-rc.3" })
75560
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "app-badge-version", children: "0.1.1-rc.4" })
75564
75561
  ]
75565
75562
  }
75566
75563
  ),
@@ -652,6 +652,10 @@
652
652
  height: 180px;
653
653
  }
654
654
 
655
+ .h-\[200px\] {
656
+ height: 200px;
657
+ }
658
+
655
659
  .h-full {
656
660
  height: 100%;
657
661
  }
@@ -951,6 +955,10 @@
951
955
  overflow-x: auto;
952
956
  }
953
957
 
958
+ .overflow-y-auto {
959
+ overflow-y: auto;
960
+ }
961
+
954
962
  .rounded {
955
963
  border-radius: .25rem;
956
964
  }
@@ -1462,6 +1470,10 @@
1462
1470
  padding-top: calc(var(--spacing) * 0);
1463
1471
  }
1464
1472
 
1473
+ .pt-2 {
1474
+ padding-top: calc(var(--spacing) * 2);
1475
+ }
1476
+
1465
1477
  .pr-2 {
1466
1478
  padding-right: calc(var(--spacing) * 2);
1467
1479
  }
@@ -1795,6 +1807,11 @@
1795
1807
  transition-duration: var(--tw-duration, var(--default-transition-duration));
1796
1808
  }
1797
1809
 
1810
+ .duration-100 {
1811
+ --tw-duration: .1s;
1812
+ transition-duration: .1s;
1813
+ }
1814
+
1798
1815
  .duration-200 {
1799
1816
  --tw-duration: .2s;
1800
1817
  transition-duration: .2s;
@@ -7,8 +7,8 @@
7
7
  http-equiv="Content-Security-Policy"
8
8
  content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
9
9
  />
10
- <script type="module" crossorigin src="./assets/index-Dvk2R-ko.js"></script>
11
- <link rel="stylesheet" crossorigin href="./assets/index-D2W2biEe.css">
10
+ <script type="module" crossorigin src="./assets/index-BttVUwrw.js"></script>
11
+ <link rel="stylesheet" crossorigin href="./assets/index-DjlJs7Yy.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openwork",
3
- "version": "0.1.1-rc.3",
3
+ "version": "0.1.1-rc.4",
4
4
  "description": "A tactical agent interface for deepagentsjs",
5
5
  "main": "./out/main/index.js",
6
6
  "files": [