@holokai/neuron-sdk 0.5.1 → 0.7.0

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/README.md CHANGED
@@ -21,7 +21,7 @@ pnpm add @holokai/neuron-sdk
21
21
 
22
22
  Runs in **Node 20+** (uses the global `fetch` and `ReadableStream`) and **modern browsers** (Chromium, Firefox, Safari latest). The package has no `node:*` imports — Holokai Desktop / Electron and the in-browser neuron-tester both consume the same SDK build.
23
23
 
24
- > **Gateway compatibility (protocol 1.1.0, HOL-2926).** Since protocol
24
+ > **Gateway compatibility (protocol 1.2.0; HOL-2926, HOL-4326).** Since protocol
25
25
  > 1.1.0 the SDK advertises each capability's contract (`description` +
26
26
  > input/output JSON Schemas) on the register frame. The gateway validates
27
27
  > register frames **strictly**, so a gateway older than HOL-2926 rejects
@@ -114,6 +114,29 @@ Examples:
114
114
  - `desktop/mcp/drive.read` — user's Google Drive, scoped to that user
115
115
  - `org/01HBQ8ZK/custom-tool` — org-specific capability
116
116
 
117
+ ### Composing capabilities (`composes`)
118
+
119
+ A capability namespaced `skill/<name>/<id>` may declare `composes`: the other
120
+ capability types it draws on to carry out one recurring job. Set it on
121
+ `defineCapability(...)`, on `neuron.handle(def, opts, fn)`'s `opts`, or on the
122
+ legacy registration object — it is only valid on a `skill/*` type, must name
123
+ 1 to 64 distinct capability types, and none of them may itself be `skill/*`.
124
+
125
+ ```ts
126
+ const weeklyReport = defineCapability({
127
+ type: 'skill/weekly-report/01',
128
+ description: 'Pulls last week\'s issues and writes a summary.',
129
+ input: z.object({}),
130
+ output: z.object({ summary: z.string() }),
131
+ composes: ['mcp/linear/list_issues', 'holo/37bfe871'],
132
+ });
133
+ ```
134
+
135
+ `composes` is carried on the register frame's capability object (gateway
136
+ protocol 1.2.0, HOL-4326) — **the gateway must be running 1.2.0 or later
137
+ before deploying an SDK version that sends it**, or the gateway rejects the
138
+ register frame outright.
139
+
117
140
  ### Namespace authorization
118
141
 
119
142
  The gateway enforces which identities may advertise which namespaces:
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { J as JsonSchema, C as Capability, a as CapabilityHandleOptions, S as ServerToNeuronFrame, N as NeuronLogger, T as Transport, A as AuthCallback, H as Handler, b as CapabilityRegistration, c as TransportCallbacks, d as ClientFrame, P as PostPath } from './transport-Ckcwj-Do.mjs';
2
- export { e as HandlerContext, f as HeartbeatInFlightEntry, L as LeasePayload, g as TaskPayload } from './transport-Ckcwj-Do.mjs';
1
+ import { J as JsonSchema, C as Capability, a as CapabilityHandleOptions, S as ServerToNeuronFrame, N as NeuronLogger, T as Transport, A as AuthCallback, H as Handler, b as CapabilityRegistration, c as TransportCallbacks, d as ClientFrame, P as PostPath } from './transport-BhFVHvR7.mjs';
2
+ export { e as HandlerContext, f as HandlerFailureOptions, g as HeartbeatInFlightEntry, L as LeasePayload, h as TaskPayload } from './transport-BhFVHvR7.mjs';
3
3
  import { EventEmitter } from 'eventemitter3';
4
4
  import { z } from 'zod';
5
5
  import { ErrorObject } from 'ajv';
@@ -48,6 +48,12 @@ interface CapabilityDefInit<TIn extends z.ZodTypeAny, TOut extends z.ZodTypeAny>
48
48
  * Bounded `[1_000, 3_600_000]` (1s..1h) by `CapabilitySchema`.
49
49
  */
50
50
  leaseTtlMs?: number;
