@xtrape/capsule-agent-node 0.6.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
@@ -8,29 +8,23 @@ current Xtrape concept. New integrations should use `XtrapeAgent` and the
8
8
  `worker` option.
9
9
 
10
10
  [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)
11
- [![Status: Release Train 0.6.0](https://img.shields.io/badge/status-0.6.0-blue.svg)](https://forgejo.xtrape.com/xtrape/xtrape-docs)
11
+ [![Status: 0.7 milestone](https://img.shields.io/badge/status-0.7.0-blue.svg)](https://forgejo.xtrape.com/xtrape/xtrape-docs)
12
12
  [![Docs](https://img.shields.io/badge/docs-xtrape--docs-blue.svg)](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:** 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.
18
+ > **Package status:** This package follows Xtrape milestone-aligned component
19
+ > versioning (`MINOR` = milestone). The npm package name remains a
20
+ > compatibility identifier; the current milestone line is `0.7.x`.
21
21
 
22
22
  ## Install
23
23
 
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
-
27
24
  ```bash
28
- pnpm add @xtrape/capsule-agent-node@^0.5.0
25
+ pnpm add @xtrape/capsule-agent-node@^0.7.0
29
26
  ```
30
27
 
31
- For source-level snapshot integration, use a local checkout of this repository
32
- and align the rest of the release train separately.
33
-
34
28
  For this repository itself:
35
29
 
36
30
  ```bash
@@ -77,7 +71,7 @@ const svc = await serveCeService({
77
71
  address: process.env.SERVICE_ADDRESS ?? "http://demo-service:8080",
78
72
  manifest: {
79
73
  contractVersion: 1,
80
- service: { id: "demo-echo-service", name: "Demo Echo Service", version: "0.6.0" },
74
+ service: { id: "demo-echo-service", name: "Demo Echo Service", version: "0.7.0" },
81
75
  runtime: { language: "node", agent: "xtrape-agent-nodejs" },
82
76
  capabilities: ["status.query"],
83
77
  message: { supportedTypes: ["QUERY"] },
@@ -88,6 +82,38 @@ const svc = await serveCeService({
88
82
  process.on("SIGTERM", () => void svc.close()); // stop heartbeat + deregister + close
89
83
  ```
90
84
 
85
+ ### CE service actions
86
+
87
+ Pass `actions` to expose operator-triggerable operations over `GET /actions`,
88
+ `POST /actions/:name/prepare`, and `POST /actions/:name/execute`:
89
+
90
+ ```ts
91
+ const svc = await serveCeService({
92
+ // ...as above
93
+ actions: [
94
+ {
95
+ name: "relogin",
96
+ label: "Re-login account",
97
+ // Optional two-phase prepare: fetch live defaults/current state before
98
+ // the operator fills the form (advertised as requiresPrepare: true).
99
+ prepare: async () => ({ initialPayload: { accountId: "acc-1" } }),
100
+ execute: async (input) => ({ ok: true }),
101
+ },
102
+ {
103
+ name: "full-sync",
104
+ label: "Full re-sync",
105
+ // Long-running action (server-ce#41 Stage B): when the pushed execute
106
+ // carries x-xtrape-command-id, the SDK replies 202 RUNNING immediately,
107
+ // runs execute in the background, and reports the outcome to server-ce
108
+ // with the per-instance access token. Against an older server-ce
109
+ // (no header) the action falls back to synchronous execution.
110
+ async: true,
111
+ execute: async (input) => runFullSync(input),
112
+ },
113
+ ],
114
+ });
115
+ ```
116
+
91
117
  ### Lower-level agent
92
118
 
93
119
  When the service already runs its own HTTP server, use `CeServiceAgent` directly:
@@ -471,13 +497,12 @@ and `logger` are configured, `structuredLogger` wins.
471
497
 
472
498
  | Package | Compatible with |
473
499
  | ---------------------------------- | ------------------------------------------------------------- |
474
- | repository line `0.6.0` | current Xtrape `0.6.0` snapshot train |
500
+ | repository line `0.7.x` | Xtrape `0.7` milestone (`xtrape-server-ce` >= 0.7.0, `@xtrape/capsule-contracts-node@^0.7.0`) |
475
501
  | `@xtrape/capsule-agent-node@0.1.x` | Xtrape Panel CE `0.1.x` and `@xtrape/capsule-contracts-node@0.1.x` |
476
502
 
477
- Use matching release-train versions across CE, Agent SDK, and Contracts. During
478
- the current `0.6.0` snapshot phase, published compatibility packages may lag
479
- the repository line until the train npm cut is published. The wire protocol may
480
- still change before `v1.0`.
503
+ Pin matching milestone (`MINOR`) versions across CE, Agent SDK, and Contracts;
504
+ patch numbers may differ per component. The wire protocol may still change
505
+ before `v1.0`.
481
506
 
482
507
  ## Troubleshooting
483
508
 
package/dist/index.cjs CHANGED
@@ -546,6 +546,27 @@ var CeServiceAgent = class {
546
546
  ...this.lifecycleAuthHeaders() ? { headers: this.lifecycleAuthHeaders() } : {}
547
547
  });
548
548
  }
549
+ /**
550
+ * Report the outcome of an async action (server-ce#41 Stage B):
551
+ * POST /v1/instances/{instanceId}/commands/{commandId}/result using the
552
+ * per-instance access token. `outcome.result` carries the action payload for
553
+ * COMPLETED; errorCode/errorMessage describe a FAILED run.
554
+ */
555
+ async reportCommandResult(commandId, outcome) {
556
+ const body = {
557
+ contractVersion: import_capsule_contracts_node.CE_CONTRACT_VERSION,
558
+ commandId,
559
+ status: outcome.status,
560
+ ...outcome.result !== void 0 ? { result: outcome.result } : {},
561
+ ...outcome.errorCode ? { errorCode: outcome.errorCode } : {},
562
+ ...outcome.errorMessage ? { errorMessage: outcome.errorMessage } : {}
563
+ };
564
+ await this.post(
565
+ `/v1/instances/${encodeURIComponent(this.instanceId)}/commands/${encodeURIComponent(commandId)}/result`,
566
+ body,
567
+ this.lifecycleAuthHeaders()
568
+ );
569
+ }
549
570
  /**
550
571
  * Validate an inbound message, run the handler, and build the reply message
551
572
  * (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
@@ -601,6 +622,7 @@ var CeServiceAgent = class {
601
622
 
602
623
  // src/ce/serve.ts
603
624
  var import_node_http = require("http");
625
+ var import_capsule_contracts_node2 = require("@xtrape/capsule-contracts-node");
604
626
  function send(res, status, body) {
605
627
  res.writeHead(status, { "content-type": "application/json" });
606
628
  res.end(JSON.stringify(body));
@@ -711,6 +733,7 @@ async function serveCeService(opts) {
711
733
  category: action.category,
712
734
  order: action.order,
713
735
  requiresPrepare: typeof action.prepare === "function" ? true : void 0,
736
+ async: action.async === true ? true : void 0,
714
737
  inputSchema: action.inputSchema
715
738
  }))
716
739
  );
@@ -769,6 +792,33 @@ async function serveCeService(opts) {
769
792
  });
770
793
  return;
771
794
  }
795
+ const commandIdRaw = req.headers["x-xtrape-command-id"];
796
+ const commandId = Array.isArray(commandIdRaw) ? commandIdRaw[0] : commandIdRaw;
797
+ if (action.async === true && typeof commandId === "string" && commandId.length > 0 && agent) {
798
+ const reporter = agent;
799
+ send(res, 202, { contractVersion: import_capsule_contracts_node2.CE_CONTRACT_VERSION, status: "RUNNING", commandId });
800
+ void (async () => {
801
+ let outcome;
802
+ try {
803
+ const result = await action.execute(payload);
804
+ outcome = {
805
+ status: "COMPLETED",
806
+ ...result !== void 0 && result !== null && typeof result === "object" && !Array.isArray(result) ? { result } : result !== void 0 ? { result: { value: result } } : {}
807
+ };
808
+ } catch (error) {
809
+ outcome = {
810
+ status: "FAILED",
811
+ errorCode: "ACTION_FAILED",
812
+ errorMessage: error instanceof Error ? error.message : "action handler failed"
813
+ };
814
+ }
815
+ try {
816
+ await reporter.reportCommandResult(commandId, outcome);
817
+ } catch {
818
+ }
819
+ })();
820
+ return;
821
+ }
772
822
  try {
773
823
  send(res, 200, await action.execute(payload));
774
824
  } catch (error) {
package/dist/index.d.cts CHANGED
@@ -346,6 +346,18 @@ declare class CeServiceAgent {
346
346
  stopHeartbeat(): void;
347
347
  /** DELETE /v1/instances/{id} — graceful deregister. */
348
348
  deregister(): Promise<void>;
349
+ /**
350
+ * Report the outcome of an async action (server-ce#41 Stage B):
351
+ * POST /v1/instances/{instanceId}/commands/{commandId}/result using the
352
+ * per-instance access token. `outcome.result` carries the action payload for
353
+ * COMPLETED; errorCode/errorMessage describe a FAILED run.
354
+ */
355
+ reportCommandResult(commandId: string, outcome: {
356
+ status: "COMPLETED" | "FAILED";
357
+ result?: Record<string, unknown>;
358
+ errorCode?: string;
359
+ errorMessage?: string;
360
+ }): Promise<void>;
349
361
  /**
350
362
  * Validate an inbound message, run the handler, and build the reply message
351
363
  * (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
@@ -435,6 +447,15 @@ interface CeServiceActionDefinition {
435
447
  * current state. Advertised as `requiresPrepare: true` in the action list.
436
448
  */
437
449
  prepare?(input?: unknown): CeActionPrepareResult | Promise<CeActionPrepareResult>;
450
+ /**
451
+ * Long-running action (server-ce#41 Stage B). When true and the pushed
452
+ * execute carries an `x-xtrape-command-id` header, the listener replies
453
+ * 202 { status: "RUNNING", commandId } immediately, runs `execute` in the
454
+ * background, and reports the outcome via
455
+ * `CeServiceAgent.reportCommandResult`. Without the header (older server-ce)
456
+ * the action falls back to synchronous execution.
457
+ */
458
+ async?: boolean;
438
459
  execute(input: unknown): unknown | Promise<unknown>;
439
460
  }
440
461
  interface ServeCeServiceHandle {
package/dist/index.d.ts CHANGED
@@ -346,6 +346,18 @@ declare class CeServiceAgent {
346
346
  stopHeartbeat(): void;
347
347
  /** DELETE /v1/instances/{id} — graceful deregister. */
348
348
  deregister(): Promise<void>;
349
+ /**
350
+ * Report the outcome of an async action (server-ce#41 Stage B):
351
+ * POST /v1/instances/{instanceId}/commands/{commandId}/result using the
352
+ * per-instance access token. `outcome.result` carries the action payload for
353
+ * COMPLETED; errorCode/errorMessage describe a FAILED run.
354
+ */
355
+ reportCommandResult(commandId: string, outcome: {
356
+ status: "COMPLETED" | "FAILED";
357
+ result?: Record<string, unknown>;
358
+ errorCode?: string;
359
+ errorMessage?: string;
360
+ }): Promise<void>;
349
361
  /**
350
362
  * Validate an inbound message, run the handler, and build the reply message
351
363
  * (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
@@ -435,6 +447,15 @@ interface CeServiceActionDefinition {
435
447
  * current state. Advertised as `requiresPrepare: true` in the action list.
436
448
  */
437
449
  prepare?(input?: unknown): CeActionPrepareResult | Promise<CeActionPrepareResult>;
450
+ /**
451
+ * Long-running action (server-ce#41 Stage B). When true and the pushed
452
+ * execute carries an `x-xtrape-command-id` header, the listener replies
453
+ * 202 { status: "RUNNING", commandId } immediately, runs `execute` in the
454
+ * background, and reports the outcome via
455
+ * `CeServiceAgent.reportCommandResult`. Without the header (older server-ce)
456
+ * the action falls back to synchronous execution.
457
+ */
458
+ async?: boolean;
438
459
  execute(input: unknown): unknown | Promise<unknown>;
439
460
  }
440
461
  interface ServeCeServiceHandle {
package/dist/index.js CHANGED
@@ -512,6 +512,27 @@ var CeServiceAgent = class {
512
512
  ...this.lifecycleAuthHeaders() ? { headers: this.lifecycleAuthHeaders() } : {}
513
513
  });
514
514
  }
515
+ /**
516
+ * Report the outcome of an async action (server-ce#41 Stage B):
517
+ * POST /v1/instances/{instanceId}/commands/{commandId}/result using the
518
+ * per-instance access token. `outcome.result` carries the action payload for
519
+ * COMPLETED; errorCode/errorMessage describe a FAILED run.
520
+ */
521
+ async reportCommandResult(commandId, outcome) {
522
+ const body = {
523
+ contractVersion: CE_CONTRACT_VERSION,
524
+ commandId,
525
+ status: outcome.status,
526
+ ...outcome.result !== void 0 ? { result: outcome.result } : {},
527
+ ...outcome.errorCode ? { errorCode: outcome.errorCode } : {},
528
+ ...outcome.errorMessage ? { errorMessage: outcome.errorMessage } : {}
529
+ };
530
+ await this.post(
531
+ `/v1/instances/${encodeURIComponent(this.instanceId)}/commands/${encodeURIComponent(commandId)}/result`,
532
+ body,
533
+ this.lifecycleAuthHeaders()
534
+ );
535
+ }
515
536
  /**
516
537
  * Validate an inbound message, run the handler, and build the reply message
517
538
  * (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
@@ -567,6 +588,7 @@ var CeServiceAgent = class {
567
588
 
568
589
  // src/ce/serve.ts
569
590
  import { createServer } from "http";
591
+ import { CE_CONTRACT_VERSION as CE_CONTRACT_VERSION2 } from "@xtrape/capsule-contracts-node";
570
592
  function send(res, status, body) {
571
593
  res.writeHead(status, { "content-type": "application/json" });
572
594
  res.end(JSON.stringify(body));
@@ -677,6 +699,7 @@ async function serveCeService(opts) {
677
699
  category: action.category,
678
700
  order: action.order,
679
701
  requiresPrepare: typeof action.prepare === "function" ? true : void 0,
702
+ async: action.async === true ? true : void 0,
680
703
  inputSchema: action.inputSchema
681
704
  }))
682
705
  );
@@ -735,6 +758,33 @@ async function serveCeService(opts) {
735
758
  });
736
759
  return;
737
760
  }
761
+ const commandIdRaw = req.headers["x-xtrape-command-id"];
762
+ const commandId = Array.isArray(commandIdRaw) ? commandIdRaw[0] : commandIdRaw;
763
+ if (action.async === true && typeof commandId === "string" && commandId.length > 0 && agent) {
764
+ const reporter = agent;
765
+ send(res, 202, { contractVersion: CE_CONTRACT_VERSION2, status: "RUNNING", commandId });
766
+ void (async () => {
767
+ let outcome;
768
+ try {
769
+ const result = await action.execute(payload);
770
+ outcome = {
771
+ status: "COMPLETED",
772
+ ...result !== void 0 && result !== null && typeof result === "object" && !Array.isArray(result) ? { result } : result !== void 0 ? { result: { value: result } } : {}
773
+ };
774
+ } catch (error) {
775
+ outcome = {
776
+ status: "FAILED",
777
+ errorCode: "ACTION_FAILED",
778
+ errorMessage: error instanceof Error ? error.message : "action handler failed"
779
+ };
780
+ }
781
+ try {
782
+ await reporter.reportCommandResult(commandId, outcome);
783
+ } catch {
784
+ }
785
+ })();
786
+ return;
787
+ }
738
788
  try {
739
789
  send(res, 200, await action.execute(payload));
740
790
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtrape/capsule-agent-node",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Node.js Embedded Agent SDK for reporting Workers to the Xtrape control plane.",
5
5
  "keywords": [
6
6
  "xtrape",
@@ -44,7 +44,7 @@
44
44
  "lint": "tsc --noEmit"
45
45
  },
46
46
  "dependencies": {
47
- "@xtrape/capsule-contracts-node": "^0.6.0"
47
+ "@xtrape/capsule-contracts-node": "^0.7.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "tsup": "^8.3.5",