cowork-os 0.4.0 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-os",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "CoWork OS - The most complete open-source AI assistant platform",
5
5
  "overrides": {
6
6
  "@whiskeysockets/baileys": {
@@ -117,6 +117,7 @@
117
117
  "discord.js": "^14.25.1",
118
118
  "docx": "^9.5.2",
119
119
  "e2b": "^2.12.1",
120
+ "electron": "^40.4.1",
120
121
  "electron-updater": "^6.3.9",
121
122
  "ethers": "^6.16.0",
122
123
  "eventsource": "^4.1.0",
@@ -165,7 +166,6 @@
165
166
  "@vitest/coverage-v8": "^4.0.18",
166
167
  "concurrently": "^9.2.1",
167
168
  "cross-env": "^10.1.0",
168
- "electron": "^40.4.1",
169
169
  "electron-builder": "^26.7.0",
170
170
  "eslint": "^9.39.2",
171
171
  "globals": "^17.3.0",
@@ -24,22 +24,34 @@ import process from "node:process";
24
24
 
25
25
  const BETTER_SQLITE3_VERSION = "12.6.2";
26
26
  const NPM_CMD = process.platform === "win32" ? "npm.cmd" : "npm";
27
- const cwdRequire = (() => {
28
- try {
29
- return createRequire(path.join(process.cwd(), "package.json"));
30
- } catch {
31
- return createRequire(import.meta.url);
32
- }
33
- })();
27
+ const cwdRequire = createRequire(path.join(process.cwd(), "package.json"));
28
+ const scriptRequire = createRequire(import.meta.url);
34
29
 
35
30
  function resolveFromCwd(specifier) {
31
+ // Try CWD first (dev/source builds), then script location (global npm install)
36
32
  try {
37
33
  return cwdRequire.resolve(specifier);
38
34
  } catch {
39
- return null;
35
+ try {
36
+ return scriptRequire.resolve(specifier);
37
+ } catch {
38
+ return null;
39
+ }
40
40
  }
41
41
  }
42
42
 
43
+ /**
44
+ * Find a file inside an npm package directory by resolving the package's
45
+ * package.json first (which is always resolvable regardless of exports maps),
46
+ * then constructing the file path directly.
47
+ */
48
+ function resolvePackageFile(packageName, filePath) {
49
+ const pkgJson = resolveFromCwd(`${packageName}/package.json`);
50
+ if (!pkgJson) return null;
51
+ const candidate = path.join(path.dirname(pkgJson), filePath);
52
+ return fs.existsSync(candidate) ? candidate : null;
53
+ }
54
+
43
55
  function getElectronBinaryPath() {
44
56
  try {
45
57
  const electronBinary = cwdRequire("electron");
@@ -254,7 +266,7 @@ function main() {
254
266
  const attempt = (attemptJobs) => {
255
267
  const env = baseEnvWithJobs(attemptJobs);
256
268
  const installRootDir = getInstallRootDir();
257
- const electronInstallScript = resolveFromCwd("electron/install.js");
269
+ const electronInstallScript = resolvePackageFile("electron", "install.js");
258
270
  const electronBinary = getElectronBinaryPath();
259
271
 
260
272
  if (!electronInstallScript) {
@@ -319,10 +331,12 @@ function main() {
319
331
  }
320
332
 
321
333
  // 3) Fallback: electron-rebuild.
322
- const electronRebuildEntry = resolveFromCwd("@electron/rebuild");
323
- const electronRebuildCli = electronRebuildEntry
324
- ? path.join(path.dirname(electronRebuildEntry), "cli.js")
325
- : null;
334
+ const electronRebuildCli = resolvePackageFile("@electron/rebuild", "cli.js")
335
+ || (() => {
336
+ // Fallback: try resolving the main entry and deriving cli.js
337
+ const entry = resolveFromCwd("@electron/rebuild");
338
+ return entry ? path.join(path.dirname(entry), "cli.js") : null;
339
+ })();
326
340
  if (!electronRebuildCli || !fs.existsSync(electronRebuildCli)) {
327
341
  console.log(
328
342
  "[cowork] @electron/rebuild is not installed; skipping fallback rebuild."
@@ -222,6 +222,7 @@ function createExecutorWithLLMHandler(handler: (messages: Any[]) => LLMResponse)
222
222
  kind: "none",
223
223
  },
224
224
  })),
225
+ getContextUtilization: vi.fn().mockReturnValue({ utilization: 0 }),
225
226
  getAvailableTokens: vi.fn().mockReturnValue(1_000_000),
226
227
  };
227
228
  executor.checkBudgets = vi.fn();
@@ -879,13 +880,13 @@ describe("TaskExecutor executeStep failure handling", () => {
879
880
  expect((executor as Any).conversationHistory.at(-1)?.content).toContain("This is attempt 2");
880
881
  });
881
882
 
882
- it("does not re-run recovery plan insertion for the same failing signature twice", async () => {
883
+ it("does not auto-insert recovery plan steps for repeated failure signatures", async () => {
883
884
  const executor = createExecutorWithStubs(
884
885
  [
885
886
  toolUseResponse("run_command", { command: "exit 1" }),
886
- textResponse("done"),
887
+ textResponse(""),
887
888
  toolUseResponse("run_command", { command: "exit 1" }),
888
- textResponse("done"),
889
+ textResponse(""),
889
890
  ],
890
891
  {
891
892
  run_command: { success: false, error: "cannot complete this task without a workaround" },
@@ -903,23 +904,22 @@ describe("TaskExecutor executeStep failure handling", () => {
903
904
  await (executor as Any).executeStep(failedStep);
904
905
  await (executor as Any).executeStep(failedStep);
905
906
 
906
- expect(handlePlanRevisionSpy).toHaveBeenCalledTimes(1);
907
- expect(failedStep.status).toBe("failed");
908
- expect(executor.planRevisionCount).toBe(1);
907
+ expect(handlePlanRevisionSpy).not.toHaveBeenCalled();
908
+ expect(executor.planRevisionCount).toBe(0);
909
909
  const planDescriptions = executor.plan.steps.map((step: Any) => step.description);
910
910
  expect(
911
- planDescriptions.filter((desc: string) => desc.includes("alternative toolchain")).length,
912
- ).toBe(1);
913
- expect(planDescriptions.length).toBe(4);
911
+ planDescriptions.some((desc: string) => desc.includes("alternative toolchain")),
912
+ ).toBe(false);
913
+ expect(planDescriptions.length).toBe(2);
914
914
  });
915
915
 
916
- it("adds recovery steps again when failure reason changes after a retry", async () => {
916
+ it("does not auto-insert recovery steps even when failure reason changes between retries", async () => {
917
917
  const executor = createExecutorWithStubs(
918
918
  [
919
919
  toolUseResponse("run_command", { command: "exit 1" }),
920
- textResponse("done"),
920
+ textResponse(""),
921
921
  toolUseResponse("run_command", { command: "exit 1" }),
922
- textResponse("done"),
922
+ textResponse(""),
923
923
  ],
924
924
  {},
925
925
  );
@@ -948,18 +948,18 @@ describe("TaskExecutor executeStep failure handling", () => {
948
948
  await (executor as Any).executeStep(failedStep);
949
949
  await (executor as Any).executeStep(failedStep);
950
950
 
951
- expect(handlePlanRevisionSpy).toHaveBeenCalledTimes(2);
951
+ expect(handlePlanRevisionSpy).not.toHaveBeenCalled();
952
952
  const planDescriptions = executor.plan.steps.map((step: Any) => step.description);
953
953
  expect(
954
- planDescriptions.filter((desc: string) => desc.includes("alternative toolchain")).length,
955
- ).toBe(2);
956
- expect(planDescriptions.length).toBe(6);
957
- expect(executor.planRevisionCount).toBe(2);
954
+ planDescriptions.some((desc: string) => desc.includes("alternative toolchain")),
955
+ ).toBe(false);
956
+ expect(planDescriptions.length).toBe(2);
957
+ expect(executor.planRevisionCount).toBe(0);
958
958
  });
959
959
 
960
- it("adds recovery plan steps without clearing unrelated pending steps", async () => {
960
+ it("keeps existing plan steps unchanged when recovery insertion is not triggered", async () => {
961
961
  const executor = createExecutorWithStubs(
962
- [toolUseResponse("run_command", { command: "exit 1" }), textResponse("done")],
962
+ [toolUseResponse("run_command", { command: "exit 1" }), textResponse("")],
963
963
  {
964
964
  run_command: { success: false, error: "exit code 1" },
965
965
  },
@@ -974,21 +974,18 @@ describe("TaskExecutor executeStep failure handling", () => {
974
974
 
975
975
  await (executor as Any).executeStep(failedStep);
976
976
 
977
- expect(failedStep.status).toBe("failed");
977
+ expect(failedStep.status).toBe("completed");
978
978
  const planDescriptions = executor.plan.steps.map((step: Any) => step.description);
979
- expect(planDescriptions).toContain(
980
- "Try an alternative toolchain or different input strategy for: Run baseline task",
981
- );
982
- expect(planDescriptions).toContain(
983
- "If normal tools are blocked, implement the smallest safe code/feature change needed to continue and complete the goal.",
984
- );
979
+ expect(
980
+ planDescriptions.some((desc: string) => desc.includes("alternative toolchain")),
981
+ ).toBe(false);
985
982
  expect(planDescriptions).toContain("Validate output");
986
- expect(planDescriptions.length).toBe(4);
983
+ expect(planDescriptions.length).toBe(2);
987
984
  });
988
985
 
989
- it("triggers recovery on blocked-step reasons even without explicit user request", async () => {
986
+ it("does not auto-trigger recovery planning when user did not explicitly request recovery", async () => {
990
987
  const executor = createExecutorWithStubs(
991
- [toolUseResponse("run_command", { command: "exit 1" }), textResponse("done")],
988
+ [toolUseResponse("run_command", { command: "exit 1" }), textResponse("")],
992
989
  {
993
990
  run_command: { success: false, error: "cannot complete this task without a workaround" },
994
991
  },
@@ -1006,10 +1003,10 @@ describe("TaskExecutor executeStep failure handling", () => {
1006
1003
  await (executor as Any).executeStep(failedStep);
1007
1004
 
1008
1005
  const planDescriptions = executor.plan.steps.map((step: Any) => step.description);
1009
- expect(planDescriptions).toContain(
1010
- "Try an alternative toolchain or different input strategy for: Run baseline task",
1011
- );
1012
- expect(failedStep.status).toBe("failed");
1013
- expect(executor.planRevisionCount).toBe(1);
1006
+ expect(
1007
+ planDescriptions.some((desc: string) => desc.includes("alternative toolchain")),
1008
+ ).toBe(false);
1009
+ expect(failedStep.status).toBe("completed");
1010
+ expect(executor.planRevisionCount).toBe(0);
1014
1011
  });
1015
1012
  });