diff options
| author | Adam Malczewski <[email protected]> | 2026-06-11 12:23:06 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-11 12:23:06 +0900 |
| commit | c2b4c05d91fa88b8d02c055a0e15c22abd8e21f3 (patch) | |
| tree | 3f7c2feddbe697a79abd952bb80ed0e01dac0a7a /packages/kernel/src/host | |
| parent | f6b45507210e04e9884256b0132900640de4334b (diff) | |
| download | dispatch-c2b4c05d91fa88b8d02c055a0e15c22abd8e21f3.tar.gz dispatch-c2b4c05d91fa88b8d02c055a0e15c22abd8e21f3.zip | |
feat(cache-warming): per-conversation prompt-cache warming + warm() service
Backend-driven warming targeting whatever provider a conversation uses (incl. the
external Claude provider-anthropic). Core engine + on/off + last-cache-% done;
interval-as-view-control pending a ui-contract NumberField (surface-system gap).
Mechanism:
- kernel: expose HostAPI.emit (typed bus event emit; counterpart of on)
- session-orchestrator: turnStarted/turnSettled event hooks (conversationId/cwd/model);
warm() service (cacheWarmHandle) reusing the real-turn assembly (byte-identical prefix,
provider-agnostic), refuses mid-turn, never persists/emits, returns Usage
- cache-warming (new ext): per-conversation timers (arm on settle, cancel on start,
in-flight invalidation), calls warm(), pct=round(clamp(cacheRead/input,0,1)*100),
persists {enabled,intervalMs} (default on/240s), registers a controls surface
- host-bin: register cache-warming; transport-http: HostAPI stub +emit (fan-out)
Honors old-code invariants. 760 vitest + 109 bun = 869 tests; tsc -b EXIT 0; biome clean.
Diffstat (limited to 'packages/kernel/src/host')
| -rw-r--r-- | packages/kernel/src/host/host.test.ts | 43 | ||||
| -rw-r--r-- | packages/kernel/src/host/host.ts | 3 |
2 files changed, 46 insertions, 0 deletions
diff --git a/packages/kernel/src/host/host.test.ts b/packages/kernel/src/host/host.test.ts index 669d093..0067091 100644 --- a/packages/kernel/src/host/host.test.ts +++ b/packages/kernel/src/host/host.test.ts @@ -617,6 +617,49 @@ describe("createHost", () => { expect(received).toEqual(["hello"]); }); + it("emit dispatches to handlers registered via on", async () => { + const hook = defineEventHook<string>("test/emit-dispatch"); + const received: string[] = []; + + const ext = createExtension("emit-ext", { + activate: (host) => { + host.on(hook, (payload) => { + received.push(payload); + }); + }, + }); + + const host = createHost([ext], deps); + await host.activate(); + + const api = host.getHostAPI(); + api.emit(hook, "world"); + expect(received).toEqual(["world"]); + }); + + it("emit isolates a throwing handler (does not propagate)", async () => { + const hook = defineEventHook<string>("test/emit-isolation"); + const received: string[] = []; + + const ext = createExtension("emit-isolation-ext", { + activate: (host) => { + host.on(hook, () => { + throw new Error("handler boom"); + }); + host.on(hook, (payload) => { + received.push(payload); + }); + }, + }); + + const host = createHost([ext], deps); + await host.activate(); + + const api = host.getHostAPI(); + expect(() => api.emit(hook, "safe")).not.toThrow(); + expect(received).toEqual(["safe"]); + }); + it("applyFilters threads a value through registered filters in order", async () => { const hook = defineFilter<string>("test/text-transform"); diff --git a/packages/kernel/src/host/host.ts b/packages/kernel/src/host/host.ts index a6396a9..2a262be 100644 --- a/packages/kernel/src/host/host.ts +++ b/packages/kernel/src/host/host.ts @@ -122,6 +122,9 @@ export function createHost(extensions: readonly Extension[], deps: HostDeps): Ho on<TPayload>(hook: EventHookDescriptor<TPayload>, handler: EventHandler<TPayload>) { return deps.bus.on(hook, handler); }, + emit<TPayload>(hook: EventHookDescriptor<TPayload>, payload: TPayload) { + deps.bus.emit(hook, payload); + }, addFilter<TValue>(hook: FilterDescriptor<TValue>, fn: FilterHandler<TValue>) { return deps.bus.addFilter(hook, fn); }, |
