@directive-run/claude-plugin 1.19.1 → 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.
|
|
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.
|
|
55
|
+
"@directive-run/knowledge": "1.19.3"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "tsx scripts/build-skills.ts && tsup",
|
|
@@ -189,11 +189,13 @@ publish closure (if any) receives lifecycle events.
|
|
|
189
189
|
> holder from two places (e.g., a Worker that mounts one Directive
|
|
190
190
|
> system per tenant DO), a module-level `let publishRef` would be
|
|
191
191
|
> SHARED — the second tenant's `attach` would silently overwrite the
|
|
192
|
-
> first tenant's pipe. **Always
|
|
193
|
-
> factory function** so
|
|
194
|
-
>
|
|
195
|
-
>
|
|
196
|
-
>
|
|
192
|
+
> first tenant's pipe. **Always construct BOTH the adapter AND the
|
|
193
|
+
> module inside the same factory function** so the adapter's
|
|
194
|
+
> `events` callbacks close over the same factory-local `publishRef`
|
|
195
|
+
> as the source's `attach`. Sharing an adapter across factory calls
|
|
196
|
+
> re-introduces the cross-contamination because the adapter's
|
|
197
|
+
> `events.onConnect` was bound at construction time to whichever
|
|
198
|
+
> factory's `publishRef` was in scope first.
|
|
197
199
|
|
|
198
200
|
```ts
|
|
199
201
|
import { createSystem, createModule, t } from "@directive-run/core";
|
|
@@ -364,7 +366,13 @@ that should accompany this plugin in regulated deployments.
|
|
|
364
366
|
## Adapter packages
|
|
365
367
|
|
|
366
368
|
The canonical bridges live in **`@directive-run/sources`** as subpath
|
|
367
|
-
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
|
+
```
|
|
368
376
|
|
|
369
377
|
| Subpath | Source factory | Wraps |
|
|
370
378
|
|---|---|---|
|
|
@@ -375,7 +383,57 @@ exports. One install, optional peerDependencies per vendor.
|
|
|
375
383
|
| `@directive-run/sources/sentry` | `sourceFromSentryHook()` | (future RFC) Sentry production-error stream |
|
|
376
384
|
|
|
377
385
|
Install only the umbrella; the vendor peerDeps are optional and pull in
|
|
378
|
-
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.
|
|
379
437
|
|
|
380
438
|
---
|
|
381
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
|