← Commit history

SDK v1.9.47: corrected bridge.json schema (entrypoint/kind) + cold-start tiers section

John Lauer ·0cbf992087 ·4mo ago ·parent 8cc0546
3 files changed +57−11
SKILL.md+55−9
@@ -33,28 +33,45 @@ If a *bridge verb* misbehaves → fix the bridge. If a *generic AD capability* i 
 ```jsonc
 {
-  "name": "blender",                       // registry name → verb prefix "blender_"
+  "manifest_version": 1,
+  "name": "blender",                        // registry name → verb prefix "blender_"
+  "displayName": "Blender",                 // in-app chip label (the tool's real name)
   "version": "1.2.0",                       // numeric; a newer CACHE copy supersedes the bundled seed
   "description": "Drive Blender headless + GUI",
   "author": "you",
-  "docs": "https://wiki.adom.inc/adom/adom-desktop-blender-bridge",  // your OWN dedicated page (drives skillPkg + hero)
+  "license": "MIT",
+  "docs": "https://wiki.adom.inc/adom/adom-desktop-blender-bridge",  // your OWN page (drives skillPkg + hero)
   "hero": "https://.../hero.png",           // optional; else AD uses your wiki page hero, else a monogram
   "spawn": {
-    "kind": "process",                      // "process" (AD spawns it) | "external-http" (AD only probes)
-    "cmd": "python",                        // or a bundled interpreter (e.g. ship node_modules; see Prewarm)
-    "args": ["server.py", "--port", "{port}"],  // {port} ← AD's STABLE allocated port
+    "kind": "python",                       // python | node | exe | external-http  (NOT "process")
+    "entrypoint": "server.py",              // the file/exe AD launches; AD AUTO-APPENDS `--port <port>` to argv
+    "port": 0,                              // 0 = AD picks a STABLE port (recommended). Bind 127.0.0.1:<that port>.
     "persistent": true,                     // AD's supervisor respawns it if it dies
-    "cwd": "."
+    "healthEndpoint": "/health",            // INSIDE spawn. AD probes it for the chip (default /health)
+    "stopMethod": "kill",                   // kill | sigterm | graceful_endpoint
+    "killImageName": "python.exe"           // image name for taskkill on stop (kind:python/node)
   },
-  "healthEndpoint": "/health",              // default /health; AD probes it for the chip
+  "verbPrefixes": ["blender_"],             // any verb with these prefixes routes to you
+  "verbs": ["blender_render", "blender_status", "blender_describe"],  // every verb (routing + Verbs tab)
   "statusVerb": "blender_status",           // verb a caller polls after a long op times out
-  "updateManifestUrl": "https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json"
+  "updateManifestUrl": "https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json",
+
+  // v1.9.47 cold-start (see "Cold-start tiers" below):
+  "detect": {                               // Tier 2 — how AD DETECTS your HOST APP. AD NEVER installs it.
+    "hostApp": "Blender",                   // omit the whole `detect` block if your bridge brings its own runtime
+    "appPathsExe": "blender.exe",           //   (e.g. pup ships its own browser → no host app)
+    "paths": { "windows": ["%ProgramFiles%\\Blender Foundation\\*\\blender.exe"] }
+  },
+  "prewarm": { "assets": [] }               // Tier 1 — heavy runtime assets AD prewarms (e.g. ["chrome-for-testing"])
 }
 ```
 
