@xtrape/capsule-agent-node 0.5.0 → 0.6.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 +81 -19
- package/dist/index.cjs +166 -9
- package/dist/index.d.cts +64 -2
- package/dist/index.d.ts +64 -2
- package/dist/index.js +164 -8
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -8,31 +8,28 @@ current Xtrape concept. New integrations should use `XtrapeAgent` and the
|
|
|
8
8
|
`worker` option.
|
|
9
9
|
|
|
10
10
|
[](./LICENSE)
|
|
11
|
-
[](https://forgejo.xtrape.com/xtrape/xtrape-docs)
|
|
12
|
+
[](https://forgejo.xtrape.com/xtrape/xtrape-docs)
|
|
13
13
|
|
|
14
14
|
The package supports the Node.js Embedded Agent mode for one Worker. Multi-Worker
|
|
15
15
|
ownership belongs to a sidecar or external Agent. Gateway, when present, routes
|
|
16
16
|
Agent/control-plane traffic and does not replace the Agent role.
|
|
17
17
|
|
|
18
|
-
> **Package status:**
|
|
19
|
-
>
|
|
20
|
-
>
|
|
21
|
-
> interfaces may still change.
|
|
18
|
+
> **Package status:** This package follows the unified Xtrape `0.6.0` release
|
|
19
|
+
> train. The npm package name remains a compatibility identifier, but release
|
|
20
|
+
> versioning now follows the shared Xtrape train version.
|
|
22
21
|
|
|
23
22
|
## Install
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
This repository tracks the `0.6.0` snapshot train. Until the `0.6.0` npm cut is
|
|
25
|
+
published, install the latest published compatibility package:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
pnpm add @xtrape/capsule-agent-node
|
|
28
|
+
pnpm add @xtrape/capsule-agent-node@^0.5.0
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
During v0.3 development, this repository may use a local workspace, GitHub
|
|
34
|
-
branch dependency, or `0.3.0-rc.x` package for
|
|
35
|
-
`@xtrape/capsule-contracts-node` until the final `0.3.0` package is published.
|
|
31
|
+
For source-level snapshot integration, use a local checkout of this repository
|
|
32
|
+
and align the rest of the release train separately.
|
|
36
33
|
|
|
37
34
|
For this repository itself:
|
|
38
35
|
|
|
@@ -41,6 +38,68 @@ pnpm install
|
|
|
41
38
|
pnpm build
|
|
42
39
|
```
|
|
43
40
|
|
|
41
|
+
## Xtrape CE Phase 0 Service Agent
|
|
42
|
+
|
|
43
|
+
For the Xtrape CE 0.1 runtime loop (`Telegram → xtrape-server-ce → xtrape-service
|
|
44
|
+
→ reply`), use `CeServiceAgent` (or the `serveCeService` bootstrap). An
|
|
45
|
+
`xtrape-service` registers an instance to `xtrape-server-ce`, heartbeats, and
|
|
46
|
+
replies to `ConversationMessage`s. `Service` and `Worker` are roles here, not the
|
|
47
|
+
legacy `Capsule` concept.
|
|
48
|
+
|
|
49
|
+
### One-call bootstrap
|
|
50
|
+
|
|
51
|
+
`serveCeService` starts an HTTP listener (`POST /messages`, `GET /health`),
|
|
52
|
+
registers the instance (sending the registration token if server-ce requires
|
|
53
|
+
one), and runs heartbeats — so a service does not copy-paste lifecycle code:
|
|
54
|
+
|
|
55
|
+
For deployed or containerized use, set `address` to the callback URL that
|
|
56
|
+
`xtrape-server-ce` can actually reach and allowlist, for example
|
|
57
|
+
`http://demo-service:8080`. The SDK only derives a default address when you
|
|
58
|
+
bind to an explicit local-only host such as `127.0.0.1`.
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { serveCeService } from "@xtrape/capsule-agent-node";
|
|
62
|
+
|
|
63
|
+
const svc = await serveCeService({
|
|
64
|
+
serverUrl: process.env.XTRAPE_SERVER_URL ?? "http://localhost:3000",
|
|
65
|
+
registrationToken: process.env.SERVER_CE_REGISTRATION_TOKEN,
|
|
66
|
+
port: 8080,
|
|
67
|
+
// required in deployed/containerized environments
|
|
68
|
+
address: process.env.SERVICE_ADDRESS ?? "http://demo-service:8080",
|
|
69
|
+
manifest: {
|
|
70
|
+
contractVersion: 1,
|
|
71
|
+
service: { id: "demo-echo-service", name: "Demo Echo Service", version: "0.6.0" },
|
|
72
|
+
runtime: { language: "node", agent: "xtrape-agent-nodejs" },
|
|
73
|
+
capabilities: ["status.query"],
|
|
74
|
+
message: { supportedTypes: ["QUERY"] },
|
|
75
|
+
},
|
|
76
|
+
handler: (msg) => `echo: ${msg.content}`,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
process.on("SIGTERM", () => void svc.close()); // stop heartbeat + deregister + close
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Lower-level agent
|
|
83
|
+
|
|
84
|
+
When the service already runs its own HTTP server, use `CeServiceAgent` directly:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { CeServiceAgent } from "@xtrape/capsule-agent-node";
|
|
88
|
+
|
|
89
|
+
const agent = new CeServiceAgent({
|
|
90
|
+
serverUrl: "http://localhost:3000",
|
|
91
|
+
registrationToken: process.env.SERVER_CE_REGISTRATION_TOKEN,
|
|
92
|
+
address: "http://127.0.0.1:8080",
|
|
93
|
+
manifest: /* ServiceManifest */ undefined as never,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const stop = await agent.start(); // register + heartbeat
|
|
97
|
+
// in your POST /messages route:
|
|
98
|
+
const reply = await agent.handleMessage(requestJson, (m) => `echo: ${m.content}`);
|
|
99
|
+
// on shutdown:
|
|
100
|
+
await stop(); // stop heartbeat + deregister
|
|
101
|
+
```
|
|
102
|
+
|
|
44
103
|
## Minimal Example
|
|
45
104
|
|
|
46
105
|
This example uses the canonical alias `XtrapeAgent`, configures
|
|
@@ -402,10 +461,13 @@ and `logger` are configured, `structuredLogger` wins.
|
|
|
402
461
|
|
|
403
462
|
| Package | Compatible with |
|
|
404
463
|
| ---------------------------------- | ------------------------------------------------------------- |
|
|
464
|
+
| repository line `0.6.0` | current Xtrape `0.6.0` snapshot train |
|
|
405
465
|
| `@xtrape/capsule-agent-node@0.1.x` | Xtrape Panel CE `0.1.x` and `@xtrape/capsule-contracts-node@0.1.x` |
|
|
406
466
|
|
|
407
|
-
Use matching
|
|
408
|
-
|
|
467
|
+
Use matching release-train versions across CE, Agent SDK, and Contracts. During
|
|
468
|
+
the current `0.6.0` snapshot phase, published compatibility packages may lag
|
|
469
|
+
the repository line until the train npm cut is published. The wire protocol may
|
|
470
|
+
still change before `v1.0`.
|
|
409
471
|
|
|
410
472
|
## Troubleshooting
|
|
411
473
|
|
|
@@ -455,10 +517,10 @@ The token is single-use and short-lived.
|
|
|
455
517
|
|
|
456
518
|
## Documentation
|
|
457
519
|
|
|
458
|
-
-
|
|
459
|
-
- Node embedded Agent guide: https://xtrape
|
|
460
|
-
- Action model: https://xtrape
|
|
461
|
-
- Xtrape Panel CE: https://xtrape
|
|
520
|
+
- Public docs repo: https://forgejo.xtrape.com/xtrape/xtrape-site
|
|
521
|
+
- Node embedded Agent guide: https://forgejo.xtrape.com/xtrape/xtrape-site/src/branch/main/docs/agents/node-embedded-agent.md
|
|
522
|
+
- Action model: https://forgejo.xtrape.com/xtrape/xtrape-site/src/branch/main/docs/agents/action-model.md
|
|
523
|
+
- Xtrape Panel CE docs: https://forgejo.xtrape.com/xtrape/xtrape-site/src/branch/main/docs/opstage-ce/overview.md
|
|
462
524
|
|
|
463
525
|
## Contributing
|
|
464
526
|
|
package/dist/index.cjs
CHANGED
|
@@ -31,7 +31,8 @@ __export(index_exports, {
|
|
|
31
31
|
FileTokenStore: () => FileTokenStore,
|
|
32
32
|
NetworkError: () => NetworkError,
|
|
33
33
|
RegistrationError: () => RegistrationError,
|
|
34
|
-
XtrapeAgent: () => CapsuleAgent
|
|
34
|
+
XtrapeAgent: () => CapsuleAgent,
|
|
35
|
+
serveCeService: () => serveCeService
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(index_exports);
|
|
37
38
|
|
|
@@ -431,6 +432,12 @@ var CapsuleAgent = class {
|
|
|
431
432
|
// src/ce/index.ts
|
|
432
433
|
var import_node_crypto = require("crypto");
|
|
433
434
|
var import_capsule_contracts_node = require("@xtrape/capsule-contracts-node");
|
|
435
|
+
var CeInboundMessageValidationError = class extends Error {
|
|
436
|
+
constructor(message, options) {
|
|
437
|
+
super(message, options);
|
|
438
|
+
this.name = "CeInboundMessageValidationError";
|
|
439
|
+
}
|
|
440
|
+
};
|
|
434
441
|
var CeServiceAgent = class {
|
|
435
442
|
serviceId;
|
|
436
443
|
instanceId;
|
|
@@ -438,6 +445,7 @@ var CeServiceAgent = class {
|
|
|
438
445
|
manifest;
|
|
439
446
|
address;
|
|
440
447
|
tenant;
|
|
448
|
+
registrationToken;
|
|
441
449
|
fetchImpl;
|
|
442
450
|
health = "HEALTHY";
|
|
443
451
|
hbTimer;
|
|
@@ -448,6 +456,7 @@ var CeServiceAgent = class {
|
|
|
448
456
|
this.tenant = opts.manifest.tenant ?? "default";
|
|
449
457
|
this.serviceId = opts.manifest.service.id;
|
|
450
458
|
this.instanceId = opts.instanceId ?? `${this.serviceId}-${(0, import_node_crypto.randomUUID)().slice(0, 8)}`;
|
|
459
|
+
this.registrationToken = opts.registrationToken;
|
|
451
460
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
452
461
|
}
|
|
453
462
|
/** The ServiceInstance this agent reports on register/heartbeat. */
|
|
@@ -462,11 +471,26 @@ var CeServiceAgent = class {
|
|
|
462
471
|
health: this.health
|
|
463
472
|
});
|
|
464
473
|
}
|
|
465
|
-
/** POST /v1/instances { manifest, instance } */
|
|
474
|
+
/** POST /v1/instances { manifest, instance } (sends the registration token if set). */
|
|
466
475
|
async register() {
|
|
467
|
-
|
|
476
|
+
const headers = this.registrationToken ? { "x-xtrape-registration-token": this.registrationToken } : void 0;
|
|
477
|
+
await this.post("/v1/instances", { manifest: this.manifest, instance: this.instance() }, headers);
|
|
468
478
|
return { instanceId: this.instanceId };
|
|
469
479
|
}
|
|
480
|
+
/**
|
|
481
|
+
* Lifecycle convenience: register, then start periodic heartbeats. Returns a
|
|
482
|
+
* `stop()` that stops heartbeats and best-effort deregisters — so a service
|
|
483
|
+
* does not have to wire register/heartbeat/deregister by hand.
|
|
484
|
+
*/
|
|
485
|
+
async start(opts = {}) {
|
|
486
|
+
await this.register();
|
|
487
|
+
if (opts.heartbeatMs !== 0) this.startHeartbeat(opts.heartbeatMs ?? 1e4, opts.metrics);
|
|
488
|
+
return async () => {
|
|
489
|
+
this.stopHeartbeat();
|
|
490
|
+
await this.deregister().catch(() => {
|
|
491
|
+
});
|
|
492
|
+
};
|
|
493
|
+
}
|
|
470
494
|
setHealth(h) {
|
|
471
495
|
this.health = h;
|
|
472
496
|
}
|
|
@@ -508,7 +532,15 @@ var CeServiceAgent = class {
|
|
|
508
532
|
* (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
|
|
509
533
|
*/
|
|
510
534
|
async handleMessage(raw, handler) {
|
|
511
|
-
|
|
535
|
+
let msg;
|
|
536
|
+
try {
|
|
537
|
+
msg = import_capsule_contracts_node.ConversationMessageSchema.parse(raw);
|
|
538
|
+
} catch (error) {
|
|
539
|
+
throw new CeInboundMessageValidationError(
|
|
540
|
+
error instanceof Error ? error.message : "invalid inbound ConversationMessage",
|
|
541
|
+
{ cause: error }
|
|
542
|
+
);
|
|
543
|
+
}
|
|
512
544
|
const out = await handler(msg);
|
|
513
545
|
const reply = typeof out === "string" ? { content: out } : out;
|
|
514
546
|
return import_capsule_contracts_node.ConversationMessageSchema.parse({
|
|
@@ -527,8 +559,8 @@ var CeServiceAgent = class {
|
|
|
527
559
|
...reply.payload ? { payload: reply.payload } : {}
|
|
528
560
|
});
|
|
529
561
|
}
|
|
530
|
-
post(path, body) {
|
|
531
|
-
return this.fetchJson(path, { method: "POST", body: JSON.stringify(body) });
|
|
562
|
+
post(path, body, headers) {
|
|
563
|
+
return this.fetchJson(path, { method: "POST", body: JSON.stringify(body), ...headers ? { headers } : {} });
|
|
532
564
|
}
|
|
533
565
|
async fetchJson(path, init) {
|
|
534
566
|
const res = await this.fetchImpl(this.serverUrl + path, {
|
|
@@ -536,13 +568,137 @@ var CeServiceAgent = class {
|
|
|
536
568
|
headers: { "content-type": "application/json", ...init.headers ?? {} }
|
|
537
569
|
});
|
|
538
570
|
const text = await res.text();
|
|
539
|
-
const data = text ? JSON.parse(text) : void 0;
|
|
540
571
|
if (!res.ok) {
|
|
541
572
|
throw new Error(`server-ce ${path} -> ${res.status}: ${text}`);
|
|
542
573
|
}
|
|
543
|
-
return
|
|
574
|
+
if (!text) return void 0;
|
|
575
|
+
try {
|
|
576
|
+
return JSON.parse(text);
|
|
577
|
+
} catch (error) {
|
|
578
|
+
throw new Error(`server-ce ${path} returned non-JSON success response: ${text}`, { cause: error });
|
|
579
|
+
}
|
|
544
580
|
}
|
|
545
581
|
};
|
|
582
|
+
|
|
583
|
+
// src/ce/serve.ts
|
|
584
|
+
var import_node_http = require("http");
|
|
585
|
+
function send(res, status, body) {
|
|
586
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
587
|
+
res.end(JSON.stringify(body));
|
|
588
|
+
}
|
|
589
|
+
function readBody(req) {
|
|
590
|
+
return new Promise((resolve, reject) => {
|
|
591
|
+
let data = "";
|
|
592
|
+
req.on("data", (c) => data += c);
|
|
593
|
+
req.on("end", () => resolve(data));
|
|
594
|
+
req.on("error", reject);
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
function isLocalOnlyHost(host) {
|
|
598
|
+
return host === "127.0.0.1" || host === "localhost" || host === "::1";
|
|
599
|
+
}
|
|
600
|
+
function defaultLocalAddress(host, port) {
|
|
601
|
+
if (host === "::1") return `http://[::1]:${port}`;
|
|
602
|
+
return `http://${host}:${port}`;
|
|
603
|
+
}
|
|
604
|
+
function listen(server, port, host) {
|
|
605
|
+
return new Promise((resolve, reject) => {
|
|
606
|
+
const onError = (error) => {
|
|
607
|
+
server.off("listening", onListening);
|
|
608
|
+
reject(error);
|
|
609
|
+
};
|
|
610
|
+
const onListening = () => {
|
|
611
|
+
server.off("error", onError);
|
|
612
|
+
resolve();
|
|
613
|
+
};
|
|
614
|
+
server.once("error", onError);
|
|
615
|
+
server.once("listening", onListening);
|
|
616
|
+
server.listen(port, host);
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
function sendMessageError(res, error) {
|
|
620
|
+
if (error instanceof CeInboundMessageValidationError) {
|
|
621
|
+
send(res, 400, { code: "VALIDATION_FAILED", message: error.message, retryable: false });
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const message = error instanceof Error ? error.message : "message handler failed";
|
|
625
|
+
send(res, 500, { code: "HANDLER_FAILED", message, retryable: true });
|
|
626
|
+
}
|
|
627
|
+
async function serveCeService(opts) {
|
|
628
|
+
const host = opts.host ?? "0.0.0.0";
|
|
629
|
+
let agent;
|
|
630
|
+
const server = (0, import_node_http.createServer)((req, res) => {
|
|
631
|
+
void (async () => {
|
|
632
|
+
try {
|
|
633
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
634
|
+
send(res, 200, { ok: true, service: opts.manifest.service.id });
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (req.method === "POST" && req.url === "/messages") {
|
|
638
|
+
if (!agent) {
|
|
639
|
+
send(res, 503, { code: "NOT_READY", message: "service not registered yet", retryable: true });
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const raw = await readBody(req);
|
|
643
|
+
let payload;
|
|
644
|
+
try {
|
|
645
|
+
payload = raw ? JSON.parse(raw) : {};
|
|
646
|
+
} catch (error) {
|
|
647
|
+
send(res, 400, {
|
|
648
|
+
code: "VALIDATION_FAILED",
|
|
649
|
+
message: error instanceof Error ? error.message : "invalid JSON request body",
|
|
650
|
+
retryable: false
|
|
651
|
+
});
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const reply = await agent.handleMessage(payload, opts.handler);
|
|
655
|
+
send(res, 200, reply);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
send(res, 404, { code: "NOT_FOUND", message: "not found", retryable: false });
|
|
659
|
+
} catch (e) {
|
|
660
|
+
sendMessageError(res, e);
|
|
661
|
+
}
|
|
662
|
+
})();
|
|
663
|
+
});
|
|
664
|
+
await listen(server, opts.port ?? 0, host);
|
|
665
|
+
const info = server.address();
|
|
666
|
+
const port = typeof info === "object" && info ? info.port : opts.port ?? 0;
|
|
667
|
+
const address = opts.address ?? (isLocalOnlyHost(host) ? defaultLocalAddress(host, port) : void 0);
|
|
668
|
+
if (!address) {
|
|
669
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
670
|
+
throw new Error(
|
|
671
|
+
`serveCeService requires an explicit address when host=${host}. For deployed/containerized use, pass a reachable callback URL such as http://demo-service:8080.`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
try {
|
|
675
|
+
agent = new CeServiceAgent({
|
|
676
|
+
serverUrl: opts.serverUrl,
|
|
677
|
+
manifest: opts.manifest,
|
|
678
|
+
address,
|
|
679
|
+
instanceId: opts.instanceId,
|
|
680
|
+
registrationToken: opts.registrationToken,
|
|
681
|
+
fetchImpl: opts.fetchImpl
|
|
682
|
+
});
|
|
683
|
+
await agent.register();
|
|
684
|
+
if (opts.heartbeatMs !== 0) agent.startHeartbeat(opts.heartbeatMs ?? 1e4);
|
|
685
|
+
} catch (error) {
|
|
686
|
+
agent?.stopHeartbeat();
|
|
687
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
688
|
+
throw error;
|
|
689
|
+
}
|
|
690
|
+
return {
|
|
691
|
+
agent,
|
|
692
|
+
address,
|
|
693
|
+
port,
|
|
694
|
+
async close() {
|
|
695
|
+
agent?.stopHeartbeat();
|
|
696
|
+
await agent?.deregister().catch(() => {
|
|
697
|
+
});
|
|
698
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
}
|
|
546
702
|
// Annotate the CommonJS export names for ESM import in node:
|
|
547
703
|
0 && (module.exports = {
|
|
548
704
|
AgentApiClient,
|
|
@@ -556,5 +712,6 @@ var CeServiceAgent = class {
|
|
|
556
712
|
FileTokenStore,
|
|
557
713
|
NetworkError,
|
|
558
714
|
RegistrationError,
|
|
559
|
-
XtrapeAgent
|
|
715
|
+
XtrapeAgent,
|
|
716
|
+
serveCeService
|
|
560
717
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -277,6 +277,12 @@ interface CeServiceAgentOptions {
|
|
|
277
277
|
address: string;
|
|
278
278
|
/** optional fixed instance id; defaults to `<serviceId>-<rand>` */
|
|
279
279
|
instanceId?: string;
|
|
280
|
+
/**
|
|
281
|
+
* Shared registration token. When server-ce has `SERVER_CE_REGISTRATION_TOKEN`
|
|
282
|
+
* set, registration requires it; the agent sends it as the
|
|
283
|
+
* `x-xtrape-registration-token` header on register.
|
|
284
|
+
*/
|
|
285
|
+
registrationToken?: string;
|
|
280
286
|
/** injectable fetch (tests); defaults to global fetch */
|
|
281
287
|
fetchImpl?: typeof fetch;
|
|
282
288
|
}
|
|
@@ -294,16 +300,26 @@ declare class CeServiceAgent {
|
|
|
294
300
|
private readonly manifest;
|
|
295
301
|
private readonly address;
|
|
296
302
|
private readonly tenant;
|
|
303
|
+
private readonly registrationToken?;
|
|
297
304
|
private readonly fetchImpl;
|
|
298
305
|
private health;
|
|
299
306
|
private hbTimer?;
|
|
300
307
|
constructor(opts: CeServiceAgentOptions);
|
|
301
308
|
/** The ServiceInstance this agent reports on register/heartbeat. */
|
|
302
309
|
instance(): ServiceInstance;
|
|
303
|
-
/** POST /v1/instances { manifest, instance } */
|
|
310
|
+
/** POST /v1/instances { manifest, instance } (sends the registration token if set). */
|
|
304
311
|
register(): Promise<{
|
|
305
312
|
instanceId: string;
|
|
306
313
|
}>;
|
|
314
|
+
/**
|
|
315
|
+
* Lifecycle convenience: register, then start periodic heartbeats. Returns a
|
|
316
|
+
* `stop()` that stops heartbeats and best-effort deregisters — so a service
|
|
317
|
+
* does not have to wire register/heartbeat/deregister by hand.
|
|
318
|
+
*/
|
|
319
|
+
start(opts?: {
|
|
320
|
+
heartbeatMs?: number;
|
|
321
|
+
metrics?: () => Record<string, unknown>;
|
|
322
|
+
}): Promise<() => Promise<void>>;
|
|
307
323
|
setHealth(h: ServiceInstanceHealth): void;
|
|
308
324
|
/** POST /v1/instances/{id}/heartbeat */
|
|
309
325
|
sendHeartbeat(metricsSummary?: Record<string, unknown>): Promise<void>;
|
|
@@ -321,4 +337,50 @@ declare class CeServiceAgent {
|
|
|
321
337
|
private fetchJson;
|
|
322
338
|
}
|
|
323
339
|
|
|
324
|
-
|
|
340
|
+
/**
|
|
341
|
+
* One-call bootstrap for an `xtrape-service`: start an HTTP listener that serves
|
|
342
|
+
* `POST /messages` (and `GET /health`), register the instance to xtrape-server-ce,
|
|
343
|
+
* and run heartbeats. Returns a handle whose `close()` stops heartbeats,
|
|
344
|
+
* deregisters, and closes the listener.
|
|
345
|
+
*
|
|
346
|
+
* Uses only `node:http` so it stays framework-agnostic. A service that already
|
|
347
|
+
* has an HTTP server can use `CeServiceAgent` directly instead.
|
|
348
|
+
*/
|
|
349
|
+
interface ServeCeServiceOptions {
|
|
350
|
+
/** xtrape-server-ce base URL, e.g. http://localhost:3000 */
|
|
351
|
+
serverUrl: string;
|
|
352
|
+
manifest: ServiceManifest;
|
|
353
|
+
/** business handler invoked per inbound ConversationMessage */
|
|
354
|
+
handler: CeMessageHandler;
|
|
355
|
+
/** shared registration token, if server-ce requires one */
|
|
356
|
+
registrationToken?: string;
|
|
357
|
+
/** listen port; 0 picks an ephemeral port (default 0) */
|
|
358
|
+
port?: number;
|
|
359
|
+
/** listen host (default 0.0.0.0) */
|
|
360
|
+
host?: string;
|
|
361
|
+
/**
|
|
362
|
+
* Address server-ce should call back.
|
|
363
|
+
*
|
|
364
|
+
* Required for deployed/containerized use. When omitted, the SDK only derives
|
|
365
|
+
* a default address for explicit local-only hosts such as `127.0.0.1` or
|
|
366
|
+
* `localhost`.
|
|
367
|
+
*/
|
|
368
|
+
address?: string;
|
|
369
|
+
/** heartbeat interval ms; 0 disables (default 10000) */
|
|
370
|
+
heartbeatMs?: number;
|
|
371
|
+
instanceId?: string;
|
|
372
|
+
/** injectable fetch (tests); defaults to global fetch */
|
|
373
|
+
fetchImpl?: typeof fetch;
|
|
374
|
+
}
|
|
375
|
+
interface ServeCeServiceHandle {
|
|
376
|
+
agent: CeServiceAgent;
|
|
377
|
+
/** advertised address registered with server-ce */
|
|
378
|
+
address: string;
|
|
379
|
+
/** actual listening port */
|
|
380
|
+
port: number;
|
|
381
|
+
/** stop heartbeats, deregister (best-effort), and close the HTTP server */
|
|
382
|
+
close(): Promise<void>;
|
|
383
|
+
}
|
|
384
|
+
declare function serveCeService(opts: ServeCeServiceOptions): Promise<ServeCeServiceHandle>;
|
|
385
|
+
|
|
386
|
+
export { type ActionHandler, type ActionPrepareHandler, AgentApiClient, AgentApiError, AgentAuthError, type AgentLogLevel, type AgentLogRecord, type AgentMode, BusDepthExceededError, BusDisabledError, BusRateLimitedError, CapsuleAgent, type CapsuleAgentOptions, type CeMessageHandler, type CeMessageReply, CeServiceAgent, type CeServiceAgentOptions, type ConfigProvider, type ConsoleLogger, FileTokenStore, type HealthProvider, type LegacyServiceAgentOptions, NetworkError, type PublishBusEventInput, type PublishBusEventResult, type RegisteredAction, RegistrationError, type ServeCeServiceHandle, type ServeCeServiceOptions, type ServiceSnapshot, type StructuredLogger, type TokenStore, type WorkerAgentOptions, type WorkerDefinition, type WorkerSnapshot, CapsuleAgent as XtrapeAgent, type XtrapeAgentOptions, serveCeService };
|
package/dist/index.d.ts
CHANGED
|
@@ -277,6 +277,12 @@ interface CeServiceAgentOptions {
|
|
|
277
277
|
address: string;
|
|
278
278
|
/** optional fixed instance id; defaults to `<serviceId>-<rand>` */
|
|
279
279
|
instanceId?: string;
|
|
280
|
+
/**
|
|
281
|
+
* Shared registration token. When server-ce has `SERVER_CE_REGISTRATION_TOKEN`
|
|
282
|
+
* set, registration requires it; the agent sends it as the
|
|
283
|
+
* `x-xtrape-registration-token` header on register.
|
|
284
|
+
*/
|
|
285
|
+
registrationToken?: string;
|
|
280
286
|
/** injectable fetch (tests); defaults to global fetch */
|
|
281
287
|
fetchImpl?: typeof fetch;
|
|
282
288
|
}
|
|
@@ -294,16 +300,26 @@ declare class CeServiceAgent {
|
|
|
294
300
|
private readonly manifest;
|
|
295
301
|
private readonly address;
|
|
296
302
|
private readonly tenant;
|
|
303
|
+
private readonly registrationToken?;
|
|
297
304
|
private readonly fetchImpl;
|
|
298
305
|
private health;
|
|
299
306
|
private hbTimer?;
|
|
300
307
|
constructor(opts: CeServiceAgentOptions);
|
|
301
308
|
/** The ServiceInstance this agent reports on register/heartbeat. */
|
|
302
309
|
instance(): ServiceInstance;
|
|
303
|
-
/** POST /v1/instances { manifest, instance } */
|
|
310
|
+
/** POST /v1/instances { manifest, instance } (sends the registration token if set). */
|
|
304
311
|
register(): Promise<{
|
|
305
312
|
instanceId: string;
|
|
306
313
|
}>;
|
|
314
|
+
/**
|
|
315
|
+
* Lifecycle convenience: register, then start periodic heartbeats. Returns a
|
|
316
|
+
* `stop()` that stops heartbeats and best-effort deregisters — so a service
|
|
317
|
+
* does not have to wire register/heartbeat/deregister by hand.
|
|
318
|
+
*/
|
|
319
|
+
start(opts?: {
|
|
320
|
+
heartbeatMs?: number;
|
|
321
|
+
metrics?: () => Record<string, unknown>;
|
|
322
|
+
}): Promise<() => Promise<void>>;
|
|
307
323
|
setHealth(h: ServiceInstanceHealth): void;
|
|
308
324
|
/** POST /v1/instances/{id}/heartbeat */
|
|
309
325
|
sendHeartbeat(metricsSummary?: Record<string, unknown>): Promise<void>;
|
|
@@ -321,4 +337,50 @@ declare class CeServiceAgent {
|
|
|
321
337
|
private fetchJson;
|
|
322
338
|
}
|
|
323
339
|
|
|
324
|
-
|
|
340
|
+
/**
|
|
341
|
+
* One-call bootstrap for an `xtrape-service`: start an HTTP listener that serves
|
|
342
|
+
* `POST /messages` (and `GET /health`), register the instance to xtrape-server-ce,
|
|
343
|
+
* and run heartbeats. Returns a handle whose `close()` stops heartbeats,
|
|
344
|
+
* deregisters, and closes the listener.
|
|
345
|
+
*
|
|
346
|
+
* Uses only `node:http` so it stays framework-agnostic. A service that already
|
|
347
|
+
* has an HTTP server can use `CeServiceAgent` directly instead.
|
|
348
|
+
*/
|
|
349
|
+
interface ServeCeServiceOptions {
|
|
350
|
+
/** xtrape-server-ce base URL, e.g. http://localhost:3000 */
|
|
351
|
+
serverUrl: string;
|
|
352
|
+
manifest: ServiceManifest;
|
|
353
|
+
/** business handler invoked per inbound ConversationMessage */
|
|
354
|
+
handler: CeMessageHandler;
|
|
355
|
+
/** shared registration token, if server-ce requires one */
|
|
356
|
+
registrationToken?: string;
|
|
357
|
+
/** listen port; 0 picks an ephemeral port (default 0) */
|
|
358
|
+
port?: number;
|
|
359
|
+
/** listen host (default 0.0.0.0) */
|
|
360
|
+
host?: string;
|
|
361
|
+
/**
|
|
362
|
+
* Address server-ce should call back.
|
|
363
|
+
*
|
|
364
|
+
* Required for deployed/containerized use. When omitted, the SDK only derives
|
|
365
|
+
* a default address for explicit local-only hosts such as `127.0.0.1` or
|
|
366
|
+
* `localhost`.
|
|
367
|
+
*/
|
|
368
|
+
address?: string;
|
|
369
|
+
/** heartbeat interval ms; 0 disables (default 10000) */
|
|
370
|
+
heartbeatMs?: number;
|
|
371
|
+
instanceId?: string;
|
|
372
|
+
/** injectable fetch (tests); defaults to global fetch */
|
|
373
|
+
fetchImpl?: typeof fetch;
|
|
374
|
+
}
|
|
375
|
+
interface ServeCeServiceHandle {
|
|
376
|
+
agent: CeServiceAgent;
|
|
377
|
+
/** advertised address registered with server-ce */
|
|
378
|
+
address: string;
|
|
379
|
+
/** actual listening port */
|
|
380
|
+
port: number;
|
|
381
|
+
/** stop heartbeats, deregister (best-effort), and close the HTTP server */
|
|
382
|
+
close(): Promise<void>;
|
|
383
|
+
}
|
|
384
|
+
declare function serveCeService(opts: ServeCeServiceOptions): Promise<ServeCeServiceHandle>;
|
|
385
|
+
|
|
386
|
+
export { type ActionHandler, type ActionPrepareHandler, AgentApiClient, AgentApiError, AgentAuthError, type AgentLogLevel, type AgentLogRecord, type AgentMode, BusDepthExceededError, BusDisabledError, BusRateLimitedError, CapsuleAgent, type CapsuleAgentOptions, type CeMessageHandler, type CeMessageReply, CeServiceAgent, type CeServiceAgentOptions, type ConfigProvider, type ConsoleLogger, FileTokenStore, type HealthProvider, type LegacyServiceAgentOptions, NetworkError, type PublishBusEventInput, type PublishBusEventResult, type RegisteredAction, RegistrationError, type ServeCeServiceHandle, type ServeCeServiceOptions, type ServiceSnapshot, type StructuredLogger, type TokenStore, type WorkerAgentOptions, type WorkerDefinition, type WorkerSnapshot, CapsuleAgent as XtrapeAgent, type XtrapeAgentOptions, serveCeService };
|
package/dist/index.js
CHANGED
|
@@ -398,6 +398,12 @@ import {
|
|
|
398
398
|
ServiceInstanceSchema,
|
|
399
399
|
ConversationMessageSchema
|
|
400
400
|
} from "@xtrape/capsule-contracts-node";
|
|
401
|
+
var CeInboundMessageValidationError = class extends Error {
|
|
402
|
+
constructor(message, options) {
|
|
403
|
+
super(message, options);
|
|
404
|
+
this.name = "CeInboundMessageValidationError";
|
|
405
|
+
}
|
|
406
|
+
};
|
|
401
407
|
var CeServiceAgent = class {
|
|
402
408
|
serviceId;
|
|
403
409
|
instanceId;
|
|
@@ -405,6 +411,7 @@ var CeServiceAgent = class {
|
|
|
405
411
|
manifest;
|
|
406
412
|
address;
|
|
407
413
|
tenant;
|
|
414
|
+
registrationToken;
|
|
408
415
|
fetchImpl;
|
|
409
416
|
health = "HEALTHY";
|
|
410
417
|
hbTimer;
|
|
@@ -415,6 +422,7 @@ var CeServiceAgent = class {
|
|
|
415
422
|
this.tenant = opts.manifest.tenant ?? "default";
|
|
416
423
|
this.serviceId = opts.manifest.service.id;
|
|
417
424
|
this.instanceId = opts.instanceId ?? `${this.serviceId}-${randomUUID().slice(0, 8)}`;
|
|
425
|
+
this.registrationToken = opts.registrationToken;
|
|
418
426
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
419
427
|
}
|
|
420
428
|
/** The ServiceInstance this agent reports on register/heartbeat. */
|
|
@@ -429,11 +437,26 @@ var CeServiceAgent = class {
|
|
|
429
437
|
health: this.health
|
|
430
438
|
});
|
|
431
439
|
}
|
|
432
|
-
/** POST /v1/instances { manifest, instance } */
|
|
440
|
+
/** POST /v1/instances { manifest, instance } (sends the registration token if set). */
|
|
433
441
|
async register() {
|
|
434
|
-
|
|
442
|
+
const headers = this.registrationToken ? { "x-xtrape-registration-token": this.registrationToken } : void 0;
|
|
443
|
+
await this.post("/v1/instances", { manifest: this.manifest, instance: this.instance() }, headers);
|
|
435
444
|
return { instanceId: this.instanceId };
|
|
436
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* Lifecycle convenience: register, then start periodic heartbeats. Returns a
|
|
448
|
+
* `stop()` that stops heartbeats and best-effort deregisters — so a service
|
|
449
|
+
* does not have to wire register/heartbeat/deregister by hand.
|
|
450
|
+
*/
|
|
451
|
+
async start(opts = {}) {
|
|
452
|
+
await this.register();
|
|
453
|
+
if (opts.heartbeatMs !== 0) this.startHeartbeat(opts.heartbeatMs ?? 1e4, opts.metrics);
|
|
454
|
+
return async () => {
|
|
455
|
+
this.stopHeartbeat();
|
|
456
|
+
await this.deregister().catch(() => {
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
}
|
|
437
460
|
setHealth(h) {
|
|
438
461
|
this.health = h;
|
|
439
462
|
}
|
|
@@ -475,7 +498,15 @@ var CeServiceAgent = class {
|
|
|
475
498
|
* (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
|
|
476
499
|
*/
|
|
477
500
|
async handleMessage(raw, handler) {
|
|
478
|
-
|
|
501
|
+
let msg;
|
|
502
|
+
try {
|
|
503
|
+
msg = ConversationMessageSchema.parse(raw);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
throw new CeInboundMessageValidationError(
|
|
506
|
+
error instanceof Error ? error.message : "invalid inbound ConversationMessage",
|
|
507
|
+
{ cause: error }
|
|
508
|
+
);
|
|
509
|
+
}
|
|
479
510
|
const out = await handler(msg);
|
|
480
511
|
const reply = typeof out === "string" ? { content: out } : out;
|
|
481
512
|
return ConversationMessageSchema.parse({
|
|
@@ -494,8 +525,8 @@ var CeServiceAgent = class {
|
|
|
494
525
|
...reply.payload ? { payload: reply.payload } : {}
|
|
495
526
|
});
|
|
496
527
|
}
|
|
497
|
-
post(path, body) {
|
|
498
|
-
return this.fetchJson(path, { method: "POST", body: JSON.stringify(body) });
|
|
528
|
+
post(path, body, headers) {
|
|
529
|
+
return this.fetchJson(path, { method: "POST", body: JSON.stringify(body), ...headers ? { headers } : {} });
|
|
499
530
|
}
|
|
500
531
|
async fetchJson(path, init) {
|
|
501
532
|
const res = await this.fetchImpl(this.serverUrl + path, {
|
|
@@ -503,13 +534,137 @@ var CeServiceAgent = class {
|
|
|
503
534
|
headers: { "content-type": "application/json", ...init.headers ?? {} }
|
|
504
535
|
});
|
|
505
536
|
const text = await res.text();
|
|
506
|
-
const data = text ? JSON.parse(text) : void 0;
|
|
507
537
|
if (!res.ok) {
|
|
508
538
|
throw new Error(`server-ce ${path} -> ${res.status}: ${text}`);
|
|
509
539
|
}
|
|
510
|
-
return
|
|
540
|
+
if (!text) return void 0;
|
|
541
|
+
try {
|
|
542
|
+
return JSON.parse(text);
|
|
543
|
+
} catch (error) {
|
|
544
|
+
throw new Error(`server-ce ${path} returned non-JSON success response: ${text}`, { cause: error });
|
|
545
|
+
}
|
|
511
546
|
}
|
|
512
547
|
};
|
|
548
|
+
|
|
549
|
+
// src/ce/serve.ts
|
|
550
|
+
import { createServer } from "http";
|
|
551
|
+
function send(res, status, body) {
|
|
552
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
553
|
+
res.end(JSON.stringify(body));
|
|
554
|
+
}
|
|
555
|
+
function readBody(req) {
|
|
556
|
+
return new Promise((resolve, reject) => {
|
|
557
|
+
let data = "";
|
|
558
|
+
req.on("data", (c) => data += c);
|
|
559
|
+
req.on("end", () => resolve(data));
|
|
560
|
+
req.on("error", reject);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
function isLocalOnlyHost(host) {
|
|
564
|
+
return host === "127.0.0.1" || host === "localhost" || host === "::1";
|
|
565
|
+
}
|
|
566
|
+
function defaultLocalAddress(host, port) {
|
|
567
|
+
if (host === "::1") return `http://[::1]:${port}`;
|
|
568
|
+
return `http://${host}:${port}`;
|
|
569
|
+
}
|
|
570
|
+
function listen(server, port, host) {
|
|
571
|
+
return new Promise((resolve, reject) => {
|
|
572
|
+
const onError = (error) => {
|
|
573
|
+
server.off("listening", onListening);
|
|
574
|
+
reject(error);
|
|
575
|
+
};
|
|
576
|
+
const onListening = () => {
|
|
577
|
+
server.off("error", onError);
|
|
578
|
+
resolve();
|
|
579
|
+
};
|
|
580
|
+
server.once("error", onError);
|
|
581
|
+
server.once("listening", onListening);
|
|
582
|
+
server.listen(port, host);
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
function sendMessageError(res, error) {
|
|
586
|
+
if (error instanceof CeInboundMessageValidationError) {
|
|
587
|
+
send(res, 400, { code: "VALIDATION_FAILED", message: error.message, retryable: false });
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const message = error instanceof Error ? error.message : "message handler failed";
|
|
591
|
+
send(res, 500, { code: "HANDLER_FAILED", message, retryable: true });
|
|
592
|
+
}
|
|
593
|
+
async function serveCeService(opts) {
|
|
594
|
+
const host = opts.host ?? "0.0.0.0";
|
|
595
|
+
let agent;
|
|
596
|
+
const server = createServer((req, res) => {
|
|
597
|
+
void (async () => {
|
|
598
|
+
try {
|
|
599
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
600
|
+
send(res, 200, { ok: true, service: opts.manifest.service.id });
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (req.method === "POST" && req.url === "/messages") {
|
|
604
|
+
if (!agent) {
|
|
605
|
+
send(res, 503, { code: "NOT_READY", message: "service not registered yet", retryable: true });
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const raw = await readBody(req);
|
|
609
|
+
let payload;
|
|
610
|
+
try {
|
|
611
|
+
payload = raw ? JSON.parse(raw) : {};
|
|
612
|
+
} catch (error) {
|
|
613
|
+
send(res, 400, {
|
|
614
|
+
code: "VALIDATION_FAILED",
|
|
615
|
+
message: error instanceof Error ? error.message : "invalid JSON request body",
|
|
616
|
+
retryable: false
|
|
617
|
+
});
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const reply = await agent.handleMessage(payload, opts.handler);
|
|
621
|
+
send(res, 200, reply);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
send(res, 404, { code: "NOT_FOUND", message: "not found", retryable: false });
|
|
625
|
+
} catch (e) {
|
|
626
|
+
sendMessageError(res, e);
|
|
627
|
+
}
|
|
628
|
+
})();
|
|
629
|
+
});
|
|
630
|
+
await listen(server, opts.port ?? 0, host);
|
|
631
|
+
const info = server.address();
|
|
632
|
+
const port = typeof info === "object" && info ? info.port : opts.port ?? 0;
|
|
633
|
+
const address = opts.address ?? (isLocalOnlyHost(host) ? defaultLocalAddress(host, port) : void 0);
|
|
634
|
+
if (!address) {
|
|
635
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
636
|
+
throw new Error(
|
|
637
|
+
`serveCeService requires an explicit address when host=${host}. For deployed/containerized use, pass a reachable callback URL such as http://demo-service:8080.`
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
try {
|
|
641
|
+
agent = new CeServiceAgent({
|
|
642
|
+
serverUrl: opts.serverUrl,
|
|
643
|
+
manifest: opts.manifest,
|
|
644
|
+
address,
|
|
645
|
+
instanceId: opts.instanceId,
|
|
646
|
+
registrationToken: opts.registrationToken,
|
|
647
|
+
fetchImpl: opts.fetchImpl
|
|
648
|
+
});
|
|
649
|
+
await agent.register();
|
|
650
|
+
if (opts.heartbeatMs !== 0) agent.startHeartbeat(opts.heartbeatMs ?? 1e4);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
agent?.stopHeartbeat();
|
|
653
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
654
|
+
throw error;
|
|
655
|
+
}
|
|
656
|
+
return {
|
|
657
|
+
agent,
|
|
658
|
+
address,
|
|
659
|
+
port,
|
|
660
|
+
async close() {
|
|
661
|
+
agent?.stopHeartbeat();
|
|
662
|
+
await agent?.deregister().catch(() => {
|
|
663
|
+
});
|
|
664
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
}
|
|
513
668
|
export {
|
|
514
669
|
AgentApiClient,
|
|
515
670
|
AgentApiError,
|
|
@@ -522,5 +677,6 @@ export {
|
|
|
522
677
|
FileTokenStore,
|
|
523
678
|
NetworkError,
|
|
524
679
|
RegistrationError,
|
|
525
|
-
CapsuleAgent as XtrapeAgent
|
|
680
|
+
CapsuleAgent as XtrapeAgent,
|
|
681
|
+
serveCeService
|
|
526
682
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xtrape/capsule-agent-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Node.js Embedded Agent SDK for reporting Workers to the Xtrape control plane.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"xtrape",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
],
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "git+https://
|
|
16
|
+
"url": "git+https://forgejo.xtrape.com/xtrape/xtrape-agent-nodejs.git"
|
|
17
17
|
},
|
|
18
|
-
"homepage": "https://
|
|
18
|
+
"homepage": "https://forgejo.xtrape.com/xtrape/xtrape-agent-nodejs#readme",
|
|
19
19
|
"bugs": {
|
|
20
|
-
"url": "https://
|
|
20
|
+
"url": "https://forgejo.xtrape.com/xtrape/xtrape-agent-nodejs/issues"
|
|
21
21
|
},
|
|
22
22
|
"type": "module",
|
|
23
23
|
"main": "./dist/index.cjs",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"lint": "tsc --noEmit"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@xtrape/capsule-contracts-node": "^0.
|
|
47
|
+
"@xtrape/capsule-contracts-node": "^0.6.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"tsup": "^8.3.5",
|