Skip to content

Commit 0988ec5

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/generative-ui-mcp-apps-BG4vy
# Conflicts: # apps/local/executor.config.ts
2 parents 145c07b + 368f256 commit 0988ec5

38 files changed

Lines changed: 129 additions & 610 deletions

File tree

.github/workflows/publish-desktop.yml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,18 @@ jobs:
170170
env:
171171
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
172172
run: |
173-
find artifacts -type f \
173+
set -euo pipefail
174+
while IFS= read -r file; do
175+
echo "Uploading: $file"
176+
gh release upload "$RELEASE_TAG" "$file" --repo "$GITHUB_REPOSITORY" --clobber
177+
done < <(find artifacts -type f \
174178
\( -name "*.dmg" -o -name "*.zip" -o -name "*.exe" \
175179
-o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \
176-
-o -name "latest*.yml" \) | while read file; do
177-
echo "Uploading: $file"
178-
gh release upload "$RELEASE_TAG" "$file" --repo "$GITHUB_REPOSITORY" --clobber || true
179-
done
180+
-o -name "latest*.yml" \))
181+
182+
# Flip draft → published only after every desktop asset is uploaded —
183+
# this is the atomic point where the new tag becomes "latest".
184+
- name: Promote release (draft → published)
185+
env:
186+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
187+
run: gh release edit "$RELEASE_TAG" --draft=false --repo "$GITHUB_REPOSITORY"

