@directive-run/claude-plugin 1.19.2 → 1.19.3

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": "@directive-run/claude-plugin",
3
- "version": "1.19.2",
3
+ "version": "1.19.3",
4
4
  "description": "Claude Code plugin for Directive — 12 skills covering modules, constraints, resolvers, derivations, AI orchestration, and adapters. Installable via Claude Code's plugin marketplace or consumable programmatically as an npm package.",
5
5
  "license": "(MIT OR Apache-2.0)",
6
6
  "author": "Jason Comes",
@@ -52,7 +52,7 @@
52
52
  "tsx": "^4.19.2",
53
53
  "typescript": "^5.7.2",
54
54
  "vitest": "^3.0.0",
55
- "@directive-run/knowledge": "1.19.2"
55
+ "@directive-run/knowledge": "1.19.3"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsx scripts/build-skills.ts && tsup",
@@ -366,7 +366,13 @@ that should accompany this plugin in regulated deployments.
366
366
  ## Adapter packages
367
367
 
368
368
  The canonical bridges live in **`@directive-run/sources`** as subpath
369
- exports. One install, optional peerDependencies per vendor.
369
+ exports. One install, optional peerDependencies per vendor:
370
+
371
+ ```bash
372
+ pnpm add @directive-run/sources # umbrella
373
+ pnpm add @directive-run/sources @supabase/supabase-js # + supabase
374
+ pnpm add @directive-run/sources @cloudflare/workers-types # + cloudflare (devDep)
375
+ ```
370
376
 
371
377
  | Subpath | Source factory | Wraps |
372
378
  |---|---|---|
@@ -377,7 +383,57 @@ exports. One install, optional peerDependencies per vendor.
377
383
  | `@directive-run/sources/sentry` | `sourceFromSentryHook()` | (future RFC) Sentry production-error stream |
378
384
 
379
385
  Install only the umbrella; the vendor peerDeps are optional and pull in
380
- only when the corresponding subpath is imported.
386
+ only when the corresponding subpath is imported. `package.json` marks
387
+ both `@supabase/supabase-js` and `@cloudflare/workers-types` as
388
+ optional peer dependencies — consumers using only one subpath get no
389
+ install-error nag for the other.
390
+
391
+ ---
392
+
393
+ ## Observability — pipe `source.*` events to OpenTelemetry
394
+
395
+ `attachSourcesToOtel(system, { tracer, serviceName })` bridges the
396
+ `system.observe()` source lifecycle (`source.attach` / `.publish` /
397
+ `.detach` / `.error`) into OTel spans. Pairs with `createOtelPlugin`
398
+ which exports the agent-side trace surface; the two together cover
399
+ the entire publish-to-prompt pipeline:
400
+
401
+ ```ts
402
+ import { createAgentOrchestrator, createOtelPlugin } from "@directive-run/ai";
403
+ import { attachSourcesToOtel } from "@directive-run/ai";
404
+ import { trace } from "@opentelemetry/api";
405
+
406
+ const tracer = trace.getTracer("agent-service", "1.0.0");
407
+
408
+ const orchestrator = createAgentOrchestrator({
409
+ runner: anthropic(/* ... */),
410
+ plugins: [createOtelPlugin({ tracer })],
411
+ });
412
+
413
+ // One long-lived span per (sourceId, moduleId); publishes land as span
414
+ // events on the parent attach span (cardinality-budgeted).
415
+ const unsub = attachSourcesToOtel(system, {
416
+ tracer,
417
+ serviceName: "agent-service",
418
+ // Optional: throttle high-frequency publishes (default 1 = every publish).
419
+ publishSampleRate: 1,
420
+ });
421
+
422
+ // On `system.destroy()` / `evict()`, call unsub to close any open spans.
423
+ ```
424
+
425
+ What you see in your trace backend:
426
+ - `directive.source.attached` — long-lived span; closes on `detach` with
427
+ OK status, or on `unsub()` with `directive.detached: true` attribute.
428
+ - Span events (`publish`) on the parent span with `source.event_name`
429
+ attribute and a wall-clock timestamp.
430
+ - `directive.source.error` spans with `phase: "attach" | "cleanup" |
431
+ "runtime"` and the truncated error message.
432
+
433
+ This complements `createOtelPlugin`'s agent-span coverage so an SRE
434
+ debugging "agent stopped mid-stream" can trace back through
435
+ `runStream` → `liveContext` interrupt → the watched fact → the source
436
+ publish that triggered the change — one trace tree, no manual joins.
381
437
 
382
438
  ---
383
439
 
