@holokai/neuron-sdk 0.3.0 → 0.5.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 +28 -5
- package/dist/index.d.mts +21 -8
- package/dist/index.d.ts +21 -8
- package/dist/index.js +137 -44
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +137 -44
- package/dist/index.mjs.map +1 -1
- package/dist/ipc.d.mts +1 -1
- package/dist/ipc.d.ts +1 -1
- package/dist/{transport-QqkooS7K.d.mts → transport-Ckcwj-Do.d.mts} +95 -3
- package/dist/{transport-QqkooS7K.d.ts → transport-Ckcwj-Do.d.ts} +95 -3
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -10,6 +10,7 @@ var CapabilityTypeSchema = z.string().min(1).regex(
|
|
|
10
10
|
"capability type must be namespaced: {owner}/{package}[/{name}][.{variant}]"
|
|
11
11
|
);
|
|
12
12
|
var UserIdSchema = z.string().min(1);
|
|
13
|
+
var JsonSchemaSchema = z.unknown();
|
|
13
14
|
var CapabilityScopeSchema = z.union([
|
|
14
15
|
z.literal("any"),
|
|
15
16
|
z.object({ onBehalfOf: UserIdSchema }).strict()
|
|
@@ -18,6 +19,36 @@ var CapabilitySchema = z.object({
|
|
|
18
19
|
type: CapabilityTypeSchema,
|
|
19
20
|
scope: CapabilityScopeSchema,
|
|
20
21
|
concurrency: z.number().int().positive(),
|
|
22
|
+
/**
|
|
23
|
+
* Plain-language contract advertisement (HOL-2926). All three fields
|
|
24
|
+
* are optional and additive: older neurons keep sending
|
|
25
|
+
* `{ type, scope, concurrency }` and the gateway accepts both shapes.
|
|
26
|
+
* When present they flow (fire-and-forget) to Moku's
|
|
27
|
+
* `capability_catalog` so registry-sourced capabilities render with a
|
|
28
|
+
* real contract in the supervisor catalog instead of type-only stubs.
|
|
29
|
+
*
|
|
30
|
+
* Omit rather than null — the fields are `.optional()`, not
|
|
31
|
+
* `.nullable()`, and a `null` description fails validation.
|
|
32
|
+
*/
|
|
33
|
+
description: z.string().optional(),
|
|
34
|
+
/**
|
|
35
|
+
* JSON Schema for the capability's input payload — opaque on the wire,
|
|
36
|
+
* but `null` is rejected: the reconcile push to Moku is last-writer-wins
|
|
37
|
+
* per present key, so a null here could wipe a stored schema.
|
|
38
|
+
* (`description` already rejects null via `z.string()`.)
|
|
39
|
+
*/
|
|
40
|
+
// The `: boolean` return annotations stop TS (5.5+) from inferring the
|
|
41
|
+
// lambdas as type predicates, which would narrow the field from
|
|
42
|
+
// `unknown` to `{}` and ripple into every consumer.
|
|
43
|
+
inputSchema: JsonSchemaSchema.refine(
|
|
44
|
+
(v) => v !== null,
|
|
45
|
+
"omit inputSchema instead of sending null"
|
|
46
|
+
).optional(),
|
|
47
|
+
/** JSON Schema for the capability's output payload — same rules as inputSchema. */
|
|
48
|
+
outputSchema: JsonSchemaSchema.refine(
|
|
49
|
+
(v) => v !== null,
|
|
50
|
+
"omit outputSchema instead of sending null"
|
|
51
|
+
).optional(),
|
|
21
52
|
/**
|
|
22
53
|
* Per-capability lease TTL override in milliseconds (HOL-356).
|
|
23
54
|
* The gateway uses this when minting a lease's `expiresAt`; absent
|
|
@@ -33,7 +64,6 @@ var CapabilitySchema = z.object({
|
|
|
33
64
|
*/
|
|
34
65
|
leaseTtlMs: z.number().int().min(1e3).max(60 * 60 * 1e3).optional()
|
|
35
66
|
}).strict();
|
|
36
|
-
var JsonSchemaSchema = z.unknown();
|
|
37
67
|
var LeaseIdSchema = z.string().min(1);
|
|
38
68
|
var TaskPayloadSchema = z.object({
|
|
39
69
|
taskId: z.string().min(1),
|
|
@@ -55,9 +85,10 @@ var NeuronIdSchema = z.string().min(1);
|
|
|
55
85
|
var RegisterFrameSchema = z.object({
|
|
56
86
|
type: z.literal("register"),
|
|
57
87
|
neuronId: NeuronIdSchema,
|
|
58
|
-
// Free-form by design — the gateway
|
|
59
|
-
//
|
|
60
|
-
//
|
|
88
|
+
// Free-form by design — the gateway records this verbatim and performs
|
|
89
|
+
// no version negotiation; compatibility comes from additive-only frame
|
|
90
|
+
// evolution (see PROTOCOL_VERSION in transport.ts). Do not pin this to
|
|
91
|
+
// the exported constant.
|
|
61
92
|
protocolVersion: z.string().min(1),
|
|
62
93
|
capabilities: z.array(CapabilitySchema).min(1).refine(
|
|
63
94
|
(caps) => new Set(caps.map((c) => c.type)).size === caps.length,
|
|
@@ -171,7 +202,7 @@ z.discriminatedUnion("type", [
|
|
|
171
202
|
]);
|
|
172
203
|
|
|
173
204
|
// ../neuron-protocol/src/transport.ts
|
|
174
|
-
var PROTOCOL_VERSION = "1.
|
|
205
|
+
var PROTOCOL_VERSION = "1.1.0";
|
|
175
206
|
var SSE_PATH = "/neuron/events";
|
|
176
207
|
var SSE_EVENT = {
|
|
177
208
|
registered: "registered",
|
|
@@ -226,6 +257,12 @@ function toRegistrationCapability(def, opts) {
|
|
|
226
257
|
type: def.type,
|
|
227
258
|
scope,
|
|
228
259
|
concurrency,
|
|
260
|
+
// Contract advertisement (HOL-2926). `description` is required on the
|
|
261
|
+
// def, so it is always present here; `opts.description` lets a
|
|
262
|
+
// deployment reword it without redefining the capability.
|
|
263
|
+
description: opts?.description ?? def.description,
|
|
264
|
+
inputSchema: def.inputSchema,
|
|
265
|
+
outputSchema: def.outputSchema,
|
|
229
266
|
...typeof leaseTtlMs === "number" ? { leaseTtlMs } : {}
|
|
230
267
|
};
|
|
231
268
|
}
|
|
@@ -245,7 +282,6 @@ var HeartbeatLoop = class {
|
|
|
245
282
|
this.timer = setInterval(() => {
|
|
246
283
|
void this.tick();
|
|
247
284
|
}, this.intervalMs);
|
|
248
|
-
if (typeof this.timer.unref === "function") this.timer.unref();
|
|
249
285
|
}
|
|
250
286
|
stop() {
|
|
251
287
|
if (this.timer) {
|
|
@@ -496,9 +532,6 @@ var HttpTransport = class {
|
|
|
496
532
|
this.reconnectTimer = null;
|
|
497
533
|
void this.openStream();
|
|
498
534
|
}, delay);
|
|
499
|
-
if (typeof this.reconnectTimer === "object" && this.reconnectTimer !== null && "unref" in this.reconnectTimer && typeof this.reconnectTimer.unref === "function") {
|
|
500
|
-
this.reconnectTimer.unref();
|
|
501
|
-
}
|
|
502
535
|
}
|
|
503
536
|
};
|
|
504
537
|
var TransportError = class extends Error {
|
|
@@ -518,6 +551,7 @@ var TerminalFailure = class extends Error {
|
|
|
518
551
|
this.name = "TerminalFailure";
|
|
519
552
|
}
|
|
520
553
|
};
|
|
554
|
+
var DEFAULT_PROGRESS_TIMEOUT_MS = 5e3;
|
|
521
555
|
function validateInput(zod, validator, jsonSchema, data) {
|
|
522
556
|
if (zod) {
|
|
523
557
|
const result = zod.safeParse(data);
|
|
@@ -530,8 +564,20 @@ function validateInput(zod, validator, jsonSchema, data) {
|
|
|
530
564
|
return errors === null ? { ok: true, value: data } : { ok: false, errors };
|
|
531
565
|
}
|
|
532
566
|
async function runLease(opts) {
|
|
533
|
-
const {
|
|
534
|
-
|
|
567
|
+
const {
|
|
568
|
+
lease,
|
|
569
|
+
handler,
|
|
570
|
+
zodInput,
|
|
571
|
+
zodOutput,
|
|
572
|
+
inputSchema,
|
|
573
|
+
outputSchema,
|
|
574
|
+
transport,
|
|
575
|
+
validator,
|
|
576
|
+
logger,
|
|
577
|
+
signal,
|
|
578
|
+
progressTimeoutMs = DEFAULT_PROGRESS_TIMEOUT_MS
|
|
579
|
+
} = opts;
|
|
580
|
+
const inputCheck = validateInput(zodInput, validator, inputSchema, lease.task.input);
|
|
535
581
|
if (!inputCheck.ok) {
|
|
536
582
|
await sendNack(
|
|
537
583
|
transport,
|
|
@@ -544,13 +590,13 @@ async function runLease(opts) {
|
|
|
544
590
|
return;
|
|
545
591
|
}
|
|
546
592
|
const parsedInput = inputCheck.value;
|
|
547
|
-
const
|
|
593
|
+
const reporter = makeProgressReporter(transport, lease.leaseId, logger, signal, progressTimeoutMs);
|
|
548
594
|
const ctx = {
|
|
549
595
|
task: lease.task,
|
|
550
596
|
leaseId: lease.leaseId,
|
|
551
597
|
signal,
|
|
552
598
|
log: scopedLogger(logger, lease),
|
|
553
|
-
progress:
|
|
599
|
+
progress: reporter.progress,
|
|
554
600
|
fail: (reason, opts2) => {
|
|
555
601
|
throw new TerminalFailure(reason, opts2?.code);
|
|
556
602
|
}
|
|
@@ -559,7 +605,9 @@ async function runLease(opts) {
|
|
|
559
605
|
try {
|
|
560
606
|
output = await handler(parsedInput, ctx);
|
|
561
607
|
} catch (err) {
|
|
562
|
-
|
|
608
|
+
const cancelled = signal.aborted || err.name === "AbortError";
|
|
609
|
+
if (!cancelled) await reporter.drained();
|
|
610
|
+
if (cancelled) {
|
|
563
611
|
await sendNack(
|
|
564
612
|
transport,
|
|
565
613
|
lease.leaseId,
|
|
@@ -593,7 +641,8 @@ async function runLease(opts) {
|
|
|
593
641
|
);
|
|
594
642
|
return;
|
|
595
643
|
}
|
|
596
|
-
|
|
644
|
+
await reporter.drained();
|
|
645
|
+
const outputCheck = validateInput(zodOutput, validator, outputSchema, output);
|
|
597
646
|
if (!outputCheck.ok) {
|
|
598
647
|
await sendNack(
|
|
599
648
|
transport,
|
|
@@ -634,20 +683,37 @@ async function sendNack(transport, leaseId, kind, reason, extras = {}, logger) {
|
|
|
634
683
|
);
|
|
635
684
|
});
|
|
636
685
|
}
|
|
637
|
-
function
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
686
|
+
function makeProgressReporter(transport, leaseId, logger, signal, timeoutMs) {
|
|
687
|
+
let tail = Promise.resolve();
|
|
688
|
+
return {
|
|
689
|
+
progress: (update) => {
|
|
690
|
+
const frame = {
|
|
691
|
+
type: "progress",
|
|
692
|
+
leaseId,
|
|
693
|
+
percent: update.percent,
|
|
694
|
+
message: update.message,
|
|
695
|
+
data: update.data,
|
|
696
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
697
|
+
};
|
|
698
|
+
tail = tail.then(async () => {
|
|
699
|
+
if (signal.aborted) return;
|
|
700
|
+
try {
|
|
701
|
+
await withTimeout(transport.post(POST_PATH.progress, frame), timeoutMs);
|
|
702
|
+
} catch (err) {
|
|
703
|
+
logger.warn({ err, leaseId }, "progress POST failed; continuing");
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
},
|
|
707
|
+
drained: () => tail
|
|
649
708
|
};
|
|
650
709
|
}
|
|
710
|
+
function withTimeout(work, ms) {
|
|
711
|
+
let timer;
|
|
712
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
713
|
+
timer = setTimeout(() => reject(new Error(`timed out after ${ms}ms`)), ms);
|
|
714
|
+
});
|
|
715
|
+
return Promise.race([work, timeout]).finally(() => clearTimeout(timer));
|
|
716
|
+
}
|
|
651
717
|
function scopedLogger(logger, lease) {
|
|
652
718
|
const scope = { leaseId: lease.leaseId, taskId: lease.task.taskId };
|
|
653
719
|
return {
|
|
@@ -660,32 +726,39 @@ function scopedLogger(logger, lease) {
|
|
|
660
726
|
var Ajv = AjvDefault.default ?? AjvDefault;
|
|
661
727
|
var addFormats = addFormatsDefault.default ?? addFormatsDefault;
|
|
662
728
|
var SchemaValidator = class {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
constructor() {
|
|
729
|
+
constructor(onCompileError) {
|
|
730
|
+
this.onCompileError = onCompileError;
|
|
666
731
|
this.ajv = new Ajv({ allErrors: true, strict: false });
|
|
667
732
|
addFormats(this.ajv);
|
|
668
733
|
}
|
|
734
|
+
ajv;
|
|
735
|
+
cache = /* @__PURE__ */ new WeakMap();
|
|
736
|
+
// Schemas whose compilation threw. Cannot-compile is an environment or
|
|
737
|
+
// schema-authoring problem, not evidence about the data — so validate()
|
|
738
|
+
// FAILS OPEN for these (returns null) instead of reporting a mismatch.
|
|
739
|
+
// Nacking on compile failure turned every lease into an
|
|
740
|
+
// INPUT_SCHEMA_MISMATCH retry loop on runtimes where ajv's codegen is
|
|
741
|
+
// unavailable (Electron renderers with CSP `script-src 'self'` block
|
|
742
|
+
// `new Function`) — HOL-2937.
|
|
743
|
+
uncompilable = /* @__PURE__ */ new WeakSet();
|
|
669
744
|
/**
|
|
670
|
-
* Returns null if the value matches the schema
|
|
671
|
-
*
|
|
745
|
+
* Returns null if the value matches the schema, no schema was given, or
|
|
746
|
+
* the schema cannot be compiled in this runtime (fail-open — the gateway
|
|
747
|
+
* remains the enforcement point); otherwise an array of structured errors.
|
|
672
748
|
*/
|
|
673
749
|
validate(schema, data) {
|
|
674
750
|
if (!schema || typeof schema !== "object") return null;
|
|
751
|
+
if (this.uncompilable.has(schema)) return null;
|
|
675
752
|
let validator = this.cache.get(schema);
|
|
676
753
|
if (!validator) {
|
|
677
754
|
try {
|
|
678
755
|
validator = this.ajv.compile(schema);
|
|
679
756
|
} catch (err) {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
params: {},
|
|
686
|
-
message: `schema failed to compile: ${err instanceof Error ? err.message : String(err)}`
|
|
687
|
-
}
|
|
688
|
-
];
|
|
757
|
+
this.uncompilable.add(schema);
|
|
758
|
+
this.onCompileError?.(
|
|
759
|
+
`schema failed to compile \u2014 skipping SDK-side validation for this schema: ${err instanceof Error ? err.message : String(err)}`
|
|
760
|
+
);
|
|
761
|
+
return null;
|
|
689
762
|
}
|
|
690
763
|
this.cache.set(schema, validator);
|
|
691
764
|
}
|
|
@@ -818,7 +891,9 @@ var Neuron = class extends EventEmitter {
|
|
|
818
891
|
super();
|
|
819
892
|
this.logger = opts.logger ?? noopLogger;
|
|
820
893
|
this.neuronId = opts.neuronId;
|
|
821
|
-
this.validator = new SchemaValidator(
|
|
894
|
+
this.validator = new SchemaValidator(
|
|
895
|
+
(message) => this.logger.warn({ neuronId: this.neuronId }, message)
|
|
896
|
+
);
|
|
822
897
|
this.transport = isWithTransport(opts) ? opts.transport : new HttpTransport({
|
|
823
898
|
gatewayUrl: opts.gatewayUrl,
|
|
824
899
|
neuronId: opts.neuronId,
|
|
@@ -890,7 +965,15 @@ var Neuron = class extends EventEmitter {
|
|
|
890
965
|
);
|
|
891
966
|
}
|
|
892
967
|
handler = optsOrHandler;
|
|
893
|
-
capability = {
|
|
968
|
+
capability = {
|
|
969
|
+
type: reg.type,
|
|
970
|
+
scope: reg.scope,
|
|
971
|
+
concurrency: reg.concurrency,
|
|
972
|
+
...reg.description !== void 0 ? { description: reg.description } : {},
|
|
973
|
+
...reg.inputSchema !== void 0 ? { inputSchema: reg.inputSchema } : {},
|
|
974
|
+
...reg.outputSchema !== void 0 ? { outputSchema: reg.outputSchema } : {},
|
|
975
|
+
...typeof reg.leaseTtlMs === "number" ? { leaseTtlMs: reg.leaseTtlMs } : {}
|
|
976
|
+
};
|
|
894
977
|
inputSchema = reg.inputSchema;
|
|
895
978
|
outputSchema = reg.outputSchema;
|
|
896
979
|
}
|
|
@@ -1001,7 +1084,15 @@ var Neuron = class extends EventEmitter {
|
|
|
1001
1084
|
this.hasOpenedOnce = true;
|
|
1002
1085
|
this.heartbeat.start();
|
|
1003
1086
|
} catch (err) {
|
|
1004
|
-
|
|
1087
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
1088
|
+
this.logger.error(
|
|
1089
|
+
{ err: cause },
|
|
1090
|
+
"register POST rejected; SSE is open but the neuron is NOT registered and will receive no leases"
|
|
1091
|
+
);
|
|
1092
|
+
this.emit(
|
|
1093
|
+
"error",
|
|
1094
|
+
new Error(`registration rejected by gateway: ${cause.message}`, { cause })
|
|
1095
|
+
);
|
|
1005
1096
|
}
|
|
1006
1097
|
}
|
|
1007
1098
|
handleConnectionClose(reason) {
|
|
@@ -1078,6 +1169,8 @@ var Neuron = class extends EventEmitter {
|
|
|
1078
1169
|
handler: handler.handler,
|
|
1079
1170
|
zodInput: handler.zodInput,
|
|
1080
1171
|
zodOutput: handler.zodOutput,
|
|
1172
|
+
inputSchema: handler.inputSchema,
|
|
1173
|
+
outputSchema: handler.outputSchema,
|
|
1081
1174
|
transport: this.transport,
|
|
1082
1175
|
validator: this.validator,
|
|
1083
1176
|
logger: this.logger,
|