-- **`{port}`** — AD allocates a STABLE port (persisted across respawns + AD restarts) and substitutes it. Bind `127.0.0.1:{port}`. NEVER hardcode a port (Hyper-V/Docker silently reserve ranges — see PORTS.md).
+- **`spawn.kind`** is `python` | `node` | `exe` | `external-http` — there is **no `"process"` kind**, and no `cmd`/`args`/`cwd`. AD launches `spawn.entrypoint` and **automatically appends `--port <port>`** to its argv; your server parses `--port`. (`external-http` = AD doesn't spawn, only probes a port you declare via a `discovery` block.)
+- **`spawn.port: 0`** → AD allocates a STABLE port (persisted across respawns + AD restarts) and passes it. Bind `127.0.0.1:<port>`. NEVER hardcode a port (Hyper-V/Docker silently reserve ranges — see PORTS.md).
+- **`spawn.healthEndpoint`** lives **inside `spawn`** (not at the top level), default `/health`.
 - **`updateManifestUrl`** — the full URL of YOUR manifest on YOUR page. **Declare it in `bridge.json`** so it survives a republish; AD polls it (on launch + every 4h) and auto-updates users. (AD also remembers the install URL in `~/.adom/bridge-install-urls.json` as a fallback, but the declared field is the durable, author-intended path.)
 - **`docs`** — point at YOUR dedicated wiki page. AD derives your **skillPkg** (`adom/<slug>`) and your card hero from this. Don't point it at a shared/structural page.
+- **`detect` / `prewarm`** — the v1.9.47 cold-start declarations (full rules in "Cold-start tiers" below). `detect` = your HOST APP (AD detects, never installs); `prewarm` = your heavy RUNTIME assets (AD prewarms/auto-installs). A node bridge that ships its deps still works without `prewarm`; declare `prewarm.assets` only for big *downloaded* assets like a browser.
 
 ---
 
@@ -149,6 +166,35 @@ A cold first call that pays multi-minute setup (npm install, downloading a runti 
 ---
 
+## Cold-start tiers, host-app detection & the `bridge_readiness` probe (v1.9.47)
+
+A bridge has TWO kinds of dependency, and **AD treats them OPPOSITELY.** Get this right or a first-install probe (e.g. HD's "what tools do I have") triggers a slow install at the worst moment.
+
+**Tier 1 — your RUNTIME (Node, Python, your own browser download): AD prewarms + auto-installs it.** This is your plumbing. On first boot AD installs Node/Python in the background (winget) and prewarms heavy assets you declare. You may also download your own runtime asset (pup downloads Chrome-for-Testing) — fine, it's yours. **But do it in the BACKGROUND: a `*_status` / `*_readiness` / ANY probe verb must NEVER block on a heavy download.** Spawn your server fast (it usually doesn't need the asset to *start*), kick the download off detached, and report `state:"warming"` until ready. A synchronous-download-on-first-verb is the exact footgun this release fixed (pup's 150 MB Chrome fetch used to block the probe).
+
+**Tier 2 — the HOST APP (KiCad, Fusion, Altium, …): AD ONLY DETECTS it — NEVER installs it.** The user's app is theirs; installing it pre-emptively is rude. Its *presence* is the signal: a machine with KiCad belongs to a KiCad user (EDA users are mono-tool — a Fusion user isn't a KiCad user). So AD detects it and **won't even prewarm your bridge if your host app isn't installed** (no point spinning up the KiCad bridge on a no-KiCad machine; on-demand spawn still works if they install it later).
+
+**Declare both in `bridge.json`:**
+```json
+"detect": {                          // Tier 2 — how AD finds your HOST APP (detection only)
+  "hostApp": "KiCad",
+  "appPathsExe": "kicad.exe",        // optional: Windows App Paths registry lookup (most reliable)
+  "paths": {                         // optional: candidate paths, %VAR% expanded, one * = a version dir
+    "windows": ["%ProgramFiles%\\KiCad\\*\\bin\\kicad.exe"],
+    "macos":   ["/Applications/KiCad/KiCad.app"],
+    "linux":   ["/usr/bin/kicad"]
+  }
+},
+"prewarm": { "assets": ["chrome-for-testing"] }   // Tier 1 — heavy runtime assets AD prewarms + reports
+```
+A bridge that brings its OWN runtime (pup ships its own browser) **omits `detect`** — it has no external host app. AD infers the runtime from `spawn.kind` (node→Node, python→Python).
+
+**The read-only probe: `bridge_readiness`.** AD aggregates all of this into ONE side-effect-free verb the setup AI calls — it **never spawns a bridge, never installs/downloads**. Per bridge: `{hostApp, hostAppInstalled, runtime, runtimeReady, assets, state}` where `state` ∈ **`ready`** (use it) | **`warming`** (runtime ok, a heavy asset still downloading in the background — re-poll, don't trigger it) | **`needs-runtime`** (node/python installing) | **`no-app`** (host app not installed → not relevant here). Plus a top-level `edaToolsInstalled` list — the one-line answer to "what EDA tools do I have." **Tell your users (in your skill): for a "what do I have / is it ready" question call `bridge_readiness`, NOT your `*_status` verb** — a `*_status` may spawn your bridge; readiness never does.
+
+Your two own cold-start verbs (above) still apply: `<prefix>_readiness` (read-only `{ready, installing, installProgressPct, lastError}`) + `<prefix>_prewarm` (`{wait}`-able). Those drive YOUR bridge specifically; `bridge_readiness` is the AD-level aggregate that detects + summarizes across all bridges without spawning any.
+
+---
+
 ## The SKILL SET you should ship (not one SKILL.md — a small library)
 
 Don't cram everything into one file. Ship **three kinds** of skill, each with a different reader:
package.json+1−1
@@ -1,6 +1,6 @@ {
   "slug": "adom-desktop-bridges",
-  "version": "1.1.0",
+  "version": "1.1.1",
   "type": "app",
   "description": "Bridge SDK: build/publish/discover an adom-desktop bridge. Root skill = the full SDK guide; sub-skills = shipping-a-bridge, bridge-author (authoring walkthrough), and 3 fill-in-the-blanks templates (dev/publish/user). bridge.json schema, lifecycle verbs, rich-hints-in-output rule.",
   "dependencies": {},
page.json+1−1
@@ -4,7 +4,7 @@   "slug": "adom-desktop-bridges",
   "title": "Adom Desktop - Bridge SDK",
   "brief": "Bridge SDK guide: bridge.json schema, kind:python/node/exe, hello-python + hello-rust reference templates, packaging + lifecycle commands.",
-  "version": "1.1.0",
+  "version": "1.1.1",
   "tags": [],
   "license": "MIT",
   "discovery_triggers": [