@@ -194,6 +194,17 @@ const unsub = system.observe((event) => {
194
194
 
195
195
  `system.inspect().sources` lists the declared sources + `system.inspect().attachedSourceCount` reports how many are currently bound.
196
196
 
197
+ ### Pipe `source.*` to OpenTelemetry
198
+
199
+ For production observability, `@directive-run/ai` ships
200
+ `attachSourcesToOtel(system, { tracer, serviceName })` which bridges
201
+ the lifecycle into OTel spans (one long-lived span per
202
+ `(sourceId, moduleId)`; publishes as span events; errors as
203
+ `directive.source.error` spans tagged with `phase`). See
204
+ [`../ai/ai-sources.md` § Observability](../ai/ai-sources.md#observability--pipe-source-events-to-opentelemetry)
205
+ for the full recipe; nothing in core depends on OTel so the bridge
206
+ lives next to the AI orchestrator's own span integration.
207
+
197
208
  ## Error handling — runtime errors via `reportError`
198
209
 
199
210
  Per RFC 0008, `attach` receives an optional second argument: a
@@ -314,6 +325,74 @@ sources: {
314
325
  Inside a DO `alarm()` or `webSocketClose()` handler:
315
326
  `await system.evict(/* deadline */ Date.now() + 5000)`.
316
327
 
328
+ ## Runtime compatibility
329
+
330
+ The `source` primitive itself is runtime-agnostic — `attach`, `publish`,
331
+ `unsubscribe`, `onEvict`, and `coalesce` only depend on `Promise`,
332
+ `queueMicrotask`, and standard timers (all available in every modern
333
+ JS runtime). The matrix below covers the SHIPPED adapters in
334
+ `@directive-run/sources`; consumer-authored sources inherit each
335
+ runtime's standard guarantees.
336
+
337
+ | Runtime | Source primitive | `sourceFromSupabaseChannel` | `sourceFromDOAlarm` | `sourceFromWebSocketMessage` | Notes |
338
+ |---|---|---|---|---|---|
339
+ | **Cloudflare DO** | ✅ | ✅ (peerDep `@supabase/supabase-js` works in workerd) | ✅ default `onEvict` clears alarm | ✅ default `onEvict` closes socket 1001 | DO eviction recipe at top of this section; call `await system.evict(deadline)` from `alarm()` / `webSocketClose()`. |
340
+ | **Cloudflare Workers** | ✅ | ✅ | ✅ (needs DO `storage` handle) | ✅ (needs DO `WebSocket` handle) | Same recipes as DO; the 30s wall-clock budget applies to `cleanupAllAsync`. |
341
+ | **Bun** | ✅ | ✅ (Bun supports the `ws` transport supabase uses) | n/a (DO API) | n/a (DO API) | Sync `stop()` + async `stopAsync()` both work; Bun supports top-level `await`. |
342
+ | **Deno** | ✅ | ✅ via `npm:` specifier; needs `--allow-net` | n/a (DO API) | n/a (DO API) | Permissions: `--allow-net` for any transport; `--allow-env` for SUPABASE_URL / SUPABASE_KEY. |
343
+ | **Browser** | ✅ | ✅ | n/a (DO API) | partial — DOM `WebSocket` works with a thin adapter; the Cloudflare-typed helper is DO-specific | DOM `addEventListener` / `BroadcastChannel` / `visibilitychange` recipes inline above. |
344
+ | **Node** | ✅ | ✅ | n/a (DO API) | n/a (DO API) | Test target; vitest exercises every source path. |
345
+
346
+ The `@directive-run/sandbox` validator allowlists `@directive-run/sources`
347
+ and both subpaths, so the playground / `run_in_sandbox` MCP tool / docs
348
+ live runner all accept source-using snippets.
349
+
350
+ ### Polling — when a transport is request/response only
351
+
352
+ When the upstream system has no push channel (REST endpoint, a polled
353
+ status URL, a `setInterval`-driven heartbeat), declare a polling source.
354
+ The runtime owns mount/unmount, the `onEvict` hook lets you cancel any
355
+ pending fetch before hibernation, and the engine's batching keeps the
356
+ per-tick fact mutations cheap:
357
+
358
+ ```typescript
359
+ sources: {
360
+ status: {
361
+ attach: (publish, reportError) => {
362
+ const controller = new AbortController();
363
+ const tick = async () => {
364
+ try {
365
+ const res = await fetch(statusUrl, { signal: controller.signal });
366
+ const body = await res.json();
367
+ publish("STATUS_TICK", body);
368
+ } catch (err) {
369
+ // RFC 0008 — runtime errors go through reportError so they
370
+ // route to source.error with phase: "runtime"; the source
371
+ // stays attached.
372
+ if ((err as { name?: string }).name !== "AbortError") {
373
+ reportError?.(err);
374
+ }
375
+ }
376
+ };
377
+ const id = setInterval(tick, 5_000);
378
+ void tick(); // first poll, immediate.
379
+ return () => {
380
+ clearInterval(id);
381
+ controller.abort();
382
+ };
383
+ },
384
+ coalesce: "lastWriteWins", // only the most recent tick survives a flush
385
+ },
386
+ },
387
+ ```
388
+
389
+ Choose the `coalesce` strategy that matches the consumer:
390
+ - `"lastWriteWins"` — gauge / dashboard updates where stale ticks
391
+ don't matter.
392
+ - `"none"` — counters / time-series where every tick must land.
393
+ - `"all"` — currently an alias for `"none"`; reserved for future
394
+ bounded-buffer semantics (do not depend on it).
395
+
317
396
  ## Common Patterns
318
397
 
319
398
  ### Pattern: Source supplies inbound; resolver supplies outbound