51
+ /**
52
+ * Capability types this `skill/*` capability composes (HOL-4326). Only
53
+ * valid when `type` is namespaced `skill/*` — enforced by
54
+ * `CapabilitySchema` on the gateway.
55
+ */
56
+ composes?: readonly string[];
51
57
  }
52
58
  /**
53
59
  * A typed capability definition. The phantom `_input` / `_output` fields
@@ -68,6 +74,8 @@ interface CapabilityDef<TIn = unknown, TOut = unknown> {
68
74
  readonly concurrency?: number;
69
75
  /** See {@link CapabilityDefInit.leaseTtlMs}. */
70
76
  readonly leaseTtlMs?: number;
77
+ /** See {@link CapabilityDefInit.composes}. */
78
+ readonly composes?: readonly string[];
71
79
  /** Original zod schemas — kept for runtime validation in handlers if needed. */
72
80
  readonly _zod: {
73
81
  input: z.ZodType<TIn>;
@@ -418,4 +426,18 @@ declare class SchemaValidator {
418
426
  validate(schema: JsonSchema | undefined, data: unknown): ErrorObject[] | null;
419
427
  }
420
428
 
421
- export { AuthCallback, Capability, type CapabilityDef, type CapabilityDefInit, CapabilityHandleOptions, type CapabilityPlanShape, CapabilityRegistration, Handler, HttpTransport, type HttpTransportOptions, Neuron, type NeuronEvents, NeuronLogger, type NeuronOptions, SSE_EVENT_NAMES, SchemaValidator, ServerToNeuronFrame, ShutdownError, Transport, TransportCallbacks, TransportError, WorkerPool, defineCapability, toPlanShape, toRegistrationCapability };
429
+ /**
430
+ * The exception `ctx.fail()` throws to short-circuit the handler with a
431
+ * nack. Caught by the runner; consumers should not catch it. Exported so a
432
+ * wrapper around a handler can raise one for a refusal the handler answered
433
+ * as data.
434
+ */
435
+ declare class HandlerFailure extends Error {
436
+ readonly reason: string;
437
+ readonly code?: string | undefined;
438
+ readonly details?: unknown | undefined;
439
+ readonly kind: 'terminal' | 'retryable';
440
+ constructor(reason: string, code?: string | undefined, details?: unknown | undefined, kind?: 'terminal' | 'retryable');
441
+ }
442
+
443
+ export { AuthCallback, Capability, type CapabilityDef, type CapabilityDefInit, CapabilityHandleOptions, type CapabilityPlanShape, CapabilityRegistration, Handler, HandlerFailure, HttpTransport, type HttpTransportOptions, Neuron, type NeuronEvents, NeuronLogger, type NeuronOptions, SSE_EVENT_NAMES, SchemaValidator, ServerToNeuronFrame, ShutdownError, Transport, TransportCallbacks, TransportError, WorkerPool, defineCapability, toPlanShape, toRegistrationCapability };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { J as JsonSchema, C as Capability, a as CapabilityHandleOptions, S as ServerToNeuronFrame, N as NeuronLogger, T as Transport, A as AuthCallback, H as Handler, b as CapabilityRegistration, c as TransportCallbacks, d as ClientFrame, P as PostPath } from './transport-Ckcwj-Do.js';
2
- export { e as HandlerContext, f as HeartbeatInFlightEntry, L as LeasePayload, g as TaskPayload } from './transport-Ckcwj-Do.js';
1
+ import { J as JsonSchema, C as Capability, a as CapabilityHandleOptions, S as ServerToNeuronFrame, N as NeuronLogger, T as Transport, A as AuthCallback, H as Handler, b as CapabilityRegistration, c as TransportCallbacks, d as ClientFrame, P as PostPath } from './transport-BhFVHvR7.js';
2
+ export { e as HandlerContext, f as HandlerFailureOptions, g as HeartbeatInFlightEntry, L as LeasePayload, h as TaskPayload } from './transport-BhFVHvR7.js';
3
3
  import { EventEmitter } from 'eventemitter3';
4
4
  import { z } from 'zod';
5
5
  import { ErrorObject } from 'ajv';
@@ -48,6 +48,12 @@ interface CapabilityDefInit<TIn extends z.ZodTypeAny, TOut extends z.ZodTypeAny>
48
48
  * Bounded `[1_000, 3_600_000]` (1s..1h) by `CapabilitySchema`.
49
49
  */
50
50
  leaseTtlMs?: number;
51
+ /**
52
+ * Capability types this `skill/*` capability composes (HOL-4326). Only
53
+ * valid when `type` is namespaced `skill/*` — enforced by
54
+ * `CapabilitySchema` on the gateway.
55
+ */
56
+ composes?: readonly string[];
51
57
  }
