baa-conductor

git clone 

commit
40b1c7f
parent
284e9ac
author
im_wower
date
2026-03-22 22:35:38 +0800 CST
fix(host-ops): normalize exec failure result shape
3 files changed,  +90, -6
M apps/conductor-daemon/src/index.test.js
+75, -1
 1@@ -218,6 +218,37 @@ function parseJsonBody(response) {
 2   return JSON.parse(response.body);
 3 }
 4 
 5+async function withMockedPlatform(platform, callback) {
 6+  const descriptor = Object.getOwnPropertyDescriptor(process, "platform");
 7+
 8+  assert.ok(descriptor);
 9+
10+  Object.defineProperty(process, "platform", {
11+    configurable: true,
12+    enumerable: descriptor.enumerable ?? true,
13+    value: platform
14+  });
15+
16+  try {
17+    return await callback();
18+  } finally {
19+    Object.defineProperty(process, "platform", descriptor);
20+  }
21+}
22+
23+function assertEmptyExecResultShape(result) {
24+  assert.deepEqual(result, {
25+    stdout: "",
26+    stderr: "",
27+    exitCode: null,
28+    signal: null,
29+    durationMs: 0,
30+    startedAt: null,
31+    finishedAt: null,
32+    timedOut: false
33+  });
34+}
35+
36 function createWebSocketMessageQueue(socket) {
37   const messages = [];
38   const waiters = [];
39@@ -938,6 +969,48 @@ test("handleConductorHttpRequest serves the migrated local business endpoints fr
40     assert.equal(execPayload.data.operation, "exec");
41     assert.equal(execPayload.data.result.stdout, "host-http-ok");
42 
43+    const invalidExecResponse = await handleConductorHttpRequest(
44+      {
45+        body: JSON.stringify({
46+          command: ["echo", "hello"]
47+        }),
48+        method: "POST",
49+        path: "/v1/exec"
50+      },
51+      {
52+        repository,
53+        snapshotLoader: () => snapshot
54+      }
55+    );
56+    assert.equal(invalidExecResponse.status, 200);
57+    const invalidExecPayload = parseJsonBody(invalidExecResponse);
58+    assert.equal(invalidExecPayload.data.ok, false);
59+    assert.equal(invalidExecPayload.data.error.code, "INVALID_INPUT");
60+    assertEmptyExecResultShape(invalidExecPayload.data.result);
61+
62+    const tccExecResponse = await withMockedPlatform("darwin", () =>
63+      handleConductorHttpRequest(
64+        {
65+          body: JSON.stringify({
66+            command: "pwd",
67+            cwd: join(homedir(), "Desktop", "project"),
68+            timeoutMs: 2000
69+          }),
70+          method: "POST",
71+          path: "/v1/exec"
72+        },
73+        {
74+          repository,
75+          snapshotLoader: () => snapshot
76+        }
77+      )
78+    );
79+    assert.equal(tccExecResponse.status, 200);
80+    const tccExecPayload = parseJsonBody(tccExecResponse);
81+    assert.equal(tccExecPayload.data.ok, false);
82+    assert.equal(tccExecPayload.data.error.code, "TCC_PERMISSION_DENIED");
83+    assertEmptyExecResultShape(tccExecPayload.data.result);
84+
85     const systemStateResponse = await handleConductorHttpRequest(
86       {
87         method: "GET",
88@@ -952,7 +1025,8 @@ test("handleConductorHttpRequest serves the migrated local business endpoints fr
89       recursive: true
90     });
91   }
92-});
93+}
94+);
95 
96 test("ConductorRuntime serves health and migrated local API endpoints over HTTP", async () => {
97   const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-"));
M docs/api/local-host-ops.md
+10, -0
 1@@ -234,6 +234,16 @@ curl -X POST "${LOCAL_API_BASE}/v1/files/write" \
 2       "nodeBinary": "/opt/homebrew/bin/node",
 3       "requiresFullDiskAccess": true
 4     }
 5+  },
 6+  "result": {
 7+    "stdout": "",
 8+    "stderr": "",
 9+    "exitCode": null,
10+    "signal": null,
11+    "durationMs": 0,
12+    "startedAt": null,
13+    "finishedAt": null,
14+    "timedOut": false
15   }
16 }
17 ```
M packages/host-ops/src/index.test.js
+5, -5
 1@@ -56,7 +56,6 @@ async function withPatchedEnv(patch, callback) {
 2     }
 3   }
 4 }
 5-
 6 function assertEmptyExecResultShape(result) {
 7   assert.deepEqual(result, {
 8     stdout: "",
 9@@ -93,8 +92,8 @@ test("executeCommand returns a structured failure for non-zero exit codes", { co
10   assert.equal(result.ok, false);
11   assert.equal(result.operation, "exec");
12   assert.equal(result.error.code, "EXEC_EXIT_NON_ZERO");
13-  assert.equal(result.result?.exitCode, 7);
14-  assert.equal(result.result?.stderr, "broken");
15+  assert.equal(result.result.exitCode, 7);
16+  assert.equal(result.result.stderr, "broken");
17 });
18 
19 test("executeCommand returns a complete empty exec result for INVALID_INPUT failures", { concurrency: false }, async () => {
20@@ -126,7 +125,7 @@ test(
21 
22       const outputLines = result.result.stdout.trim().split("\n");
23       assert.equal(outputLines[0], resolvedDirectory);
24-      assert.match(outputLines[1], /^v\\d+\\./);
25+      assert.match(outputLines[1], /^v\d+\./);
26     } finally {
27       await rm(directory, { force: true, recursive: true });
28     }
29@@ -145,7 +144,7 @@ test(
30       },
31       () =>
32         executeCommand({
33-          command: "node -e \\\"process.stdout.write(JSON.stringify(process.env))\\\"",
34+          command: "node -e \"process.stdout.write(JSON.stringify(process.env))\"",
35           timeoutMs: 2_000
36         })
37     );
38@@ -203,6 +202,7 @@ test(
39     assert.equal(result.error.code, "TCC_PERMISSION_DENIED");
40     assert.equal(result.error.details?.accessPoint, "cwd");
41     assert.equal(result.error.details?.protectedPath, join(homedir(), "Downloads"));
42+    assertEmptyExecResultShape(result.result);
43   }
44 );
45