apps/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "executor",
3-
"version": "1.4.25",
3+
"version": "1.4.28",
44
"private": true,
55
"bin": {
66
"executor": "./bin/executor.ts"

apps/cli/release-notes/next.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ Downloads land on each [GitHub release](https://github.com/RhysSullivan/executor
1414
## UI
1515

1616
- Sidebar now shows a "Beta" pill next to the executor wordmark.
17+
18+
## Fixes
19+
20+
- Source configuration is no longer replayed from or written back to `executor.jsonc`; local source state stays in the shared Executor database while `executor.jsonc` continues to load plugin entries.

apps/cli/src/release.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,9 @@ const syncGitHubRelease = async (input: {
200200
const notesPath = resolve(cliRoot, "release-notes", "next.md");
201201
const notesFile = existsSync(notesPath) ? notesPath : null;
202202

203+
// Draft until publish-desktop.yml finishes uploading installers and flips
204+
// it; otherwise /releases/latest/download/<desktop-asset> 404s during the
205+
// ~15-20 min desktop build window.
203206
const args = [
204207
"release",
205208
"create",
@@ -211,12 +214,11 @@ const syncGitHubRelease = async (input: {
211214
input.tag,
212215
...(notesFile ? ["--notes-file", notesFile] : ["--generate-notes"]),
213216
"--verify-tag",
217+
"--draft",
214218
];
215219

216220
if (input.channel === "beta") {
217221
args.push("--prerelease");
218-
} else {
219-
args.push("--latest");
220222
}
221223

222224
await runCommand({

apps/cloud/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@executor-js/cloud",
3-
"version": "1.4.11",
3+
"version": "1.4.14",
44
"private": true,
55
"type": "module",
66
"scripts": {

apps/desktop/electron-builder.config.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ const config: Configuration = {
2626
],
2727
mac: {
2828
category: "public.app-category.developer-tools",
29-
target: [
30-
{ target: "dmg", arch: ["arm64", "x64"] },
31-
{ target: "zip", arch: ["arm64", "x64"] },
32-
],
29+
// Do NOT pin `arch:` inside the target objects. The publish workflow's
30+
// matrix passes `--arm64` / `--x64` per leg; a config-level arch list
31+
// would override that flag and force every leg to build both archs from
32+
// a single per-leg sidecar binary, shipping mismatched-arch DMGs (errno
33+
// -86 / EBADARCH on Apple Silicon). The CLI flag is the source of truth.
34+
target: ["dmg", "zip"],
3335
hardenedRuntime: true,
3436
gatekeeperAssess: false,
3537
// electron-builder reads CSC_LINK / CSC_KEY_PASSWORD for the signing

apps/desktop/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@executor-js/desktop",
3-
"version": "1.4.25",
3+
"version": "1.4.28",
44
"private": true,
55
"homepage": "https://github.com/RhysSullivan/executor",
66
"license": "MIT",

apps/desktop/scripts/smoke-sidecar.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@
77
*
88
* Flow:
99
* 1. Spin up a tiny local OpenAPI server (one operation, returns 42).
10-
* 2. Write a temp executor.jsonc that points at it as a source.
11-
* 3. Spawn the compiled `executor-sidecar` binary with EXECUTOR_PORT=0
10+
* 2. Spawn the compiled `executor-sidecar` binary with EXECUTOR_PORT=0
1211
* and parse the `EXECUTOR_READY:<port>` sentinel.
13-
* 4. Connect via MCP streamable HTTP, call the `execute` tool with code
14-
* that invokes the OpenAPI tool, assert the answer round-trips as 42.
12+
* 3. Connect via MCP streamable HTTP, call the `execute` tool with code
13+
* that registers and invokes the OpenAPI tool, assert the answer
14+
* round-trips as 42.
1515
*
1616
* Run after `bun ./scripts/build-sidecar.ts`. Exits non-zero on any
1717
* deviation so it can gate CI.
1818
*/
19-
import { mkdtemp, rm, writeFile } from "node:fs/promises";
19+
import { mkdtemp, rm } from "node:fs/promises";
2020
import { tmpdir } from "node:os";
2121
import { join, resolve } from "node:path";
2222
import { spawn, type Subprocess } from "bun";
@@ -127,8 +127,10 @@ const startOpenApiServer = () => {
127127
};
128128

129129
const waitForReadyPort = (proc: Subprocess<"ignore", "pipe", "pipe">): Promise<number> =>
130+
// oxlint-disable-next-line executor/no-promise-reject -- boundary: standalone build-time smoke harness, no Effect runtime
130131
new Promise((resolveReady, rejectReady) => {
131132
const deadline = setTimeout(() => {
133+
// oxlint-disable-next-line executor/no-promise-reject, executor/no-error-constructor -- boundary: standalone smoke harness reporting a build-time timeout
132134
rejectReady(new Error(`sidecar did not announce ready within ${READY_TIMEOUT_MS}ms`));
133135
}, READY_TIMEOUT_MS);
134136

@@ -150,6 +152,7 @@ const waitForReadyPort = (proc: Subprocess<"ignore", "pipe", "pipe">): Promise<n
150152
const { value, done } = await reader.read();
151153
if (done) {
152154
clearTimeout(deadline);
155+
// oxlint-disable-next-line executor/no-promise-reject, executor/no-error-constructor -- boundary: standalone smoke harness, stdout-closed surfaced as rejection
153156
rejectReady(new Error("sidecar stdout closed before ready"));
154157
return;
155158
}
@@ -179,18 +182,6 @@ const main = async () => {
179182
console.log(`[smoke-sidecar] scope: ${scopeDir}`);
180183
console.log(`[smoke-sidecar] openapi: ${openapi.origin}`);
181184

182-
const config = {
183-
sources: [
184-
{
185-
kind: "openapi",
186-
spec: `${openapi.origin}/openapi.json`,
187-
baseUrl: openapi.origin,
188-
namespace: "petstore",
189-
},
190-
],
191-
};
192-
await writeFile(join(scopeDir, "executor.jsonc"), JSON.stringify(config, null, 2));
193-
194185
const proc = spawn({
195186
cmd: [BINARY],
196187
env: {
@@ -217,9 +208,11 @@ const main = async () => {
217208
if (exitCode === null) proc.kill("SIGKILL");
218209
}
219210
openapi.server.stop(true);
211+
// oxlint-disable-next-line executor/no-promise-catch -- boundary: best-effort tempdir cleanup in a standalone smoke harness
220212
await rm(scopeDir, { recursive: true, force: true }).catch(() => {});
221213
};
222214

215+
// oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: standalone smoke harness needs a finally to tear down the spawned binary + http server
223216
try {
224217
const port = await waitForReadyPort(proc);
225218
const mcpUrl = new URL(`http://127.0.0.1:${port}/mcp`);
@@ -236,9 +229,16 @@ const main = async () => {
236229
if (!hasExecute) fail(`MCP tools/list missing "execute": ${JSON.stringify(tools.tools)}`);
237230

238231
// Drive the running OpenAPI server through a multi-step orchestration
239-
// in one execute. Covers: array list response, path param dispatch, and
240-
// object responses — all going out over real HTTP from inside QuickJS.
232+
// in one execute. Covers: source registration, array list response, path
233+
// param dispatch, and object responses — all going out over real HTTP from
234+
// inside QuickJS.
241235
const code = `
236+
await tools.executor.openapi.addSource({
237+
scope: ${JSON.stringify(scopeDir)},
238+
spec: ${JSON.stringify(`${openapi.origin}/openapi.json`)},
239+
baseUrl: ${JSON.stringify(openapi.origin)},
240+
namespace: "petstore",
241+
});
242242
const list = await tools.petstore.pets.listPets({});
243243
const fetched = await tools.petstore.pets.getPet({ petId: list[1].id });
244244
return {

apps/desktop/src/main/index.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ import { SERVER_SETTINGS_USERNAME, type DesktopServerSettings } from "../shared/
2828

2929
// Pin userData to a friendly app-name-scoped dir BEFORE app.ready so every
3030
// Electron-side consumer (electron-store, electron-log, window-state) lands
31-
// at a predictable spot. User-mutable executor state (executor.jsonc,
32-
// data.db) is pinned separately to ~/.executor in main/sidecar.ts — that
33-
// path matches the CLI's default.
31+
// at a predictable spot. User-mutable executor state (data.db and the optional
32+
// executor.jsonc plugin manifest) is pinned separately to ~/.executor in
33+
// main/sidecar.ts — that path matches the CLI's default.
3434
app.setName("Executor");
3535
app.setPath("userData", join(app.getPath("appData"), "Executor"));
3636

@@ -302,6 +302,11 @@ const promptInstallUpdate = async (version: string) => {
302302
}
303303
};
304304

305+
// Re-check periodically so a long-running session picks up releases
306+
// without requiring a quit + relaunch. The boot-time check still runs;
307+
// this interval is purely a self-heal for idle apps.
308+
const UPDATE_POLL_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
309+
305310
const setupAutoUpdater = () => {
306311
if (!app.isPackaged) return;
307312
autoUpdater.logger = log;
@@ -315,6 +320,11 @@ const setupAutoUpdater = () => {
315320
autoUpdater.on("error", (err: Error) => {
316321
log.warn("[updater] error", err);
317322
});
323+
324+
setInterval(() => {
325+
if (downloadedUpdateVersion) return; // already staged; waiting on the user
326+
void runUpdateCheck({ alertOnFail: false });
327+
}, UPDATE_POLL_INTERVAL_MS);
318328
};
319329

320330
interface UpdateCheckOptions {

apps/desktop/src/main/sidecar.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,14 @@ export async function startSidecar(options: StartOptions = {}): Promise<SidecarC
7474
);
7575
}
7676

77-
// executor.jsonc and data.db go to ~/.executor — the same path the CLI's
78-
// `executor web` uses. Desktop and CLI share state on the same machine so
79-
// sources/secrets/policies set up in one show up in the other, and
80-
// user-facing commands like `executor mcp --scope ~/.executor` stay
81-
// copy-paste-friendly. Electron's userData (set in main/index.ts) is
82-
// still used for electron-store, electron-log, and window-state — those
83-
// stay app-scoped to avoid colliding with anything else under HOME.
77+
// data.db and the optional executor.jsonc plugin manifest live under
78+
// ~/.executor — the same path the CLI's `executor web` uses. Desktop and CLI
79+
// share state on the same machine so sources/secrets/policies set up in one
80+
// show up in the other, and user-facing commands like
81+
// `executor mcp --scope ~/.executor` stay copy-paste-friendly. Electron's
82+
// userData (set in main/index.ts) is still used for electron-store,
83+
// electron-log, and window-state — those stay app-scoped to avoid colliding
84+
// with anything else under HOME.
8485
const scopeDir = join(homedir(), ".executor");
8586
mkdirSync(scopeDir, { recursive: true });
8687

0 commit comments

Comments
 (0)