52
58
  /**
53
59
  * A typed capability definition. The phantom `_input` / `_output` fields
@@ -68,6 +74,8 @@ interface CapabilityDef<TIn = unknown, TOut = unknown> {
68
74
  readonly concurrency?: number;
69
75
  /** See {@link CapabilityDefInit.leaseTtlMs}. */
70
76
  readonly leaseTtlMs?: number;
77
+ /** See {@link CapabilityDefInit.composes}. */
78
+ readonly composes?: readonly string[];
71
79
  /** Original zod schemas — kept for runtime validation in handlers if needed. */
72
80
  readonly _zod: {
73
81
  input: z.ZodType<TIn>;
@@ -418,4 +426,18 @@ declare class SchemaValidator {
418
426
  validate(schema: JsonSchema | undefined, data: unknown): ErrorObject[] | null;
419
427
  }
420
428
 
421
- export { AuthCallback, Capability, type CapabilityDef, type CapabilityDefInit, CapabilityHandleOptions, type CapabilityPlanShape, CapabilityRegistration, Handler, HttpTransport, type HttpTransportOptions, Neuron, type NeuronEvents, NeuronLogger, type NeuronOptions, SSE_EVENT_NAMES, SchemaValidator, ServerToNeuronFrame, ShutdownError, Transport, TransportCallbacks, TransportError, WorkerPool, defineCapability, toPlanShape, toRegistrationCapability };
429
+ /**
430
+ * The exception `ctx.fail()` throws to short-circuit the handler with a
431
+ * nack. Caught by the runner; consumers should not catch it. Exported so a
432
+ * wrapper around a handler can raise one for a refusal the handler answered
433
+ * as data.
434
+ */
435
+ declare class HandlerFailure extends Error {
436
+ readonly reason: string;
437
+ readonly code?: string | undefined;
438
+ readonly details?: unknown | undefined;
439
+ readonly kind: 'terminal' | 'retryable';
440
+ constructor(reason: string, code?: string | undefined, details?: unknown | undefined, kind?: 'terminal' | 'retryable');
441
+ }
442
+
443
+ export { AuthCallback, Capability, type CapabilityDef, type CapabilityDefInit, CapabilityHandleOptions, type CapabilityPlanShape, CapabilityRegistration, Handler, HandlerFailure, HttpTransport, type HttpTransportOptions, Neuron, type NeuronEvents, NeuronLogger, type NeuronOptions, SSE_EVENT_NAMES, SchemaValidator, ServerToNeuronFrame, ShutdownError, Transport, TransportCallbacks, TransportError, WorkerPool, defineCapability, toPlanShape, toRegistrationCapability };
package/dist/index.js CHANGED
@@ -17,6 +17,11 @@ var CapabilityTypeSchema = zod.z.string().min(1).regex(
17
17
  "capability type must be namespaced: {owner}/{package}[/{name}][.{variant}]"
18
18
  );
19
19
  var UserIdSchema = zod.z.string().min(1);
20
+ var SKILL_CAPABILITY_PREFIX = "skill/";
21
+ function isSkillCapabilityType(type) {
22
+ return type.startsWith(SKILL_CAPABILITY_PREFIX);
23
+ }
24
+ var MAX_COMPOSED_CAPABILITIES = 64;
20
25
  var JsonSchemaSchema = zod.z.unknown();
21
26
  var CapabilityScopeSchema = zod.z.union([
22
27
  zod.z.literal("any"),
@@ -69,8 +74,29 @@ var CapabilitySchema = zod.z.object({
69
74
  * heartbeat budget should be the limiter, not a single
70
75
  * capability).
71
76
  */
72
- leaseTtlMs: zod.z.number().int().min(1e3).max(60 * 60 * 1e3).optional()
73
- }).strict();
77
+ leaseTtlMs: zod.z.number().int().min(1e3).max(60 * 60 * 1e3).optional(),
78
+ /**
79
+ * Capability types this `skill/*` capability composes (HOL-4326).
80
+ * Only valid on a `skill/*` type — enforced below, since a non-skill
81
+ * type carrying `composes` is a modeling error the gateway should
82
+ * reject rather than silently ignore.
83
+ */
84
+ composes: zod.z.array(CapabilityTypeSchema).min(1).max(MAX_COMPOSED_CAPABILITIES).refine(
85
+ (types) => new Set(types).size === types.length,
86
+ "composes must not contain duplicate capability types"
87
+ ).refine(
88
+ (types) => types.every((t) => !isSkillCapabilityType(t)),
89
+ "composes must not contain skill/* capability types"
90
+ ).optional()
91
+ }).strict().superRefine((cap, ctx) => {
92
+ if (cap.composes !== void 0 && !isSkillCapabilityType(cap.type)) {
93
+ ctx.addIssue({
94
+ code: zod.z.ZodIssueCode.custom,
95
+ message: "composes is only valid on a skill/* capability type",
96
+ path: ["composes"]
97
+ });
98
+ }
99
+ });
74
100
  var LeaseIdSchema = zod.z.string().min(1);
