hotmilk 0.1.9 → 0.1.10

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hotmilk",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "hotmilk — Pi package bundling gentle-pi, context-mode, graphify, subagents, and bundled extension toggles via hotmilk.json",
5
5
  "keywords": [
6
6
  "hotmilk",
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { CONTEXT_STACK_EXTENSION_IDS } from "./context-stack.ts";
3
+ import { bundledImportUrl } from "./resolve-bundled.ts";
3
4
  import { BUNDLED_EXTENSION_IDS, type BundledExtensionId } from "../config/hotmilk.ts";
4
5
 
5
6
  type ExtensionFactory = (pi: ExtensionAPI) => void | Promise<void>;
@@ -7,35 +8,40 @@ type ExtensionFactory = (pi: ExtensionAPI) => void | Promise<void>;
7
8
  /** Bundled deps may type against @mariozechner/pi-coding-agent; hotmilk uses @earendil-works. */
8
9
  type ExtensionModule = { default: unknown };
9
10
 
11
+ function loadBundled(relativePath: string): () => Promise<ExtensionModule> {
12
+ return () => import(bundledImportUrl(relativePath));
13
+ }
14
+
10
15
  /**
11
16
  * Load bundled extensions on demand so disabled toggles do not pay import cost.
12
17
  * Context stack registers sequentially; other enabled extensions load in parallel.
13
18
  */
14
19
  const BUNDLED_EXTENSION_LOADERS: Record<BundledExtensionId, () => Promise<ExtensionModule>> = {
15
- "skill-registry": () => import("../../node_modules/gentle-pi/extensions/skill-registry.ts"),
16
- "sdd-init": () => import("../../node_modules/gentle-pi/extensions/sdd-init.ts"),
17
- "gentle-ai": () => import("../../node_modules/gentle-pi/extensions/gentle-ai.ts"),
18
- // Same entry as upstream .pi/extensions/context-modebuild/adapters/pi/extension.js
19
- "context-mode": () => import("../../node_modules/context-mode/build/adapters/pi/extension.js"),
20
- "ask-user": () => import("pi-ask-user"),
21
- graphify: () => import("../../node_modules/graphify-pi/extensions/graphify.ts"),
22
- subagents: () => import("../../node_modules/pi-subagents/src/extension/index.ts"),
23
- goal: () => import("../../node_modules/pi-goal/.pi/extensions/pi-goal/index.ts"),
24
- docparser: () => import("../../node_modules/pi-docparser/extensions/docparser/index.ts"),
25
- obsidian: () => import("../../node_modules/@haispeed/pi-obsidian/extensions/obsidian-cli.ts"),
26
- "cursor-provider": () => import("../../node_modules/@netandreus/pi-cursor-provider/index.ts"),
27
- btw: () => import("../../node_modules/pi-btw/extensions/btw.ts"),
28
- simplify: () => import("../../node_modules/pi-simplify/dist/index.js"),
29
- "rtk-optimizer": () => import("../../node_modules/pi-rtk-optimizer/index.ts"),
30
- "mcp-adapter": () => import("../../node_modules/pi-mcp-adapter/index.ts"),
31
- "planning-with-files": () =>
32
- import("../../node_modules/@tomxprime/planning-with-files/extensions/planning-with-files/index.ts"),
33
- caveman: () => import("../../node_modules/pi-caveman/extensions/caveman.ts"),
34
- "red-green": () => import("../../node_modules/pi-red-green/dist/index.js"),
35
- "agent-dashboard": () =>
36
- import("../../node_modules/@blackbelt-technology/pi-agent-dashboard/packages/extension/src/bridge.ts"),
37
- "web-access": () => import("../../node_modules/pi-web-access/index.ts"),
38
- "pi-flows": () => import("../../node_modules/@blackbelt-technology/pi-flows/extensions/index.ts"),
20
+ "skill-registry": loadBundled("gentle-pi/extensions/skill-registry.ts"),
21
+ "sdd-init": loadBundled("gentle-pi/extensions/sdd-init.ts"),
22
+ "gentle-ai": loadBundled("gentle-pi/extensions/gentle-ai.ts"),
23
+ "context-mode": loadBundled("context-mode/build/adapters/pi/extension.js"),
24
+ "ask-user": loadBundled("pi-ask-user/index.ts"),
25
+ graphify: loadBundled("graphify-pi/extensions/graphify.ts"),
26
+ subagents: loadBundled("pi-subagents/src/extension/index.ts"),
27
+ goal: loadBundled("pi-goal/.pi/extensions/pi-goal/index.ts"),
28
+ docparser: loadBundled("pi-docparser/extensions/docparser/index.ts"),
29
+ obsidian: loadBundled("@haispeed/pi-obsidian/extensions/obsidian-cli.ts"),
30
+ "cursor-provider": loadBundled("@netandreus/pi-cursor-provider/index.ts"),
31
+ btw: loadBundled("pi-btw/extensions/btw.ts"),
32
+ simplify: loadBundled("pi-simplify/dist/index.js"),
33
+ "rtk-optimizer": loadBundled("pi-rtk-optimizer/index.ts"),
34
+ "mcp-adapter": loadBundled("pi-mcp-adapter/index.ts"),
35
+ "planning-with-files": loadBundled(
36
+ "@tomxprime/planning-with-files/extensions/planning-with-files/index.ts",
37
+ ),
38
+ caveman: loadBundled("pi-caveman/extensions/caveman.ts"),
39
+ "red-green": loadBundled("pi-red-green/dist/index.js"),
40
+ "agent-dashboard": loadBundled(
41
+ "@blackbelt-technology/pi-agent-dashboard/packages/extension/src/bridge.ts",
42
+ ),
43
+ "web-access": loadBundled("pi-web-access/index.ts"),
44
+ "pi-flows": loadBundled("@blackbelt-technology/pi-flows/extensions/index.ts"),
39
45
  };
40
46
 
41
47
  async function registerOne(pi: ExtensionAPI, id: BundledExtensionId): Promise<void> {
@@ -0,0 +1,55 @@
1
+ import { existsSync } from "node:fs";
2
+ import { basename, dirname, join } from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+
5
+ /** Split `pkg/subpath` — supports scoped packages (`@scope/name/...`). */
6
+ export function parseBundledModulePath(relativePath: string): { pkgName: string; subpath: string } {
7
+ if (relativePath.startsWith("@")) {
8
+ const parts = relativePath.split("/");
9
+ return { pkgName: `${parts[0]}/${parts[1]}`, subpath: parts.slice(2).join("/") };
10
+ }
11
+ const slash = relativePath.indexOf("/");
12
+ if (slash === -1) {
13
+ return { pkgName: relativePath, subpath: "index.ts" };
14
+ }
15
+ return { pkgName: relativePath.slice(0, slash), subpath: relativePath.slice(slash + 1) };
16
+ }
17
+
18
+ /**
19
+ * Resolve a bundled dependency file whether npm nested it under hotmilk or hoisted
20
+ * it next to hotmilk (e.g. `~/.pi/npm/node_modules/context-mode`).
21
+ */
22
+ export function resolveBundledModule(
23
+ relativePath: string,
24
+ fromModuleUrl = import.meta.url,
25
+ ): string {
26
+ const { pkgName, subpath } = parseBundledModulePath(relativePath);
27
+ let dir = dirname(fileURLToPath(fromModuleUrl));
28
+
29
+ while (true) {
30
+ const nested = join(dir, "node_modules", pkgName, subpath);
31
+ if (existsSync(nested)) {
32
+ return nested;
33
+ }
34
+
35
+ if (basename(dir) === "node_modules") {
36
+ const sibling = join(dir, pkgName, subpath);
37
+ if (existsSync(sibling)) {
38
+ return sibling;
39
+ }
40
+ }
41
+
42
+ const parent = dirname(dir);
43
+ if (parent === dir) {
44
+ break;
45
+ }
46
+ dir = parent;
47
+ }
48
+
49
+ throw new Error(`Cannot resolve bundled module "${relativePath}" from hotmilk`);
50
+ }
51
+
52
+ /** Dynamic import URL for a bundled extension entry (works with nested and hoisted installs). */
53
+ export function bundledImportUrl(relativePath: string): string {
54
+ return pathToFileURL(resolveBundledModule(relativePath)).href;
55
+ }
package/src/index.ts CHANGED
@@ -15,6 +15,9 @@ import { registerHotmilkSessionLogo } from "./ui/session-logo.ts";
15
15
  export default async function registerHotmilk(pi: ExtensionAPI): Promise<void> {
16
16
  const runtime = createHotmilkRuntime();
17
17
 
18
+ // Register before bundled imports so session_start handlers exist when bindExtensions emits.
19
+ registerHotmilkSessionLogo(pi);
20
+
18
21
  if (runtime.extensionToggles["agent-dashboard"]) {
19
22
  const { scheduleDashboardWarmStart } = await import("./bootstrap/dashboard.ts");
20
23
  scheduleDashboardWarmStart();
@@ -27,6 +30,5 @@ export default async function registerHotmilk(pi: ExtensionAPI): Promise<void> {
27
30
  registerDefaultsHandlers(pi, runtime.defaults);
28
31
  registerSessionHandlers(pi, runtime);
29
32
  registerInputCommands(pi);
30
- registerHotmilkSessionLogo(pi);
31
33
  pi.on("input", (event, ctx) => routeInputCommand(event.text, pi, ctx));
32
34
  }
@@ -56,7 +56,6 @@ function showHotmilkSessionLogo(ctx: ExtensionContext): void {
56
56
  },
57
57
  };
58
58
  });
59
- activeTui?.requestRender();
60
59
  }, LOGO_SHOW_DELAY_MS);
61
60
  }
62
61