@elizaos/app-core 2.0.0-beta.1 → 2.0.0-beta.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.
Files changed (43) hide show
  1. package/package.json +2 -2
  2. package/platforms/electrobun/native/macos/window-effects.mm +103 -0
  3. package/platforms/electrobun/package.json +9 -0
  4. package/platforms/electrobun/src/__stubs__/bun-ffi.ts +16 -0
  5. package/platforms/electrobun/src/libMacWindowEffects.dylib +0 -0
  6. package/platforms/electrobun/src/native/agent.ts +74 -3
  7. package/platforms/electrobun/src/native/desktop.ts +39 -6
  8. package/platforms/electrobun/src/native/mac-window-effects.ts +61 -1
  9. package/platforms/electrobun/src/native/permissions-shared.ts +3 -2
  10. package/platforms/electrobun/src/native/permissions.ts +11 -6
  11. package/platforms/electrobun/src/rpc-handlers.ts +7 -0
  12. package/platforms/electrobun/src/rpc-schema.ts +39 -4
  13. package/platforms/electrobun/src/runtime-permissions.ts +7 -1
  14. package/runtime/ensure-local-inference-handler.d.ts +1 -0
  15. package/runtime/ensure-local-inference-handler.d.ts.map +1 -1
  16. package/runtime/ensure-local-inference-handler.js +9 -0
  17. package/runtime/mode/remote-forwarder.d.ts.map +1 -1
  18. package/runtime/mode/remote-forwarder.js +1 -1
  19. package/runtime/mode/runtime-mode.d.ts +20 -2
  20. package/runtime/mode/runtime-mode.d.ts.map +1 -1
  21. package/runtime/mode/runtime-mode.js +69 -1
  22. package/scripts/aosp/stage-default-models.mjs +2 -2
  23. package/scripts/build-llama-cpp-dflash.mjs +75 -40
  24. package/scripts/kernel-patches/metal-kernels.mjs +357 -337
  25. package/scripts/lib/read-app-identity.mjs +5 -1
  26. package/services/local-inference/catalog.d.ts +2 -1
  27. package/services/local-inference/catalog.d.ts.map +1 -1
  28. package/services/local-inference/catalog.js +131 -12
  29. package/services/local-inference/downloader.d.ts +2 -0
  30. package/services/local-inference/downloader.d.ts.map +1 -1
  31. package/services/local-inference/downloader.js +300 -1
  32. package/services/local-inference/manifest/validator.d.ts.map +1 -1
  33. package/services/local-inference/manifest/validator.js +48 -0
  34. package/services/local-inference/providers.d.ts +1 -1
  35. package/services/local-inference/providers.js +6 -6
  36. package/services/local-inference/registry.d.ts.map +1 -1
  37. package/services/local-inference/registry.js +10 -1
  38. package/services/local-inference/types.d.ts +6 -0
  39. package/services/local-inference/types.d.ts.map +1 -1
  40. package/test/helpers/real-runtime.ts +21 -20
  41. package/platforms/electrobun/src/native/permissions-darwin.ts +0 -342
  42. package/platforms/electrobun/src/native/permissions-linux.ts +0 -34
  43. package/platforms/electrobun/src/native/permissions-win32.ts +0 -56
@@ -28,6 +28,7 @@ import { localInferenceEngine } from "../services/local-inference/engine";
28
28
  import { handlerRegistry } from "../services/local-inference/handler-registry";
29
29
  import { listInstalledModels } from "../services/local-inference/registry";
30
30
  import { installRouterHandler } from "../services/local-inference/router-handler";
31
+ import { getRuntimeMode } from "./mode/runtime-mode";
31
32
  const LOCAL_INFERENCE_PROVIDER = "eliza-local-inference";
32
33
  const DEVICE_BRIDGE_PROVIDER = "eliza-device-bridge";
33
34
  const CAPACITOR_LLAMA_PROVIDER = "capacitor-llama";
@@ -46,6 +47,9 @@ const AOSP_LLAMA_PROVIDER = "eliza-aosp-llama";
46
47
  * routing-policy layer picks between them).
