@xtrape/capsule-agent-node 0.6.1 → 0.6.2

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
@@ -88,6 +88,38 @@ const svc = await serveCeService({
88
88
  process.on("SIGTERM", () => void svc.close()); // stop heartbeat + deregister + close
89
89
  ```
90
90
 
91
+ ### CE service actions
92
+
93
+ Pass `actions` to expose operator-triggerable operations over `GET /actions`,
94
+ `POST /actions/:name/prepare`, and `POST /actions/:name/execute`:
95
+
96
+ ```ts
97
+ const svc = await serveCeService({
98
+ // ...as above
99
+ actions: [
100
+ {
101
+ name: "relogin",
102
+ label: "Re-login account",
103
+ // Optional two-phase prepare: fetch live defaults/current state before
104
+ // the operator fills the form (advertised as requiresPrepare: true).
105
+ prepare: async () => ({ initialPayload: { accountId: "acc-1" } }),
106
+ execute: async (input) => ({ ok: true }),
107
+ },
108
+ {
109
+ name: "full-sync",
110
+ label: "Full re-sync",
111
+ // Long-running action (server-ce#41 Stage B): when the pushed execute
112
+ // carries x-xtrape-command-id, the SDK replies 202 RUNNING immediately,
113
+ // runs execute in the background, and reports the outcome to server-ce
114
+ // with the per-instance access token. Against an older server-ce
115
+ // (no header) the action falls back to synchronous execution.
116
+ async: true,
117
+ execute: async (input) => runFullSync(input),
118
+ },
119
+ ],
120
+ });
121
+ ```
122
+
91
123
  ### Lower-level agent
92
124
 
93
125
  When the service already runs its own HTTP server, use `CeServiceAgent` directly:
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.6.2",
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.6.1"
48
48
  },
49
49
  "devDependencies": {
50
50
  "tsup": "^8.3.5",