agent-relay 11.8.4 → 11.8.5

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.
Files changed (2) hide show
  1. package/dist/index.cjs +63 -13
  2. package/package.json +9 -9
package/dist/index.cjs CHANGED
@@ -20644,8 +20644,13 @@ function placementActionName(capability) {
20644
20644
  function placementActionInput(input, placement) {
20645
20645
  const payload = { ...input ?? {} };
20646
20646
  payload.capability = placement.capability;
20647
- payload.node = placement.node;
20648
- payload.target_node = placement.node;
20647
+ if (placement.node) {
20648
+ payload.node = placement.node;
20649
+ payload.target_node = placement.node;
20650
+ } else {
20651
+ delete payload.node;
20652
+ delete payload.target_node;
20653
+ }
20649
20654
  if (placement.repo)
20650
20655
  payload.repo = placement.repo;
20651
20656
  if (placement.ttlMs > 0) {
@@ -20694,6 +20699,7 @@ var RelaycastMessagingClient = class {
20694
20699
  agentClient;
20695
20700
  selfNodeName;
20696
20701
  placementTtlMs;
20702
+ placementSandboxOnly;
20697
20703
  maxQueuedPlacements;
20698
20704
  placementLog;
20699
20705
  sessionRef;
@@ -20705,6 +20711,7 @@ var RelaycastMessagingClient = class {
20705
20711
  this.agentClient = options.agentClient ?? (options.agentToken ? this.relaycast.as?.(options.agentToken, options.agentClientOptions) : void 0);
20706
20712
  this.selfNodeName = options.selfNodeName;
20707
20713
  this.placementTtlMs = options.placementTtlMs ?? 60 * 60 * 1e3;
20714
+ this.placementSandboxOnly = options.placementSandboxOnly ?? false;
20708
20715
  this.maxQueuedPlacements = options.maxQueuedPlacements ?? 100;
20709
20716
  this.placementLog = options.placementLog;
20710
20717
  this.sessionRef = options.sessionRef === void 0 ? currentReplaySessionRef() : resolveReplaySessionRef(options.sessionRef);
@@ -21057,6 +21064,8 @@ var RelaycastMessagingClient = class {
21057
21064
  const capability = nonEmptyPlacement(input.capability, "placement capability");
21058
21065
  const repo = input.repo?.trim() || void 0;
21059
21066
  const targetNode = this.resolvePlacementNode(input.node, input.selfNodeName);
21067
+ const sandboxOnly = input.sandboxOnly ?? this.placementSandboxOnly;
21068
+ const failFast = input.failFast ?? true;
21060
21069
  const ttlMs = Math.max(0, input.ttlMs ?? input.ttlOverrideMs ?? this.placementTtlMs);
21061
21070
  const pollIntervalMs = Math.max(25, input.pollIntervalMs ?? 1e3);
21062
21071
  const startedAt = Date.now();
@@ -21065,19 +21074,22 @@ var RelaycastMessagingClient = class {
21065
21074
  try {
21066
21075
  while (true) {
21067
21076
  attempts += 1;
21068
- const decision = await this.selectPlacementNode({ capability, repo, targetNode });
21077
+ const decision = await this.selectPlacementNode({ capability, repo, targetNode, sandboxOnly });
21069
21078
  if (decision.node) {
21070
21079
  const actionName = input.actionName ?? placementActionName(capability);
21080
+ const clientMustTarget = Boolean(targetNode || repo || sandboxOnly);
21071
21081
  const actionInput = placementActionInput(input.input, {
21072
21082
  capability,
21073
- node: decision.node.name,
21083
+ ...clientMustTarget ? { node: decision.node.name } : {},
21074
21084
  repo,
21075
21085
  ttlMs
21076
21086
  });
21077
21087
  const ack = await this.commands.invoke(actionName, actionInput);
21088
+ const placedNode = clientMustTarget ? decision.node : await this.resolvePlacementAckNode(ack, capability);
21089
+ const placedNodeLabel = placedNode?.name ?? ack.dispatchedNodeId ?? ack.handlerNodeId ?? "engine-selected node";
21078
21090
  const confirmation = input.confirm ? await this.confirmPlacementInvocation(actionName, ack, {
21079
21091
  capability,
21080
- node: decision.node.name,
21092
+ node: placedNodeLabel,
21081
21093
  repo,
21082
21094
  attempts,
21083
21095
  // Defaults and validation live in confirmPlacementInvocation
@@ -21087,10 +21099,10 @@ var RelaycastMessagingClient = class {
21087
21099
  }) : void 0;
21088
21100
  return {
21089
21101
  ...ack,
21090
- node: decision.node,
21102
+ ...placedNode ? { node: placedNode } : {},
21091
21103
  placement: {
21092
21104
  capability,
21093
- node: decision.node.name,
21105
+ ...placedNode ? { node: placedNode.name } : {},
21094
21106
  ...repo ? { repo } : {},
21095
21107
  attempts,
21096
21108
  queued,
@@ -21108,9 +21120,9 @@ var RelaycastMessagingClient = class {
21108
21120
  attempts
21109
21121
  });
21110
21122
  }
21111
- if (input.failFast || Date.now() - startedAt >= ttlMs) {
21112
- const code = decision.reconcileReason === "unmapped_repo" ? "unmapped_repo" : "placement_ttl_expired";
21113
- const message = code === "unmapped_repo" ? `${decision.message}; no node maps the requested repo` : `${decision.message}; placement TTL expired`;
21123
+ if (failFast || Date.now() - startedAt >= ttlMs) {
21124
+ const code = failFast ? decision.reconcileReason === "unmapped_repo" ? "unmapped_repo" : decision.reconcileReason === "target_offline" ? "node_unavailable" : decision.reconcileReason === "sandbox_policy_mismatch" ? "sandbox_policy_mismatch" : "no_eligible_node" : decision.reconcileReason === "unmapped_repo" ? "unmapped_repo" : "placement_ttl_expired";
21125
+ const message = code === "unmapped_repo" ? `${decision.message}; no node maps the requested repo` : failFast ? `${decision.message}; placement failed fast` : `${decision.message}; placement TTL expired`;
21114
21126
  await this.reconcilePlacement(input, {
21115
21127
  action: "failed",
21116
21128
  reason: decision.reconcileReason,
@@ -21270,9 +21282,17 @@ var RelaycastMessagingClient = class {
21270
21282
  reconcileReason: "no_eligible_node"
21271
21283
  };
21272
21284
  }
21273
- if (!node.live) {
21285
+ if (input.sandboxOnly && !this.nodeIsCloudSandbox(node)) {
21286
+ return {
21287
+ message: `Placement rejected: node "${node.name}" is not a Cloud sandbox`,
21288
+ hardFail: true,
21289
+ reason: "sandbox_policy_mismatch",
21290
+ reconcileReason: "sandbox_policy_mismatch"
21291
+ };
21292
+ }
21293
+ if (!this.nodeIsPlacementReady(node)) {
21274
21294
  return {
21275
- message: `Placement queued: target node "${node.name}" is offline`,
21295
+ message: `Placement queued: target node "${node.name}" is not placement-ready`,
21276
21296
  reconcileReason: "target_offline"
21277
21297
  };
21278
21298
  }
@@ -21286,10 +21306,17 @@ var RelaycastMessagingClient = class {
21286
21306
  }
21287
21307
  const nodes = await this.nodes.list({ capability: input.capability });
21288
21308
  const capable = nodes.filter((node) => this.nodeHasCapability(node, input.capability));
21289
- const live = capable.filter((node) => node.live);
21309
+ const policyEligible = input.sandboxOnly ? capable.filter((node) => this.nodeIsCloudSandbox(node)) : capable;
21310
+ const live = policyEligible.filter((node) => this.nodeIsPlacementReady(node));
21290
21311
  const eligible = live.filter((node) => this.nodeMapsRepo(node, input.repo));
21291
21312
  if (eligible[0])
21292
21313
  return { node: eligible[0] };
21314
+ if (input.sandboxOnly) {
21315
+ return {
21316
+ message: `Placement queued: no placement-ready Cloud sandbox advertises capability "${input.capability}"`,
21317
+ reconcileReason: "sandbox_policy_mismatch"
21318
+ };
21319
+ }
21293
21320
  if (input.repo && live.length > 0) {
21294
21321
  return {
21295
21322
  message: `Placement queued: no live node advertising "${input.capability}" maps repo "${input.repo}"`,
@@ -21304,6 +21331,29 @@ var RelaycastMessagingClient = class {
21304
21331
  nodeHasCapability(node, capability) {
21305
21332
  return node.capabilities.some((item) => item.name === capability);
21306
21333
  }
21334
+ /** Return true only when the fresh roster reports a usable action handler. */
21335
+ nodeIsPlacementReady(node) {
21336
+ return node.status === "online" && node.live === true && node.handlersLive === true;
21337
+ }
21338
+ /** Match the stable Cloud JIT provenance tag without relying on disposable node names. */
21339
+ nodeIsCloudSandbox(node) {
21340
+ return Boolean(node.tags?.some((tag) => /^cloud:node-type:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?-jit$/.test(tag)));
21341
+ }
21342
+ async resolvePlacementAckNode(ack, capability) {
21343
+ const nodeId = ack.dispatchedNodeId ?? ack.handlerNodeId;
21344
+ if (nodeId) {
21345
+ try {
21346
+ const nodes = await this.nodes.list({ capability });
21347
+ return nodes.find((candidate) => candidate.id === nodeId || candidate.nodeId === nodeId);
21348
+ } catch (error51) {
21349
+ try {
21350
+ this.placementLog?.(`[placement] dispatch ${ack.invocationId ?? "unknown"} accepted by ${nodeId}, but roster metadata could not be refreshed: ${error51 instanceof Error ? error51.message : String(error51)}`);
21351
+ } catch {
21352
+ }
21353
+ }
21354
+ }
21355
+ return void 0;
21356
+ }
21307
21357
  nodeMapsRepo(node, repo) {
21308
21358
  if (!repo)
21309
21359
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay",
3
- "version": "11.8.4",
3
+ "version": "11.8.5",
4
4
  "description": "Real-time agent-to-agent communication system",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -43,14 +43,14 @@
43
43
  "pack:validate": "npm pack --dry-run"
44
44
  },
45
45
  "dependencies": {
46
- "@agent-relay/cloud": "11.8.4",
47
- "@agent-relay/config": "11.8.4",
48
- "@agent-relay/fleet": "11.8.4",
49
- "@agent-relay/harness-driver": "11.8.4",
50
- "@agent-relay/harnesses": "11.8.4",
51
- "@agent-relay/sdk": "11.8.4",
52
- "@agent-relay/session": "11.8.4",
53
- "@agent-relay/utils": "11.8.4",
46
+ "@agent-relay/cloud": "11.8.5",
47
+ "@agent-relay/config": "11.8.5",
48
+ "@agent-relay/fleet": "11.8.5",
49
+ "@agent-relay/harness-driver": "11.8.5",
50
+ "@agent-relay/harnesses": "11.8.5",
51
+ "@agent-relay/sdk": "11.8.5",
52
+ "@agent-relay/session": "11.8.5",
53
+ "@agent-relay/utils": "11.8.5",
54
54
  "@modelcontextprotocol/sdk": "^1.23.0",
55
55
  "@relayfile/client": "^0.10.27",
56
56
  "@relayflows/cli": "1.0.1",