47
48
  */
48
49
  const LOCAL_INFERENCE_PRIORITY = 0;
50
+ export function shouldRegisterLocalInferenceHandlers(mode) {
51
+ return mode === "local" || mode === "local-only";
52
+ }
49
53
  function getLoader(runtime) {
50
54
  const candidate = runtime.getService?.("localInferenceLoader");
51
55
  if (!candidate || typeof candidate !== "object")
@@ -278,6 +282,11 @@ async function tryRegisterCapacitorLoader(runtime) {
278
282
  return false;
279
283
  }
280
284
  export async function ensureLocalInferenceHandler(runtime) {
285
+ const runtimeMode = getRuntimeMode();
286
+ if (!shouldRegisterLocalInferenceHandlers(runtimeMode)) {
287
+ logger.info(`[local-inference] Runtime mode is ${runtimeMode}; skipping local model handler registration`);
288
+ return;
289
+ }
281
290
  const runtimeWithRegistration = runtime;
282
291
  if (typeof runtimeWithRegistration.getModel !== "function" ||
283
292
  typeof runtimeWithRegistration.registerModel !== "function") {
@@ -1 +1 @@
1
- {"version":3,"file":"remote-forwarder.d.ts","sourceRoot":"","sources":["../../../../../../src/runtime/mode/remote-forwarder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAelC,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAKT;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAClC,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAC/B,OAAO,CAkBT;AAUD;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,GACvB,OAAO,CAAC,OAAO,CAAC,CA8ClB"}
1
+ {"version":3,"file":"remote-forwarder.d.ts","sourceRoot":"","sources":["../../../../../../src/runtime/mode/remote-forwarder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAelC,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAKT;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAClC,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAC/B,OAAO,CAkBT;AAUD;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,GACvB,OAAO,CAAC,OAAO,CAAC,CAkDlB"}
@@ -96,7 +96,7 @@ export async function forwardRemoteCloudMutation(req, res) {
96
96
  if (!shouldForwardToRemoteTarget(url.pathname, method))
97
97
  return false;
98
98
  if (!snapshot.remoteApiBase) {
99
- sendJsonError(res, 503, "Remote target not configured");
99
+ sendJsonError(res, snapshot.remoteApiBaseError ? 400 : 503, snapshot.remoteApiBaseError ?? "Remote target not configured");
100
100
  return true;
101
101
  }
102
102
  const targetUrl = new URL(`${url.pathname}${url.search}`, snapshot.remoteApiBase);
@@ -23,9 +23,11 @@ export interface RuntimeModeSnapshot {
23
23
  mode: RuntimeMode;
24
24
  deploymentTarget: DeploymentTargetConfig | null;
25
25
  /** Present iff `mode === "remote"`. The local-instance HTTP base the
26
- * controller proxies to. Cloud bases are rejected upstream during
27
- * onboarding (see `onboarding.ts`). */
26
+ * controller proxies to. Cloud/public bases are rejected here too so
27
+ * stale or hand-edited config cannot turn remote mode into cloud mode. */
28
28
  remoteApiBase: string | null;
29
+ /** Populated when a remote target was configured but rejected. */
30
+ remoteApiBaseError: string | null;
29
31
  remoteAccessToken: string | null;
30
32
  }
31
33
  declare const RuntimeModeConfigSchema: z.ZodObject<{
@@ -50,5 +52,21 @@ export declare function getRuntimeMode(): RuntimeMode;
50
52
  export declare function getRuntimeModeSnapshot(): RuntimeModeSnapshot;
51
53
  /** True for both `local` and `local-only`. */
52
54
  export declare function isLocalRuntime(mode: RuntimeMode): boolean;
55
+ export interface RemoteApiBaseValidationOk {
56
+ ok: true;
57
+ href: string;
58
+ }
59
+ export interface RemoteApiBaseValidationErr {
60
+ ok: false;
61
+ error: string;
62
+ }
63
+ export type RemoteApiBaseValidation = RemoteApiBaseValidationOk | RemoteApiBaseValidationErr;
64
+ /**
65
+ * Remote mode is a thin controller for another local/private Eliza instance,
66
+ * never for Eliza Cloud or a public model API. Accept loopback, private
67
+ * RFC1918/CGNAT/link-local hosts, and .local mDNS names.
68
+ */
69
+ export declare function validateRemoteApiBase(value: string | null | undefined): RemoteApiBaseValidation;
70
+ export declare function isLocalRemoteHost(hostname: string): boolean;
53
71
  export {};
54
72
  //# sourceMappingURL=runtime-mode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-mode.d.ts","sourceRoot":"","sources":["../../../../../../src/runtime/mode/runtime-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EACL,KAAK,sBAAsB,EAE5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa,qDAKhB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,WAAW,CAAC;IAClB,gBAAgB,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD;;4CAEwC;IACxC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAOD,QAAA,MAAM,uBAAuB;;;;;iBASb,CAAC;AAEjB,KAAK,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAatE;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,GAChD,mBAAmB,CAkCrB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAE5C;AAED,4BAA4B;AAC5B,wBAAgB,sBAAsB,IAAI,mBAAmB,CAE5D;AAED,8CAA8C;AAC9C,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAEzD"}
1
+ {"version":3,"file":"runtime-mode.d.ts","sourceRoot":"","sources":["../../../../../../src/runtime/mode/runtime-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EACL,KAAK,sBAAsB,EAE5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa,qDAKhB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,WAAW,CAAC;IAClB,gBAAgB,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAChD;;+EAE2E;IAC3E,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,kEAAkE;IAClE,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAOD,QAAA,MAAM,uBAAuB;;;;;iBASb,CAAC;AAEjB,KAAK,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAatE;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,GAChD,mBAAmB,CAuCrB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAE5C;AAED,4BAA4B;AAC5B,wBAAgB,sBAAsB,IAAI,mBAAmB,CAE5D;AAED,8CAA8C;AAC9C,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAEzD;AAED,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,uBAAuB,GAC/B,yBAAyB,GACzB,0BAA0B,CAAC;AAE/B;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,uBAAuB,CAuBzB;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAsB3D"}
@@ -55,10 +55,13 @@ function parseRuntimeModeConfig(config) {
55
55
  export function resolveRuntimeMode(config) {
56
56
  const deploymentTarget = normalizeDeploymentTargetConfig(config?.deploymentTarget);
57
57
  if (deploymentTarget?.runtime === "remote") {
58
+ const remoteApiBase = deploymentTarget.remoteApiBase?.trim() || null;
59
+ const remoteValidation = validateRemoteApiBase(remoteApiBase);
58
60
  return {
59
61
  mode: "remote",
60
62
  deploymentTarget,
61
- remoteApiBase: deploymentTarget.remoteApiBase?.trim() || null,
63
+ remoteApiBase: remoteValidation.ok ? remoteValidation.href : null,
64
+ remoteApiBaseError: remoteValidation.ok ? null : remoteValidation.error,
62
65
  remoteAccessToken: deploymentTarget.remoteAccessToken?.trim() || null,
63
66
  };
64
67
  }
@@ -67,6 +70,7 @@ export function resolveRuntimeMode(config) {
67
70
  mode: "cloud",
68
71
  deploymentTarget,
69
72
  remoteApiBase: null,
73
+ remoteApiBaseError: null,
70
74
  remoteAccessToken: null,
71
75
  };
72
76
  }
@@ -78,6 +82,7 @@ export function resolveRuntimeMode(config) {
78
82
  mode: cloudExplicitlyDisabled ? "local-only" : "local",
79
83
  deploymentTarget: deploymentTarget ?? null,
80
84
  remoteApiBase: null,
85
+ remoteApiBaseError: null,
81
86
  remoteAccessToken: null,
82
87
  };
83
88
  }
@@ -97,3 +102,66 @@ export function getRuntimeModeSnapshot() {
97
102
  export function isLocalRuntime(mode) {
98
103
  return mode === "local" || mode === "local-only";
99
104
  }
105
+ /**
106
+ * Remote mode is a thin controller for another local/private Eliza instance,
107
+ * never for Eliza Cloud or a public model API. Accept loopback, private
108
+ * RFC1918/CGNAT/link-local hosts, and .local mDNS names.
109
+ */
110
+ export function validateRemoteApiBase(value) {
111
+ const raw = value?.trim();
112
+ if (!raw) {
113
+ return { ok: false, error: "Remote target not configured" };
114
+ }
115
+ let url;
116
+ try {
117
+ url = new URL(raw);
118
+ }
119
+ catch {
120
+ return { ok: false, error: "Remote target must be a valid URL" };
121
+ }
122
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
123
+ return { ok: false, error: "Remote target must use http or https" };
124
+ }
125
+ if (!isLocalRemoteHost(url.hostname)) {
126
+ return {
127
+ ok: false,
128
+ error: "Remote mode can only target loopback, .local, or private-network Eliza instances",
129
+ };
130
+ }
131
+ url.pathname = url.pathname.replace(/\/+$/, "");
132
+ return { ok: true, href: url.toString() };
133
+ }
134
+ export function isLocalRemoteHost(hostname) {
135
+ const host = hostname.trim().toLowerCase().replace(/^\[(.*)\]$/, "$1");
136
+ if (!host)
137
+ return false;
138
+ if (host === "localhost" || host.endsWith(".localhost"))
139
+ return true;
140
+ if (host.endsWith(".local"))
141
+ return true;
142
+ if (host === "::1")
143
+ return true;
144
+ if (host.startsWith("fe80:"))
145
+ return true;
146
+ const parts = host.split(".");
147
+ if (parts.length !== 4)
148
+ return false;
149
+ const octets = parts.map((p) => Number(p));
150
+ if (!octets.every((n) => Number.isInteger(n) && n >= 0 && n <= 255)) {
151
+ return false;
152
+ }
153
+ const [a, b] = octets;
154
+ if (a === 10)
155
+ return true;
156
+ if (a === 127)
157
+ return true;
158
+ if (a === 169 && b === 254)
159
+ return true;
160
+ if (a === 172 && b >= 16 && b <= 31)
161
+ return true;
162
+ if (a === 192 && b === 168)
163
+ return true;
164
+ if (a === 100 && b >= 64 && b <= 127)
165
+ return true;
166
+ return false;
167
+ }
@@ -68,7 +68,7 @@ const repoRoot = resolveRepoRootFromImportMeta(import.meta.url);
68
68
  const CHAT_MODEL_ELIZA_1_MOBILE = {
69
69
  id: "eliza-1-mobile-1_7b",
70
70
  displayName: "Eliza-1 mobile",
71
- hfRepo: "elizaos/eliza-1-mobile-1_7b",
71
+ hfRepo: "elizalabs/eliza-1-mobile-1_7b",
72
72
  ggufFile: "text/eliza-1-mobile-1_7b-32k.gguf",
73
73
  expectedMinBytes: 900 * 1024 * 1024,
74
74
  expectedMaxBytes: 1700 * 1024 * 1024,
@@ -78,7 +78,7 @@ const CHAT_MODEL_ELIZA_1_MOBILE = {
78
78
  const EMBEDDING_MODEL_ELIZA_1_LITE = {
79
79
  id: "eliza-1-lite-0_6b",
80
80
  displayName: "Eliza-1 lite",
81
- hfRepo: "elizaos/eliza-1-lite-0_6b",
81
+ hfRepo: "elizalabs/eliza-1-lite-0_6b",
82
82
  ggufFile: "text/eliza-1-lite-0_6b-32k.gguf",
83
83
  expectedMinBytes: 300 * 1024 * 1024,
84
84
  expectedMaxBytes: 800 * 1024 * 1024,
@@ -647,8 +647,8 @@ function cmakeFlagsForTarget(target, ctx) {
647
647
  // EMBED_LIBRARY behavior:
648
648
  //
649
649
  // * iOS (later in this function) sets it to ON unconditionally —
650
- // the static-archive build needs the metallib data baked into
651
- // the framework via .incbin.
650
+ // the static-archive build needs compiled metallib bytes baked
651
+ // into the framework via .incbin.
652
652
  // * Darwin desktop (this branch) sets it OFF so the metallib is
653
653
  // compiled at build time into a sidecar default.metallib that
654
654
  // ships next to llama-server. The kernel-patches/metal-kernels
@@ -657,13 +657,10 @@ function cmakeFlagsForTarget(target, ctx) {
657
657
  // is compiled into its own .air and merged into default.metallib
658
658
  // alongside ggml-metal.air.
659
659
  //
660
- // The EMBED path is incompatible with the standalones because
661
- // it concatenates ggml-metal.metal + ggml-common.h into a single
662
- // compilation unit, and the standalones redefine block_qjl1_256
663
- // and block_q4_polar (same byte layout, different field names)
664
- // plus QK_POLAR / QK_QJL / QJL_RESIDUAL_BYTES already in
665
- // ggml-common.h. Wiring them through the EMBED path requires a
666
- // dup-strip pass that is filed as a separate follow-up.
660
+ // The EMBED path is patched by kernel-patches/metal-kernels.mjs
661
+ // to embed a compiled default.metallib instead of concatenated
662
+ // source. That keeps the standalone shader TUs separate and
663
+ // avoids duplicate block_* / constant declarations.
667
664
  flags.push("-DGGML_METAL_EMBED_LIBRARY=OFF");
668
665
  } else if (backend === "cuda") {
669
666
  flags[flags.indexOf("-DGGML_CUDA=OFF")] = "-DGGML_CUDA=ON";
@@ -826,13 +823,11 @@ function cmakeFlagsForTarget(target, ctx) {
826
823
  } else {
827
824
  flags.push("-DCMAKE_OSX_SYSROOT=iphoneos");
828
825
  }
829
- // iOS static-archive build needs the metallib data baked in via .incbin
830
- // since there is no place on-device to ship a sidecar default.metallib.
831
- // Override the OFF default that the metal backend block set above.
832
- // NOTE: targetCompatibility() refuses iOS Metal before configure/build
833
- // until kernel-patches/metal-kernels.mjs wires the EMBED_LIBRARY path.
834
- // Keep this flag correct for the future patch, but do not rely on late
835
- // requiredKernelsMissing() failure for today's unsupported path.
826
+ // iOS static-archive build needs compiled metallib data baked in via
827
+ // .incbin since there is no sidecar default.metallib next to a console
828
+ // binary. The Metal patcher rewrites the EMBED_LIBRARY CMake branch to
829
+ // compile ggml-metal.metal + the milady standalones into one metallib
830
+ // before embedding it.
836
831
  const embedIdx = flags.indexOf("-DGGML_METAL_EMBED_LIBRARY=OFF");
837
832
  if (embedIdx !== -1) {
838
833
  flags[embedIdx] = "-DGGML_METAL_EMBED_LIBRARY=ON";
@@ -857,17 +852,6 @@ function targetCompatibility(target, ctx) {
857
852
  "fused (omnivoice-grafted) targets are desktop/server only — mobile fusion is not wired yet",
858
853
  };
859
854
  }
860
- if (platform === "ios" && backend === "metal") {
861
- return {
862
- ok: false,
863
- reason:
864
- "iOS Metal cannot yet ship the full Eliza-1 kernel set: the current " +
865
- "metal-kernels.mjs patch wires only the non-EMBED default.metallib " +
866
- "branch used by darwin host builds. iOS uses GGML_METAL_EMBED_LIBRARY=ON, " +
867
- "so TurboQuant/QJL/Polar standalones are not embedded. Refusing before " +
868
- "configure/build; wire the embedded metallib path before enabling this target.",
869
- };
870
- }
871
855
  if (platform === "darwin" && process.platform !== "darwin") {
872
856
  return { ok: false, reason: "darwin target requires macOS host" };
873
857
  }
@@ -1044,6 +1028,12 @@ function ensureCheckout(cacheDir, ref) {
1044
1028
  if (fs.existsSync(path.join(cacheDir, ".git"))) {
1045
1029
  run("git", ["fetch", "--depth=1", "origin", ref], { cwd: cacheDir });
1046
1030
  run("git", ["checkout", "FETCH_HEAD"], { cwd: cacheDir });
1031
+ // This checkout is a generated build cache owned by this script. Always
1032
+ // reset tracked and untracked source edits before applying our patch set
1033
+ // so stale kernel-patch artifacts from prior builds cannot leak into a
1034
+ // new artifact.
1035
+ run("git", ["reset", "--hard", "FETCH_HEAD"], { cwd: cacheDir });
1036
+ run("git", ["clean", "-fd"], { cwd: cacheDir });
1047
1037
  } else {
1048
1038
  fs.mkdirSync(path.dirname(cacheDir), { recursive: true });
1049
1039
  run("git", ["clone", "--depth=1", "--branch", ref, REMOTE, cacheDir]);
@@ -1097,12 +1087,11 @@ function ensureCheckout(cacheDir, ref) {
1097
1087
  // wiring is a separate fork-internals patch and is the next agent's
1098
1088
  // mission.
1099
1089
  //
1100
- // * The EMBED_LIBRARY=ON branch (used by iOS targets) is not yet patched.
1101
- // iOS builds compile a single concatenated .metal via .incbin and would
1102
- // require stripping the duplicate decls (block_qjl1_256, block_q4_polar,
1103
- // QK_QJL, QK_POLAR, QJL_RESIDUAL_BYTES already in ggml-common.h). That
1104
- // is a separate patch tracked in kernel-patches/metal-kernels.mjs's
1105
- // module comment.
1090
+ // * The EMBED_LIBRARY=ON branch (used by iOS targets) is also patched:
1091
+ // it compiles ggml-metal.metal + the milady standalones as separate
1092
+ // .air files, merges them into one default.metallib, and embeds those
1093
+ // compiled bytes. That avoids duplicate source declarations while
1094
+ // shipping the same five kernel symbols as the desktop sidecar.
1106
1095
  //
1107
1096
  // * Vulkan: the 8 standalone .comp files are staged into
1108
1097
  // ggml/src/ggml-vulkan/vulkan-shaders/ (picked up by file(GLOB)),
@@ -1111,10 +1100,10 @@ function ensureCheckout(cacheDir, ref) {
1111
1100
  // SPV blobs are linked into libggml-vulkan.so. Symbol audit (`nm
1112
1101
  // libggml-vulkan.so | grep milady_`) passes; op-level dispatch routing
1113
1102
  // for GGML_OP_ATTN_SCORE_QJL and the milady GGML_TYPE_* values is the
1114
- // remaining gap and is the next agent's mission. Until it lands, the
1115
- // ELIZA_DFLASH_ALLOW_INCOMPLETE_VULKAN=1 env var still applies the
1116
- // help-text probe in probeKernels() can't yet see qjl/polar via CLI
1117
- // flags because the runtime can't select them.
1103
+ // remaining gap and is the next agent's mission. Incomplete Vulkan
1104
+ // artifacts are now publish-blocking: probeKernels() marks symbol-only
1105
+ // QJL/Polar/Turbo paths false until op-level dispatch is numerically
1106
+ // verified.
1118
1107
  function applyForkPatches(cacheDir, backend, target, { dryRun = false } = {}) {
1119
1108
  if (backend === "metal") {
1120
1109
  patchMetalKernelsImpl(cacheDir, { dryRun });
@@ -1294,9 +1283,17 @@ function probeKernels(target, buildDir, outDir) {
1294
1283
  // Honesty gate: Metal/Vulkan standalone shaders can compile and be present
1295
1284
  // as symbols/pipelines while still being unreachable or semantically wrong
1296
1285
  // through generic llama.cpp ops. Do not let symbol presence satisfy
1297
- // AGENTS.md §3. Flip these back to true only after dedicated ATTN_SCORE /
1298
- // QJL projection / Polar get_rows dispatch is numerically verified.
1299
- if (backend === "metal" || backend === "vulkan") {
1286
+ // AGENTS.md §3. Flip these back to true only after dedicated dispatch is
1287
+ // wired. Metal currently has a dedicated GGML_OP_ATTN_SCORE_QJL bridge;
1288
+ // Turbo and Polar remain symbol-only.
1289
+ if (backend === "metal") {
1290
+ const shipped = probeMetalShippedKernelSymbols(buildDir, outDir).symbols;
1291
+ kernels.turbo3 = false;
1292
+ kernels.turbo4 = false;
1293
+ kernels.turbo3_tcq = false;
1294
+ kernels.qjl_full = Boolean(shipped.qjl_full);
1295
+ kernels.polarquant = false;
1296
+ } else if (backend === "vulkan") {
1300
1297
  kernels.turbo3 = false;
1301
1298
  kernels.turbo4 = false;
1302
1299
  kernels.turbo3_tcq = false;
@@ -1354,6 +1351,41 @@ function requiredKernelsMissing(target, kernels) {
1354
1351
  return required.filter((k) => !kernels[k]);
1355
1352
  }
1356
1353
 
1354
+ function probeMetalShippedKernelSymbols(buildDir, outDir) {
1355
+ const shipped = {
1356
+ turbo3: false,
1357
+ turbo4: false,
1358
+ turbo3_tcq: false,
1359
+ qjl_full: false,
1360
+ polarquant: false,
1361
+ };
1362
+ const metallibs = [
1363
+ ...collectFilesUnder(buildDir, /\.metallib$/),
1364
+ ...collectFilesUnder(outDir, /\.metallib$/),
1365
+ ];
1366
+ for (const metallibPath of metallibs) {
1367
+ const bytes = fs.readFileSync(metallibPath);
1368
+ const has = (sym) => bytes.indexOf(sym) !== -1;
1369
+ if (has("kernel_turbo3_dot")) shipped.turbo3 = true;
1370
+ if (has("kernel_turbo4_dot")) shipped.turbo4 = true;
1371
+ if (has("kernel_turbo3_tcq_dot")) shipped.turbo3_tcq = true;
1372
+ if (
1373
+ has("kernel_attn_score_qjl1_256") ||
1374
+ has("kernel_get_rows_qjl1_256") ||
1375
+ has("kernel_mul_mv_qjl1_256_f32")
1376
+ ) {
1377
+ shipped.qjl_full = true;
1378
+ }
1379
+ if (
1380
+ has("kernel_get_rows_q4_polar") ||
1381
+ has("kernel_mul_mv_q4_polar_f32")
1382
+ ) {
1383
+ shipped.polarquant = true;
1384
+ }
1385
+ }
1386
+ return { metallibs, symbols: shipped };
1387
+ }
1388
+
1357
1389
  function writeCapabilities({
1358
1390
  outDir,
1359
1391
  target,
@@ -1365,6 +1397,8 @@ function writeCapabilities({
1365
1397
  const { platform, arch, backend, fused } = parseTarget(target);
1366
1398
  const kernels = probeKernels(target, buildDir, outDir);
1367
1399
  const missing = requiredKernelsMissing(target, kernels);
1400
+ const shippedKernels =
1401
+ backend === "metal" ? probeMetalShippedKernelSymbols(buildDir, outDir) : null;
1368
1402
  const capabilities = {
1369
1403
  target,
1370
1404
  platform,
@@ -1375,6 +1409,7 @@ function writeCapabilities({
1375
1409
  fork: "milady-ai/llama.cpp",
1376
1410
  forkCommit,
1377
1411
  kernels,
1412
+ shippedKernels,
1378
1413
  binaries,
1379
1414
  omnivoice,
1380
1415
  };