openpencil/scripts/ensure-agent-native.cjs
Kayshen-X a7d73ebb62 feat(ai): pencil-style agentic design tool-loop, multi-chat tabs, #27 panel restyle
Built-in design generation now runs as an agentic MCP tool-loop (reusing the
agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the
Settings experimental toggle; the orchestrator stays the default.

- design-agent system prompt + in-process design toolset (parity-locked with
  the MCP surface) + flag-gated Intent::Design routing
- spawn_agents execution as sequential sub-loops + live creation-mode badges
  (per-agent glow + 'N/M designing...' header)
- new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state,
  export_nodes, spawn_agents; style-guide local audit
- #27 AI panel restyle: rounded tool cards + green check-rings, gray user
  bubbles, model-pill bottom toolbar, header, empty-state pills, the
  PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown
- multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI
  (switch / close / + / Cmd+T) with each run bound to its tab

Large checkpoint commit spanning the working tree (Rust shell crates).
2026-07-02 21:21:06 +08:00

106 lines
3.7 KiB
JavaScript

#!/usr/bin/env node
// Provisions the Zig NAPI addon binary by building it from source.
//
// We always build from source on the host so the resulting `agent_napi.node`
// matches the runner's platform/arch. Earlier revisions also tried to download
// a prebuilt from a sibling release repo, but that path was racy: when the
// prebuilt was missing for the current submodule SHA the build fell through
// to source compilation, deposited the binary at `zig-out/napi/...`, and
// electron-builder (which only ships `packages/agent-native/napi/`) silently
// shipped without the addon — every chat request then died at the dynamic
// `@zseven-w/agent-native` import.
//
// Build prerequisite: Zig 0.15+ on PATH. CI workflows install it via
// `mlugg/setup-zig`; local devs install once via their package manager.
//
// Set OPENPENCIL_REQUIRE_AGENT_NATIVE=1 to fail the install when the build
// can't run (electron CI uses this to surface missing prerequisites early).
//
// Set OPENPENCIL_SKIP_AGENT_NATIVE=1 to no-op the script entirely. Useful for
// workflows (npm publish, lint-only CI) that never load the addon at runtime
// and would otherwise pay for a Zig build on every install.
//
// Set ZIG_TARGET to cross-compile for a non-host triple (e.g. on a macOS arm64
// runner build for x86_64-macos with `ZIG_TARGET=x86_64-macos`). Without it
// the build follows the host arch — fine for native runs, wrong when the
// runner doesn't match the artifact you intend to ship.
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const AGENT_DIR = path.join(__dirname, '..', 'packages', 'agent-native');
const NAPI_DIR = path.join(AGENT_DIR, 'napi');
const ZIG_OUT = path.join(AGENT_DIR, 'zig-out', 'napi', 'agent_napi.node');
const BUNDLED = path.join(NAPI_DIR, 'agent_napi.node');
const STRICT = process.env.OPENPENCIL_REQUIRE_AGENT_NATIVE === '1';
function log(msg) {
console.log(`[agent-native] ${msg}`);
}
function fail(msg) {
log(msg);
return STRICT ? 1 : 0;
}
function bundleBinary() {
fs.mkdirSync(NAPI_DIR, { recursive: true });
fs.copyFileSync(ZIG_OUT, BUNDLED);
log(`Bundled binary at ${BUNDLED}.`);
}
function buildFromSource() {
try {
execSync('zig version', { stdio: 'ignore' });
} catch {
return fail(
'Zig not installed (need 0.15+). Skipping. Install Zig and re-run `bun run agent:build`.',
);
}
const target = process.env.ZIG_TARGET?.trim();
const targetFlag = target ? ` -Dtarget=${target}` : '';
log(`Building NAPI addon (zig build napi -Doptimize=ReleaseFast${targetFlag})…`);
try {
execSync(`zig build napi -Doptimize=ReleaseFast${targetFlag}`, {
cwd: AGENT_DIR,
stdio: 'inherit',
});
} catch (err) {
return fail(`Zig build failed: ${err.message}`);
}
if (!fs.existsSync(ZIG_OUT)) {
return fail(`Zig build produced no output at ${ZIG_OUT}.`);
}
bundleBinary();
return 0;
}
function main() {
if (process.env.OPENPENCIL_SKIP_AGENT_NATIVE === '1') {
log('OPENPENCIL_SKIP_AGENT_NATIVE=1, skipping native binary provisioning.');
return 0;
}
if (!fs.existsSync(path.join(NAPI_DIR, 'package.json'))) {
return fail('Submodule not initialized; run `git submodule update --init`. Skipping.');
}
// Fast path: binary already in place. Make sure both lookup locations are
// populated so electron-builder (`napi/`) and the runtime loader (which
// checks `zig-out/` first) both find it without re-running the build.
if (fs.existsSync(BUNDLED)) {
log('Binary already present, skipping rebuild.');
return 0;
}
if (fs.existsSync(ZIG_OUT)) {
log('Binary already built; copying into napi/ for electron-builder.');
bundleBinary();
return 0;
}
return buildFromSource();
}
process.exit(main());