75
101
  var TaskPayloadSchema = zod.z.object({
76
102
  taskId: zod.z.string().min(1),
@@ -209,7 +235,7 @@ zod.z.discriminatedUnion("type", [
209
235
  ]);
210
236
 
211
237
  // ../neuron-protocol/src/transport.ts
212
- var PROTOCOL_VERSION = "1.1.0";
238
+ var PROTOCOL_VERSION = "1.2.0";
213
239
  var SSE_PATH = "/neuron/events";
214
240
  var SSE_EVENT = {
215
241
  registered: "registered",
@@ -235,6 +261,7 @@ function defineCapability(init) {
235
261
  scope: init.scope,
236
262
  concurrency: init.concurrency,
237
263
  leaseTtlMs: init.leaseTtlMs,
264
+ composes: init.composes,
238
265
  _zod: { input: init.input, output: init.output }
239
266
  };
240
267
  }
@@ -260,6 +287,7 @@ function toRegistrationCapability(def, opts) {
260
287
  );
261
288
  }
262
289
  const leaseTtlMs = opts?.leaseTtlMs ?? def.leaseTtlMs;
290
+ const composes = opts?.composes ?? def.composes;
263
291
  return {
264
292
  type: def.type,
265
293
  scope,
@@ -270,7 +298,8 @@ function toRegistrationCapability(def, opts) {
270
298
  description: opts?.description ?? def.description,
271
299
  inputSchema: def.inputSchema,
272
300
  outputSchema: def.outputSchema,
273
- ...typeof leaseTtlMs === "number" ? { leaseTtlMs } : {}
301
+ ...typeof leaseTtlMs === "number" ? { leaseTtlMs } : {},
302
+ ...composes !== void 0 ? { composes: [...composes] } : {}
274
303
  };
275
304
  }
276
305
 
@@ -550,13 +579,16 @@ var TransportError = class extends Error {
550
579
  };
551
580
 
552
581
  // src/lease-runner.ts
553
- var TerminalFailure = class extends Error {
554
- constructor(reason, code) {
582
+ var HandlerFailure = class extends Error {
583
+ constructor(reason, code, details, kind = "terminal") {
555
584
  super(reason);
556
585
  this.reason = reason;
557
586
  this.code = code;
558
- this.name = "TerminalFailure";
587
+ this.details = details;
588
+ this.name = "HandlerFailure";
589
+ this.kind = kind;
559
590
  }
591
+ kind;
560
592
  };
561
593
  var DEFAULT_PROGRESS_TIMEOUT_MS = 5e3;
562
594
  function validateInput(zod, validator, jsonSchema, data) {
@@ -564,10 +596,7 @@ function validateInput(zod, validator, jsonSchema, data) {
564
596
  const result = zod.safeParse(data);
565
597
  return result.success ? { ok: true, value: result.data } : { ok: false, errors: result.error.issues };
566
598
  }
567
- const errors = validator.validate(
568
- jsonSchema,
569
- data
570
- );
599
+ const errors = validator.validate(jsonSchema, data);
571
600
  return errors === null ? { ok: true, value: data } : { ok: false, errors };
572
601
  }
573
602
  async function runLease(opts) {
@@ -597,7 +626,13 @@ async function runLease(opts) {
597
626
  return;
598
627
  }
599
628
  const parsedInput = inputCheck.value;
600
- const reporter = makeProgressReporter(transport, lease.leaseId, logger, signal, progressTimeoutMs);
629
+ const reporter = makeProgressReporter(
630
+ transport,
631
+ lease.leaseId,
632
+ logger,
633
+ signal,
634
+ progressTimeoutMs
635
+ );
601
636
  const ctx = {
602
637
  task: lease.task,
603
638
  leaseId: lease.leaseId,
@@ -605,7 +640,7 @@ async function runLease(opts) {
605
640
  log: scopedLogger(logger, lease),
606
641
  progress: reporter.progress,
607
642
  fail: (reason, opts2) => {
608
- throw new TerminalFailure(reason, opts2?.code);
643
+ throw new HandlerFailure(reason, opts2?.code, opts2?.details, opts2?.kind);
609
644
  }
610
645
  };
611
646
  let output;
@@ -625,13 +660,16 @@ async function runLease(opts) {
625
660
  );
626
661
  return;
627
662
  }
628
- if (err instanceof TerminalFailure) {
663
+ if (err instanceof HandlerFailure) {
629
664
  await sendNack(
630
665
  transport,
631
666
  lease.leaseId,
632
- "terminal",
667
+ err.kind,
633
668
  err.reason,
634
- { code: err.code ?? "HANDLER_TERMINAL" },
669
+ {
670
+ code: err.code ?? (err.kind === "terminal" ? "HANDLER_TERMINAL" : "HANDLER_RETRYABLE"),
671
+ ...err.details !== void 0 ? { details: err.details } : {}
672
+ },
635
673
  logger
636
674
  );
637
675
  return;
@@ -979,7 +1017,8 @@ var Neuron = class extends eventemitter3.EventEmitter {
979
1017
  ...reg.description !== void 0 ? { description: reg.description } : {},
980
1018
  ...reg.inputSchema !== void 0 ? { inputSchema: reg.inputSchema } : {},
981
1019
  ...reg.outputSchema !== void 0 ? { outputSchema: reg.outputSchema } : {},
982
- ...typeof reg.leaseTtlMs === "number" ? { leaseTtlMs: reg.leaseTtlMs } : {}
1020
+ ...typeof reg.leaseTtlMs === "number" ? { leaseTtlMs: reg.leaseTtlMs } : {},
1021
+ ...reg.composes !== void 0 ? { composes: reg.composes } : {}
983
1022
  };
984
1023
  inputSchema = reg.inputSchema;
985
1024
  outputSchema = reg.outputSchema;
@@ -1221,6 +1260,7 @@ var Neuron = class extends eventemitter3.EventEmitter {
1221
1260
  };
1222
1261
  var SSE_EVENT_NAMES = SSE_EVENT;
1223
1262
 
1263
+ exports.HandlerFailure = HandlerFailure;
1224
1264
  exports.HttpTransport = HttpTransport;
1225
1265
  exports.Neuron = Neuron;
1226
1266
  exports.SSE_EVENT_NAMES = SSE_EVENT_NAMES;