@agent-play/sdk 3.0.2 → 3.2.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/dist/index.js CHANGED
@@ -1,20 +1,31 @@
1
+ import {
2
+ MINIMUM_PLAY_WORLD_BOUNDS,
3
+ PLAYER_CHAIN_GENESIS_STABLE_KEY,
4
+ PLAYER_CHAIN_HEADER_STABLE_KEY,
5
+ boundsContain,
6
+ clampWorldPosition,
7
+ expandBoundsToMinimumPlayArea,
8
+ mergeSnapshotWithPlayerChainNode,
9
+ parseAgentOccupantRow,
10
+ parseHumanOccupantRow,
11
+ parseMcpOccupantRow,
12
+ parsePlayerChainFanoutNotify,
13
+ parsePlayerChainFanoutNotifyFromSsePayload,
14
+ parsePlayerChainNodeRpcBody,
15
+ sortNodeRefsForSerializedFetch
16
+ } from "./chunk-VI2MTCE7.js";
17
+
1
18
  // src/world-events.ts
19
+ var SESSION_CONNECTED_EVENT = "session:connected";
20
+ var SESSION_INVALID_EVENT = "session:invalid";
21
+ var SESSION_CLOSED_EVENT = "session:closed";
22
+ var SESSION_SSE_OPEN_EVENT = "session:sse_open";
23
+ var SESSION_SSE_ERROR_EVENT = "session:sse_error";
2
24
  var PLAYER_ADDED_EVENT = "world:player_added";
3
25
  var WORLD_INTERACTION_EVENT = "world:interaction";
4
26
  var WORLD_AGENT_SIGNAL_EVENT = "world:agent_signal";
5
27
  var WORLD_JOURNEY_EVENT = "world:journey";
6
28
 
7
- // src/lib/world-bounds.ts
8
- function clampWorldPosition(p, bounds) {
9
- return {
10
- x: Math.min(Math.max(p.x, bounds.minX), bounds.maxX),
11
- y: Math.min(Math.max(p.y, bounds.minY), bounds.maxY)
12
- };
13
- }
14
- function boundsContain(bounds, p) {
15
- return p.x >= bounds.minX && p.x <= bounds.maxX && p.y >= bounds.minY && p.y <= bounds.maxY;
16
- }
17
-
18
29
  // src/lib/agent-play-debug.ts
19
30
  var configuredDebug;
20
31
  function configureAgentPlayDebug(opts) {
@@ -73,6 +84,47 @@ function formatMissingChatToolError() {
73
84
  ' Tools whose names start with "assist_" are listed as assist actions on the watch UI; give each a Zod object schema so parameters can be shown in the UI.'
74
85
  ].join("\n");
75
86
  }
87
+ function unwrapZodCell(cell) {
88
+ let current = cell;
89
+ for (let depth = 0; depth < 32; depth++) {
90
+ if (current === null || typeof current !== "object") {
91
+ return current;
92
+ }
93
+ const def = current._def;
94
+ if (!def || typeof def.typeName !== "string") {
95
+ return current;
96
+ }
97
+ const { typeName } = def;
98
+ if (typeName === "ZodOptional" || typeName === "ZodNullable" || typeName === "ZodDefault") {
99
+ const inner = def.innerType;
100
+ current = inner !== null && typeof inner === "object" ? inner : void 0;
101
+ continue;
102
+ }
103
+ if (typeName === "ZodEffects") {
104
+ current = def.schema;
105
+ continue;
106
+ }
107
+ return current;
108
+ }
109
+ return current;
110
+ }
111
+ function fieldTypeFromZodCell(cell) {
112
+ const base = unwrapZodCell(cell);
113
+ if (base === null || typeof base !== "object") {
114
+ return "string";
115
+ }
116
+ const typeName = base._def?.typeName;
117
+ if (typeName === "ZodNumber") {
118
+ return "number";
119
+ }
120
+ if (typeName === "ZodBoolean") {
121
+ return "boolean";
122
+ }
123
+ if (typeName === "ZodString") {
124
+ return "string";
125
+ }
126
+ return "string";
127
+ }
76
128
  function parametersFromSchema(schema) {
77
129
  if (schema === null || typeof schema !== "object") {
78
130
  return {};
@@ -84,7 +136,10 @@ function parametersFromSchema(schema) {
84
136
  const shape = z._def.shape();
85
137
  const out = {};
86
138
  for (const key of Object.keys(shape)) {
87
- out[key] = { field: key };
139
+ out[key] = {
140
+ field: key,
141
+ fieldType: fieldTypeFromZodCell(shape[key])
142
+ };
88
143
  }
89
144
  return out;
90
145
  }
@@ -130,229 +185,93 @@ function langchainRegistration(agent) {
130
185
  };
131
186
  }
132
187
 
133
- // src/lib/parse-occupant-row.ts
134
- function parseAgentOccupantRow(raw) {
135
- if (typeof raw.agentId !== "string" || typeof raw.name !== "string") {
136
- throw new Error("occupant: agent needs agentId and name");
137
- }
138
- if (typeof raw.x !== "number" || typeof raw.y !== "number") {
139
- throw new Error("occupant: agent needs numeric x and y");
140
- }
141
- const base = {
142
- kind: "agent",
143
- agentId: raw.agentId,
144
- name: raw.name,
145
- x: raw.x,
146
- y: raw.y
147
- };
148
- let platform;
149
- if (typeof raw.platform === "string") {
150
- platform = raw.platform;
151
- } else if (typeof raw.agentType === "string") {
152
- platform = raw.agentType;
153
- }
154
- if (platform !== void 0) {
155
- return { ...base, platform };
156
- }
157
- return base;
158
- }
159
- function parseMcpOccupantRow(raw) {
160
- if (typeof raw.id !== "string" || typeof raw.name !== "string") {
161
- throw new Error("occupant: mcp needs id and name");
162
- }
163
- if (typeof raw.x !== "number" || typeof raw.y !== "number") {
164
- throw new Error("occupant: mcp needs numeric x and y");
165
- }
166
- const base = {
167
- kind: "mcp",
168
- id: raw.id,
169
- name: raw.name,
170
- x: raw.x,
171
- y: raw.y
172
- };
173
- if (typeof raw.url === "string") {
174
- return { ...base, url: raw.url };
175
- }
176
- return base;
177
- }
178
-
179
- // src/lib/world-chain-keys.ts
180
- var PLAYER_CHAIN_GENESIS_STABLE_KEY = "__genesis__";
181
- var PLAYER_CHAIN_HEADER_STABLE_KEY = "__header__";
188
+ // src/lib/remote-play-world.ts
189
+ import { randomUUID } from "crypto";
190
+ import {
191
+ deriveNodeIdFromPassword,
192
+ loadAgentPlayCredentialsFileFromPathSync,
193
+ loadRootKey,
194
+ nodeCredentialsMaterialFromHumanPassphrase,
195
+ resolveAgentPlayCredentialsPath
196
+ } from "@agent-play/node-tools";
197
+ import { HumanMessage } from "@langchain/core/messages";
198
+ import {
199
+ INTERCOM_RESPONSE_OP,
200
+ parseWorldIntercomEventPayload,
201
+ WORLD_INTERCOM_EVENT
202
+ } from "@agent-play/intercom";
182
203
 
183
- // src/lib/player-chain-merge.ts
184
- function isRecord(v) {
185
- return typeof v === "object" && v !== null;
186
- }
187
- function stableOccupantSortKey(occ) {
188
- if (occ.kind === "agent") {
189
- return `agent:${occ.agentId}`;
204
+ // src/lib/intercom-langchain-chat-result.ts
205
+ function contentToText(content) {
206
+ if (typeof content === "string") {
207
+ return content;
190
208
  }
191
- return `mcp:${occ.id}`;
192
- }
193
- function sortNodeRefsForSerializedFetch(nodes) {
194
- const removed = nodes.filter((n) => n.removed === true);
195
- const rest = nodes.filter((n) => n.removed !== true);
196
- removed.sort((a, b) => b.leafIndex - a.leafIndex);
197
- rest.sort((a, b) => a.leafIndex - b.leafIndex);
198
- return [...removed, ...rest];
199
- }
200
- function parsePlayerChainFanoutNotify(raw) {
201
- if (!isRecord(raw)) {
202
- return void 0;
203
- }
204
- if (typeof raw.updatedAt !== "string" || raw.updatedAt.length === 0) {
205
- return void 0;
206
- }
207
- if (!Array.isArray(raw.nodes)) {
208
- return void 0;
209
- }
210
- const nodes = [];
211
- for (const row of raw.nodes) {
212
- if (!isRecord(row)) {
213
- return void 0;
214
- }
215
- if (typeof row.stableKey !== "string" || row.stableKey.length === 0) {
216
- return void 0;
217
- }
218
- if (typeof row.leafIndex !== "number" || !Number.isFinite(row.leafIndex)) {
219
- return void 0;
220
- }
221
- const ref = {
222
- stableKey: row.stableKey,
223
- leafIndex: row.leafIndex
224
- };
225
- if (row.removed === true) {
226
- ref.removed = true;
227
- }
228
- if (typeof row.updatedAt === "string" && row.updatedAt.length > 0) {
229
- ref.updatedAt = row.updatedAt;
230
- }
231
- nodes.push(ref);
209
+ if (Array.isArray(content)) {
210
+ return content.map((block) => {
211
+ if (typeof block === "string") {
212
+ return block;
213
+ }
214
+ if (typeof block === "object" && block !== null && "text" in block) {
215
+ const t = block.text;
216
+ return typeof t === "string" ? t : "";
217
+ }
218
+ return "";
219
+ }).join("");
232
220
  }
233
- return { updatedAt: raw.updatedAt, nodes };
234
- }
235
- function parsePlayerChainFanoutNotifyFromSsePayload(sseData) {
236
- if (!isRecord(sseData)) {
237
- return void 0;
221
+ if (content !== null && typeof content === "object" && "text" in content) {
222
+ const t = content.text;
223
+ return typeof t === "string" ? t : JSON.stringify(content);
238
224
  }
239
- return parsePlayerChainFanoutNotify(sseData.playerChainNotify);
225
+ return JSON.stringify(content);
240
226
  }
241
- function parsePlayerChainNodeRpcBody(json) {
242
- if (!isRecord(json) || !isRecord(json.node)) {
243
- throw new Error("getPlayerChainNode: invalid response shape");
244
- }
245
- const n = json.node;
246
- if (n.kind === "genesis") {
247
- if (n.stableKey !== PLAYER_CHAIN_GENESIS_STABLE_KEY || typeof n.text !== "string") {
248
- throw new Error("getPlayerChainNode: invalid genesis node");
249
- }
250
- return {
251
- kind: "genesis",
252
- stableKey: PLAYER_CHAIN_GENESIS_STABLE_KEY,
253
- text: n.text
254
- };
227
+ function intercomResultRecordFromLangChainInvokeOutput(output) {
228
+ if (output === null || typeof output !== "object") {
229
+ return { mode: "chat", message: String(output) };
255
230
  }
256
- if (n.kind === "header") {
257
- if (n.stableKey !== PLAYER_CHAIN_HEADER_STABLE_KEY || typeof n.sid !== "string") {
258
- throw new Error("getPlayerChainNode: invalid header node");
231
+ const o = output;
232
+ if ("structuredResponse" in o && o.structuredResponse !== void 0) {
233
+ const sr = o.structuredResponse;
234
+ if (sr !== null && typeof sr === "object" && !Array.isArray(sr)) {
235
+ return { ...sr };
259
236
  }
260
- const b = n.bounds;
261
- if (!isRecord(b)) {
262
- throw new Error("getPlayerChainNode: invalid header bounds");
263
- }
264
- const { minX, minY, maxX, maxY } = b;
265
- if (typeof minX !== "number" || typeof minY !== "number" || typeof maxX !== "number" || typeof maxY !== "number") {
266
- throw new Error("getPlayerChainNode: invalid header bounds");
267
- }
268
- return {
269
- kind: "header",
270
- stableKey: PLAYER_CHAIN_HEADER_STABLE_KEY,
271
- sid: n.sid,
272
- bounds: { minX, minY, maxX, maxY }
273
- };
274
- }
275
- if (n.kind !== "occupant") {
276
- throw new Error("getPlayerChainNode: unknown node kind");
277
- }
278
- if (typeof n.stableKey !== "string" || n.stableKey.length === 0) {
279
- throw new Error("getPlayerChainNode: invalid occupant stableKey");
280
- }
281
- if (n.removed === true) {
282
- return { kind: "occupant", stableKey: n.stableKey, removed: true };
283
- }
284
- const occ = n.occupant;
285
- if (!isRecord(occ) || occ.kind !== "agent" && occ.kind !== "mcp") {
286
- throw new Error("getPlayerChainNode: invalid occupant payload");
237
+ return { mode: "chat", structuredResponse: sr };
287
238
  }
288
- const occupant = occ.kind === "agent" ? parseAgentOccupantRow(occ) : parseMcpOccupantRow(occ);
289
- return {
290
- kind: "occupant",
291
- stableKey: n.stableKey,
292
- removed: false,
293
- occupant
294
- };
295
- }
296
- function mergeSnapshotWithPlayerChainNode(snapshot, node) {
297
- if (node.kind === "genesis") {
298
- return snapshot;
299
- }
300
- if (node.kind === "header") {
301
- return {
302
- ...snapshot,
303
- sid: node.sid,
304
- worldMap: {
305
- ...snapshot.worldMap,
306
- bounds: node.bounds
239
+ const messages = o.messages;
240
+ if (Array.isArray(messages) && messages.length > 0) {
241
+ const last = messages[messages.length - 1];
242
+ if (last !== null && typeof last === "object" && "content" in last) {
243
+ const text = contentToText(last.content);
244
+ if (text.trim().length > 0) {
245
+ return { mode: "chat", message: text };
307
246
  }
308
- };
309
- }
310
- if (node.removed === true) {
311
- return {
312
- ...snapshot,
313
- worldMap: {
314
- ...snapshot.worldMap,
315
- occupants: snapshot.worldMap.occupants.filter(
316
- (o) => stableOccupantSortKey(o) !== node.stableKey
317
- )
318
- }
319
- };
320
- }
321
- if (node.removed !== false) {
322
- throw new Error("mergeSnapshotWithPlayerChainNode: invalid occupant node");
323
- }
324
- const occ = node.occupant;
325
- const key = stableOccupantSortKey(occ);
326
- const occupants = snapshot.worldMap.occupants.filter(
327
- (o) => stableOccupantSortKey(o) !== key
328
- );
329
- return {
330
- ...snapshot,
331
- worldMap: {
332
- ...snapshot.worldMap,
333
- occupants: [...occupants, occ]
334
247
  }
335
- };
248
+ }
249
+ return { mode: "chat", output: o };
336
250
  }
337
251
 
338
252
  // src/lib/remote-play-world.ts
339
- function formatMissingApiKeyError() {
253
+ var PLAYER_CONNECTION_HEARTBEAT_MAX_ATTEMPTS = 10;
254
+ var PLAYER_CONNECTION_HEARTBEAT_RETRY_DELAY_MS = 1e4;
255
+ function formatMissingCredentialsError() {
340
256
  return [
341
- "RemotePlayWorld: options.apiKey is required.",
342
- "",
343
- " Register an agent with `agent-play create` (after `agent-play login`) and use the printed API key.",
344
- " Pass it here so addPlayer can authenticate against the server repository when Redis is enabled.",
345
- " If the server has no agent repository (local dev), still pass a non-empty placeholder string."
346
- ].join("\n");
257
+ "RemotePlayWorld: provide nodeCredentials: { rootKey, passw },",
258
+ "or run agent-play create-main-node so ~/.agent-play/credentials.json exists."
259
+ ].join(" ");
260
+ }
261
+ function formatMissingBaseUrlError() {
262
+ return [
263
+ "RemotePlayWorld: baseUrl is missing.",
264
+ "Provide options.baseUrl, or ensure credentials.json contains serverUrl."
265
+ ].join(" ");
347
266
  }
348
267
  function normalizeBaseUrl(url) {
349
268
  return url.replace(/\/$/, "");
350
269
  }
351
- function isRecord2(v) {
270
+ function isRecord(v) {
352
271
  return typeof v === "object" && v !== null;
353
272
  }
354
273
  function parseBounds(raw) {
355
- if (!isRecord2(raw)) {
274
+ if (!isRecord(raw)) {
356
275
  throw new Error("getWorldSnapshot: worldMap.bounds must be an object");
357
276
  }
358
277
  const { minX, minY, maxX, maxY } = raw;
@@ -364,7 +283,7 @@ function parseBounds(raw) {
364
283
  return { minX, minY, maxX, maxY };
365
284
  }
366
285
  function parseWorldMap(raw) {
367
- if (!isRecord2(raw)) {
286
+ if (!isRecord(raw)) {
368
287
  throw new Error("getWorldSnapshot: worldMap must be an object");
369
288
  }
370
289
  const bounds = parseBounds(raw.bounds);
@@ -375,8 +294,10 @@ function parseWorldMap(raw) {
375
294
  const occupants = [];
376
295
  const coordKeys = /* @__PURE__ */ new Set();
377
296
  for (const row of occ) {
378
- if (!isRecord2(row) || row.kind !== "agent" && row.kind !== "mcp") {
379
- throw new Error("getWorldSnapshot: each occupant must have kind agent or mcp");
297
+ if (!isRecord(row) || row.kind !== "human" && row.kind !== "agent" && row.kind !== "mcp") {
298
+ throw new Error(
299
+ "getWorldSnapshot: each occupant must have kind human, agent, or mcp"
300
+ );
380
301
  }
381
302
  const xy = typeof row.x === "number" && typeof row.y === "number" ? `${row.x},${row.y}` : "";
382
303
  if (xy.length === 0) {
@@ -386,7 +307,9 @@ function parseWorldMap(raw) {
386
307
  throw new Error("getWorldSnapshot: duplicate world map coordinate");
387
308
  }
388
309
  coordKeys.add(xy);
389
- if (row.kind === "agent") {
310
+ if (row.kind === "human") {
311
+ occupants.push(parseHumanOccupantRow(row));
312
+ } else if (row.kind === "agent") {
390
313
  occupants.push(parseAgentOccupantRow(row));
391
314
  } else {
392
315
  occupants.push(parseMcpOccupantRow(row));
@@ -395,7 +318,7 @@ function parseWorldMap(raw) {
395
318
  return { bounds, occupants };
396
319
  }
397
320
  function parseAgentPlaySnapshot(snapshot) {
398
- if (!isRecord2(snapshot) || typeof snapshot.sid !== "string") {
321
+ if (!isRecord(snapshot) || typeof snapshot.sid !== "string") {
399
322
  throw new Error("getWorldSnapshot: invalid snapshot");
400
323
  }
401
324
  const worldMap = parseWorldMap(snapshot.worldMap);
@@ -403,7 +326,7 @@ function parseAgentPlaySnapshot(snapshot) {
403
326
  if ("mcpServers" in snapshot && Array.isArray(snapshot.mcpServers)) {
404
327
  const servers = [];
405
328
  for (const m of snapshot.mcpServers) {
406
- if (!isRecord2(m) || typeof m.id !== "string" || typeof m.name !== "string") {
329
+ if (!isRecord(m) || typeof m.id !== "string" || typeof m.name !== "string") {
407
330
  continue;
408
331
  }
409
332
  const row = {
@@ -418,24 +341,24 @@ function parseAgentPlaySnapshot(snapshot) {
418
341
  return out;
419
342
  }
420
343
  function parseRegisteredAgentSummary(raw) {
421
- if (!isRecord2(raw)) {
422
- throw new Error("addPlayer: registeredAgent missing");
344
+ if (!isRecord(raw)) {
345
+ throw new Error("registerAgent: registeredAgent missing");
423
346
  }
424
347
  if (typeof raw.agentId !== "string" || typeof raw.name !== "string") {
425
- throw new Error("addPlayer: registeredAgent.agentId and name required");
348
+ throw new Error("registerAgent: registeredAgent.agentId and name required");
426
349
  }
427
350
  if (!Array.isArray(raw.toolNames)) {
428
- throw new Error("addPlayer: registeredAgent.toolNames must be an array");
351
+ throw new Error("registerAgent: registeredAgent.toolNames must be an array");
429
352
  }
430
353
  const toolNames = [];
431
354
  for (const t of raw.toolNames) {
432
355
  if (typeof t !== "string") {
433
- throw new Error("addPlayer: registeredAgent.toolNames must be strings");
356
+ throw new Error("registerAgent: registeredAgent.toolNames must be strings");
434
357
  }
435
358
  toolNames.push(t);
436
359
  }
437
360
  if (typeof raw.zoneCount !== "number" || typeof raw.yieldCount !== "number" || typeof raw.flagged !== "boolean") {
438
- throw new Error("addPlayer: registeredAgent counters invalid");
361
+ throw new Error("registerAgent: registeredAgent counters invalid");
439
362
  }
440
363
  return {
441
364
  agentId: raw.agentId,
@@ -453,20 +376,89 @@ function formatInvalidHoldSecondsError() {
453
376
  " Example: await world.hold().for(3600)"
454
377
  ].join("\n");
455
378
  }
379
+ function getSseEventName(msg) {
380
+ if (typeof msg !== "object" || msg === null) {
381
+ return void 0;
382
+ }
383
+ const m = msg;
384
+ if (typeof m.event === "string" && m.event.length > 0) {
385
+ return m.event;
386
+ }
387
+ if (typeof m.type === "string" && m.type.length > 0) {
388
+ return m.type;
389
+ }
390
+ return void 0;
391
+ }
392
+ function invokeLangChainChatAgent(agent, input) {
393
+ if (typeof agent !== "object" || agent === null) {
394
+ throw new Error("intercom: chat agent must be a non-null object");
395
+ }
396
+ const inv = agent.invoke;
397
+ if (typeof inv !== "function") {
398
+ throw new Error("intercom: chat agent must have invoke()");
399
+ }
400
+ return Promise.resolve(
401
+ inv.call(agent, input)
402
+ );
403
+ }
404
+ function normalizeIntercomSubscribePlayerIds(options) {
405
+ const raw = "playerIds" in options ? [...options.playerIds] : [options.playerId];
406
+ return new Set(
407
+ raw.map((id) => id.trim()).filter((id) => id.length > 0)
408
+ );
409
+ }
456
410
  var RemotePlayWorld = class {
457
411
  apiBase;
458
- apiKey;
459
- authToken;
412
+ rootKey;
413
+ /** Node id derived from hashed passphrase material + root (main or agent node id). */
414
+ derivedNodeId;
415
+ /** Hex password material (`hashNodePassword` on normalized human phrase); sent as `password` for repository addAgent. */
416
+ password;
417
+ onSessionEvent;
418
+ transportLog;
460
419
  sid = null;
461
420
  closed = false;
462
421
  closeListeners = /* @__PURE__ */ new Set();
463
- constructor(options) {
464
- if (typeof options.apiKey !== "string" || options.apiKey.trim().length === 0) {
465
- throw new Error(formatMissingApiKeyError());
422
+ playerConnectionInfo = /* @__PURE__ */ new Map();
423
+ constructor(options = {}) {
424
+ const creds = loadAgentPlayCredentialsFileFromPathSync(
425
+ resolveAgentPlayCredentialsPath()
426
+ );
427
+ const resolvedBaseUrl = options.baseUrl ?? creds?.serverUrl;
428
+ if (resolvedBaseUrl === void 0 || resolvedBaseUrl.trim().length === 0) {
429
+ throw new Error(formatMissingBaseUrlError());
430
+ }
431
+ this.apiBase = normalizeBaseUrl(resolvedBaseUrl);
432
+ this.onSessionEvent = options.onSessionEvent;
433
+ this.transportLog = options.logging === "on";
434
+ const nc = options.nodeCredentials ?? (creds === null ? void 0 : { rootKey: loadRootKey(), passw: creds.passw });
435
+ if (nc !== void 0 && typeof nc.rootKey === "string" && nc.rootKey.trim().length > 0 && typeof nc.passw === "string" && nc.passw.length > 0) {
436
+ this.rootKey = nc.rootKey.trim().toLowerCase();
437
+ const material = nodeCredentialsMaterialFromHumanPassphrase(nc.passw);
438
+ this.password = material;
439
+ this.derivedNodeId = deriveNodeIdFromPassword({
440
+ password: material,
441
+ rootKey: this.rootKey
442
+ });
443
+ return;
466
444
  }
467
- this.apiBase = normalizeBaseUrl(options.baseUrl);
468
- this.apiKey = options.apiKey.trim();
469
- this.authToken = options.authToken;
445
+ throw new Error(formatMissingCredentialsError());
446
+ }
447
+ logTransport(event, detail) {
448
+ if (!this.transportLog) {
449
+ return;
450
+ }
451
+ console.info(`[agent-play:RemotePlayWorld] ${event}`, detail);
452
+ }
453
+ truncateForLog(value, max = 1600) {
454
+ return value.length <= max ? value : `${value.slice(0, max)}\u2026`;
455
+ }
456
+ emitSessionEvent(event) {
457
+ this.logTransport("session:event", {
458
+ name: event.name,
459
+ detail: event.detail ?? {}
460
+ });
461
+ this.onSessionEvent?.(event);
470
462
  }
471
463
  onClose(handler) {
472
464
  this.closeListeners.add(handler);
@@ -488,8 +480,10 @@ var RemotePlayWorld = class {
488
480
  };
489
481
  }
490
482
  authHeaders() {
491
- if (this.authToken === void 0) return {};
492
- return { Authorization: `Bearer ${this.authToken}` };
483
+ return {
484
+ "x-node-id": this.derivedNodeId,
485
+ "x-node-passw": this.password
486
+ };
493
487
  }
494
488
  jsonHeaders() {
495
489
  return {
@@ -505,7 +499,51 @@ var RemotePlayWorld = class {
505
499
  }
506
500
  return fetch(input, { ...init, headers });
507
501
  }
508
- async connect() {
502
+ async validateNodeIdentity(options) {
503
+ const body = {
504
+ nodeId: options.nodeId,
505
+ rootKey: this.rootKey
506
+ };
507
+ if (options.mainNodeId !== void 0 && options.mainNodeId.trim().length > 0) {
508
+ body.mainNodeId = options.mainNodeId.trim();
509
+ }
510
+ const res = await fetch(`${this.apiBase}/api/nodes/validate`, {
511
+ method: "POST",
512
+ headers: this.jsonHeaders(),
513
+ body: JSON.stringify(body)
514
+ });
515
+ let json;
516
+ try {
517
+ json = await res.json();
518
+ } catch {
519
+ throw new Error(`node validation failed: ${String(res.status)} invalid JSON`);
520
+ }
521
+ if (!isRecord(json) || json.ok !== true) {
522
+ const reason = isRecord(json) && typeof json.reason === "string" ? json.reason : `HTTP ${String(res.status)}`;
523
+ throw new Error(`node validation failed: ${reason}`);
524
+ }
525
+ const nodeKind = isRecord(json) && typeof json.nodeKind === "string" ? json.nodeKind : void 0;
526
+ agentPlayDebug("remote-play-world", "node identity validated", {
527
+ nodeKind,
528
+ derivedNodeIdPrefix: `${this.derivedNodeId.slice(0, 8)}\u2026`
529
+ });
530
+ return nodeKind !== void 0 ? { nodeKind } : {};
531
+ }
532
+ /**
533
+ * Establishes the HTTP session via `GET /api/agent-play/session`. With {@link RemotePlayWorldConnectOptions.mainNodeId},
534
+ * validates node identity with `POST /api/nodes/validate` first.
535
+ */
536
+ async connect(options) {
537
+ const mainNodeIdOpt = options?.mainNodeId?.trim();
538
+ if (mainNodeIdOpt !== void 0 && mainNodeIdOpt.length > 0) {
539
+ const validation = await this.validateNodeIdentity({
540
+ nodeId: this.derivedNodeId,
541
+ mainNodeId: mainNodeIdOpt
542
+ });
543
+ console.info(
544
+ `[agent-play] Node identity validated (${validation.nodeKind ?? "unknown"}).`
545
+ );
546
+ }
509
547
  const res = await fetch(`${this.apiBase}/api/agent-play/session`, {
510
548
  headers: this.authHeaders()
511
549
  });
@@ -513,16 +551,38 @@ var RemotePlayWorld = class {
513
551
  throw new Error(`session failed: ${res.status}`);
514
552
  }
515
553
  const json = await res.json();
516
- if (!isRecord2(json) || typeof json.sid !== "string" || json.sid.length === 0) {
554
+ if (!isRecord(json) || typeof json.sid !== "string" || json.sid.length === 0) {
517
555
  throw new Error("session: invalid response");
518
556
  }
519
557
  this.sid = json.sid;
558
+ this.logTransport("connect:session", {
559
+ sid: this.sid,
560
+ apiBase: this.apiBase
561
+ });
562
+ this.emitSessionEvent({
563
+ name: SESSION_CONNECTED_EVENT,
564
+ detail: { sid: this.sid }
565
+ });
520
566
  }
521
567
  async close() {
522
568
  if (this.closed) {
523
569
  return;
524
570
  }
571
+ for (const [playerId, connection] of Array.from(
572
+ this.playerConnectionInfo.entries()
573
+ )) {
574
+ clearInterval(connection.timer);
575
+ try {
576
+ await this.disconnectPlayerConnection({
577
+ playerId,
578
+ connectionId: connection.connectionId
579
+ });
580
+ } catch {
581
+ }
582
+ }
583
+ this.playerConnectionInfo.clear();
525
584
  this.closed = true;
585
+ this.emitSessionEvent({ name: SESSION_CLOSED_EVENT });
526
586
  for (const handler of Array.from(this.closeListeners)) {
527
587
  try {
528
588
  handler();
@@ -557,7 +617,7 @@ var RemotePlayWorld = class {
557
617
  } catch {
558
618
  throw new Error("getWorldSnapshot: invalid JSON");
559
619
  }
560
- if (!isRecord2(json) || !("snapshot" in json)) {
620
+ if (!isRecord(json) || !("snapshot" in json)) {
561
621
  throw new Error("getWorldSnapshot: invalid response shape");
562
622
  }
563
623
  return parseAgentPlaySnapshot(json.snapshot);
@@ -600,29 +660,65 @@ var RemotePlayWorld = class {
600
660
  const { createEventSource } = await import("eventsource-client");
601
661
  let snapshot = await this.getWorldSnapshot();
602
662
  callbacks.onSnapshot(snapshot);
663
+ const sseUrl = `${this.apiBase}/api/agent-play/events?sid=${encodeURIComponent(
664
+ this.getSessionId()
665
+ )}`;
666
+ this.logTransport("subscribeWorldState:sse_open", { sseUrl });
603
667
  const source = createEventSource({
604
- url: `${this.apiBase}/api/agent-play/events?sid=${encodeURIComponent(
605
- this.getSessionId()
606
- )}`,
668
+ url: sseUrl,
607
669
  fetch: (input, init) => this.mergeAuthFetch(input, init)
608
670
  });
609
671
  closeSource = () => {
610
672
  source.close();
611
673
  };
612
674
  for await (const msg of source) {
675
+ const eventType = typeof msg === "object" && msg !== null && "type" in msg && typeof msg.type === "string" ? msg.type : "(no type)";
613
676
  if (typeof msg.data !== "string") {
677
+ this.logTransport("sse:worldState:skip", {
678
+ reason: "data_not_string",
679
+ eventType
680
+ });
614
681
  continue;
615
682
  }
683
+ this.logTransport("sse:worldState:message", {
684
+ eventType,
685
+ dataLength: msg.data.length,
686
+ dataPreview: this.truncateForLog(msg.data)
687
+ });
616
688
  let data;
617
689
  try {
618
690
  data = JSON.parse(msg.data);
619
691
  } catch {
692
+ this.logTransport("sse:worldState:parseJson", {
693
+ reason: "invalid_json",
694
+ eventType
695
+ });
620
696
  continue;
621
697
  }
698
+ if (callbacks.onIntercomEvent) {
699
+ try {
700
+ const inter = parseWorldIntercomEventPayload(data);
701
+ this.logTransport("sse:worldState:intercom", {
702
+ status: inter.status,
703
+ requestId: inter.requestId,
704
+ kind: inter.kind,
705
+ channelKey: inter.channelKey
706
+ });
707
+ callbacks.onIntercomEvent(inter);
708
+ } catch {
709
+ this.logTransport("sse:worldState:intercom", {
710
+ reason: "not_world_intercom_payload"
711
+ });
712
+ }
713
+ }
622
714
  const notify = parsePlayerChainFanoutNotifyFromSsePayload(data);
623
715
  if (notify === void 0 || notify.nodes.length === 0) {
624
716
  continue;
625
717
  }
718
+ this.logTransport("sse:worldState:playerChainNotify", {
719
+ nodeCount: notify.nodes.length,
720
+ stableKeys: notify.nodes.map((n) => n.stableKey)
721
+ });
626
722
  const ordered = sortNodeRefsForSerializedFetch(notify.nodes);
627
723
  for (const ref of ordered) {
628
724
  const node = await this.getPlayerChainNode(ref.stableKey);
@@ -631,21 +727,43 @@ var RemotePlayWorld = class {
631
727
  callbacks.onSnapshot(snapshot);
632
728
  }
633
729
  } catch (e) {
634
- callbacks.onError?.(
635
- e instanceof Error ? e : new Error(String(e))
636
- );
730
+ const err = e instanceof Error ? e : new Error(String(e));
731
+ this.logTransport("sse:worldState:error", {
732
+ message: err.message
733
+ });
734
+ callbacks.onError?.(err);
637
735
  }
638
736
  })();
639
737
  return {
640
738
  close: () => {
739
+ this.logTransport("subscribeWorldState:close", {});
641
740
  closeSource?.();
642
741
  void task;
643
742
  }
644
743
  };
645
744
  }
646
- async addPlayer(input) {
745
+ /**
746
+ * Registers an automation agent using **agent node id** (`nodeId`), sent to the server as `agentId`.
747
+ */
748
+ async addAgent(input) {
647
749
  const sid = this.getSessionId();
750
+ const effectiveMainNodeId = this.derivedNodeId;
751
+ const validation = await this.validateNodeIdentity({
752
+ nodeId: input.nodeId,
753
+ mainNodeId: effectiveMainNodeId
754
+ });
755
+ console.info(
756
+ [
757
+ "Agent Node Connection",
758
+ ` status : validated`,
759
+ ` nodeId : ${input.nodeId}`,
760
+ ` nodeKind : ${validation.nodeKind ?? "unknown"}`,
761
+ ` mainNode : ${effectiveMainNodeId}`
762
+ ].join("\n")
763
+ );
648
764
  const url = `${this.apiBase}/api/agent-play/players?sid=${encodeURIComponent(sid)}`;
765
+ const connectionId = randomUUID();
766
+ const leaseTtlSeconds = 45;
649
767
  const res = await fetch(url, {
650
768
  method: "POST",
651
769
  headers: this.jsonHeaders(),
@@ -653,29 +771,60 @@ var RemotePlayWorld = class {
653
771
  name: input.name,
654
772
  type: input.type,
655
773
  agent: input.agent,
656
- apiKey: this.apiKey,
657
- agentId: input.agentId
774
+ mainNodeId: effectiveMainNodeId,
775
+ password: this.password,
776
+ agentId: input.nodeId,
777
+ connectionId,
778
+ leaseTtlSeconds
658
779
  })
659
780
  });
660
781
  const bodyText = await res.text();
661
782
  if (!res.ok) {
662
- throw new Error(`addPlayer: ${res.status} ${bodyText}`);
783
+ throw new Error(`addAgent: ${res.status} ${bodyText}`);
663
784
  }
664
785
  let body;
665
786
  try {
666
787
  body = JSON.parse(bodyText);
667
788
  } catch {
668
- throw new Error("addPlayer: invalid JSON");
789
+ throw new Error("addAgent: invalid JSON");
669
790
  }
670
- if (!isRecord2(body)) {
671
- throw new Error("addPlayer: invalid response shape");
791
+ if (!isRecord(body)) {
792
+ throw new Error("addAgent: invalid response shape");
672
793
  }
673
794
  const playerId = body.playerId;
674
795
  const previewUrl = body.previewUrl;
675
796
  if (typeof playerId !== "string" || typeof previewUrl !== "string") {
676
- throw new Error("addPlayer: missing playerId or previewUrl");
797
+ throw new Error("addAgent: missing playerId or previewUrl");
677
798
  }
678
799
  const registeredAgent = parseRegisteredAgentSummary(body.registeredAgent);
800
+ const bodyConnectionId = typeof body.connectionId === "string" && body.connectionId.length > 0 ? body.connectionId : connectionId;
801
+ const bodyLeaseTtlSeconds = typeof body.leaseTtlSeconds === "number" && Number.isFinite(body.leaseTtlSeconds) ? body.leaseTtlSeconds : leaseTtlSeconds;
802
+ const existingConnection = this.playerConnectionInfo.get(playerId);
803
+ if (existingConnection !== void 0) {
804
+ clearInterval(existingConnection.timer);
805
+ }
806
+ const timer = setInterval(() => {
807
+ void this.heartbeatPlayerConnection({
808
+ playerId,
809
+ connectionId: bodyConnectionId,
810
+ leaseTtlSeconds: bodyLeaseTtlSeconds
811
+ }).catch((err) => {
812
+ const message = err instanceof Error ? err.message : String(err);
813
+ console.error("[agent-play:RemotePlayWorld] heartbeat:exhausted", {
814
+ playerId,
815
+ connectionId: bodyConnectionId,
816
+ leaseTtlSeconds: bodyLeaseTtlSeconds,
817
+ attempts: PLAYER_CONNECTION_HEARTBEAT_MAX_ATTEMPTS,
818
+ retryDelayMs: PLAYER_CONNECTION_HEARTBEAT_RETRY_DELAY_MS,
819
+ error: message
820
+ });
821
+ });
822
+ }, 12e3);
823
+ this.playerConnectionInfo.set(playerId, {
824
+ connectionId: bodyConnectionId,
825
+ leaseTtlSeconds: bodyLeaseTtlSeconds,
826
+ timer
827
+ });
679
828
  const now = /* @__PURE__ */ new Date();
680
829
  return {
681
830
  id: playerId,
@@ -684,8 +833,34 @@ var RemotePlayWorld = class {
684
833
  createdAt: now,
685
834
  updatedAt: now,
686
835
  previewUrl,
687
- registeredAgent
836
+ registeredAgent,
837
+ connectionId: bodyConnectionId,
838
+ leaseTtlSeconds: bodyLeaseTtlSeconds
839
+ };
840
+ }
841
+ /**
842
+ * @deprecated Use {@link addAgent} with `nodeId` (agent node id) for integrations and automation.
843
+ */
844
+ async addPlayer(input) {
845
+ const payload = {
846
+ name: input.name,
847
+ type: input.type,
848
+ agent: input.agent,
849
+ nodeId: input.agentId
688
850
  };
851
+ if (input.version !== void 0) {
852
+ payload.version = input.version;
853
+ }
854
+ if (input.createdAt !== void 0) {
855
+ payload.createdAt = input.createdAt;
856
+ }
857
+ if (input.updatedAt !== void 0) {
858
+ payload.updatedAt = input.updatedAt;
859
+ }
860
+ if (input.mainNodeId !== void 0) {
861
+ payload.mainNodeId = input.mainNodeId;
862
+ }
863
+ return this.addAgent(payload);
689
864
  }
690
865
  async recordInteraction(input) {
691
866
  await this.rpc("recordInteraction", {
@@ -697,6 +872,210 @@ var RemotePlayWorld = class {
697
872
  async recordJourney(playerId, journey) {
698
873
  await this.rpc("recordJourney", { playerId, journey });
699
874
  }
875
+ async sendIntercomResponse(payload) {
876
+ const sid = this.getSessionId();
877
+ const url = `${this.apiBase}/api/agent-play/sdk/rpc?sid=${encodeURIComponent(sid)}`;
878
+ this.logTransport("intercom:sendResponse:request", {
879
+ url,
880
+ requestId: payload.requestId,
881
+ toPlayerId: payload.toPlayerId,
882
+ fromPlayerId: payload.fromPlayerId,
883
+ kind: payload.kind,
884
+ status: payload.status,
885
+ toolName: payload.toolName,
886
+ error: payload.error,
887
+ resultPreview: payload.result !== void 0 ? this.truncateForLog(JSON.stringify(payload.result)) : void 0
888
+ });
889
+ const res = await fetch(url, {
890
+ method: "POST",
891
+ headers: this.jsonHeaders(),
892
+ body: JSON.stringify({ op: INTERCOM_RESPONSE_OP, payload })
893
+ });
894
+ const okText = await res.text();
895
+ this.logTransport("intercom:sendResponse:http", {
896
+ requestId: payload.requestId,
897
+ httpStatus: res.status,
898
+ bodyPreview: this.truncateForLog(okText)
899
+ });
900
+ if (!res.ok) {
901
+ throw new Error(`intercomResponse: ${res.status} ${okText}`);
902
+ }
903
+ }
904
+ /**
905
+ * Subscribes to the session SSE stream and handles **`forwarded`** intercom commands whose **`toPlayerId`** is in **`playerId`** or **`playerIds`**, invoking **`executeTool`** and posting **`intercomResponse`** (**`completed`** / **`failed`**).
906
+ * Uses a **single** SSE connection when **`playerIds`** lists multiple automation agents (recommended for several agents in one process).
907
+ * Not invoked automatically by {@link RemotePlayWorld.addAgent}.
908
+ */
909
+ subscribeIntercomCommands(options) {
910
+ const subscribed = normalizeIntercomSubscribePlayerIds(options);
911
+ const playerIdsSorted = Array.from(subscribed).sort();
912
+ if (subscribed.size === 0) {
913
+ this.logTransport("subscribeIntercomCommands:skip", {
914
+ reason: "empty_player_ids"
915
+ });
916
+ return { close: () => {
917
+ } };
918
+ }
919
+ this.logTransport("subscribeIntercomCommands:start", {
920
+ playerIds: playerIdsSorted
921
+ });
922
+ const { executeTool, chatAgentsByPlayerId } = options;
923
+ let closeSource = null;
924
+ const task = (async () => {
925
+ try {
926
+ const { createEventSource } = await import("eventsource-client");
927
+ const sseUrl = `${this.apiBase}/api/agent-play/events?sid=${encodeURIComponent(
928
+ this.getSessionId()
929
+ )}`;
930
+ this.logTransport("subscribeIntercomCommands:sse_open", {
931
+ sseUrl,
932
+ subscribePlayerIds: playerIdsSorted
933
+ });
934
+ const source = createEventSource({
935
+ url: sseUrl,
936
+ fetch: (input, init) => this.mergeAuthFetch(input, init)
937
+ });
938
+ closeSource = () => {
939
+ source.close();
940
+ };
941
+ for await (const msg of source) {
942
+ const sseEvent = getSseEventName(msg);
943
+ if (sseEvent !== void 0 && sseEvent !== WORLD_INTERCOM_EVENT) {
944
+ continue;
945
+ }
946
+ if (typeof msg.data !== "string") {
947
+ const eventLabel = sseEvent ?? "(unset)";
948
+ this.logTransport("sse:intercomCommands:skip", {
949
+ reason: "data_not_string",
950
+ sseEvent: eventLabel
951
+ });
952
+ continue;
953
+ }
954
+ let data;
955
+ try {
956
+ data = JSON.parse(msg.data);
957
+ } catch {
958
+ this.logTransport("sse:intercomCommands:parseJson", {
959
+ reason: "invalid_json",
960
+ sseEvent: sseEvent ?? "(unset)"
961
+ });
962
+ continue;
963
+ }
964
+ let inter;
965
+ try {
966
+ inter = parseWorldIntercomEventPayload(data);
967
+ } catch {
968
+ continue;
969
+ }
970
+ if (inter.status !== "forwarded") {
971
+ continue;
972
+ }
973
+ const cmd = inter.command;
974
+ if (cmd === void 0) {
975
+ this.logTransport("sse:intercomCommands:skip", {
976
+ reason: "missing_command",
977
+ requestId: inter.requestId
978
+ });
979
+ continue;
980
+ }
981
+ if (!subscribed.has(cmd.toPlayerId)) {
982
+ continue;
983
+ }
984
+ this.logTransport("sse:intercomCommands:forwarded", {
985
+ requestId: cmd.requestId,
986
+ fromPlayerId: cmd.fromPlayerId,
987
+ toPlayerId: cmd.toPlayerId,
988
+ kind: cmd.kind,
989
+ toolName: cmd.toolName
990
+ });
991
+ const toolName = cmd.kind === "chat" ? "chat_tool" : cmd.toolName ?? "";
992
+ const args = cmd.kind === "chat" ? { text: cmd.text ?? "" } : cmd.args ?? {};
993
+ this.logTransport("intercom:executeTool", {
994
+ requestId: cmd.requestId,
995
+ toolName,
996
+ argsPreview: this.truncateForLog(JSON.stringify(args))
997
+ });
998
+ try {
999
+ this.logTransport("intercom:executeTool:started", {
1000
+ requestId: cmd.requestId,
1001
+ toolName,
1002
+ argsPreview: this.truncateForLog(JSON.stringify(args))
1003
+ });
1004
+ let result;
1005
+ if (cmd.kind === "chat" && chatAgentsByPlayerId !== void 0 && chatAgentsByPlayerId.has(cmd.toPlayerId)) {
1006
+ const lc = chatAgentsByPlayerId.get(cmd.toPlayerId);
1007
+ if (lc === void 0) {
1008
+ throw new Error("intercom: chatAgentsByPlayerId entry missing");
1009
+ }
1010
+ this.logTransport("intercom:langchain:invoke", {
1011
+ requestId: cmd.requestId,
1012
+ toPlayerId: cmd.toPlayerId,
1013
+ textPreview: this.truncateForLog(cmd.text ?? "")
1014
+ });
1015
+ const rawOut = await invokeLangChainChatAgent(lc, {
1016
+ messages: [new HumanMessage(cmd.text ?? "")]
1017
+ });
1018
+ result = intercomResultRecordFromLangChainInvokeOutput(rawOut);
1019
+ this.logTransport("intercom:langchain:invoke:completed", {
1020
+ requestId: cmd.requestId,
1021
+ resultPreview: this.truncateForLog(JSON.stringify(result))
1022
+ });
1023
+ } else {
1024
+ result = await Promise.resolve(
1025
+ executeTool({ toolName, args })
1026
+ );
1027
+ }
1028
+ this.logTransport("intercom:executeTool:completed", {
1029
+ requestId: cmd.requestId,
1030
+ toolName,
1031
+ argsPreview: this.truncateForLog(JSON.stringify(args)),
1032
+ resultPreview: this.truncateForLog(JSON.stringify(result))
1033
+ });
1034
+ await this.sendIntercomResponse({
1035
+ requestId: cmd.requestId,
1036
+ mainNodeId: cmd.mainNodeId,
1037
+ toPlayerId: cmd.fromPlayerId,
1038
+ fromPlayerId: cmd.toPlayerId,
1039
+ kind: cmd.kind,
1040
+ status: "completed",
1041
+ toolName: cmd.toolName,
1042
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1043
+ result
1044
+ });
1045
+ } catch (err) {
1046
+ const message = err instanceof Error ? err.message : String(err);
1047
+ this.logTransport("intercom:executeTool:error", {
1048
+ requestId: cmd.requestId,
1049
+ message
1050
+ });
1051
+ await this.sendIntercomResponse({
1052
+ requestId: cmd.requestId,
1053
+ mainNodeId: cmd.mainNodeId,
1054
+ toPlayerId: cmd.fromPlayerId,
1055
+ fromPlayerId: cmd.toPlayerId,
1056
+ kind: cmd.kind,
1057
+ status: "failed",
1058
+ toolName: cmd.toolName,
1059
+ error: message,
1060
+ ts: (/* @__PURE__ */ new Date()).toISOString()
1061
+ });
1062
+ }
1063
+ }
1064
+ } catch (err) {
1065
+ const message = err instanceof Error ? err.message : String(err);
1066
+ this.logTransport("sse:intercomCommands:stream_error", { message });
1067
+ }
1068
+ })();
1069
+ return {
1070
+ close: () => {
1071
+ this.logTransport("subscribeIntercomCommands:close", {
1072
+ playerIds: playerIdsSorted
1073
+ });
1074
+ closeSource?.();
1075
+ void task;
1076
+ }
1077
+ };
1078
+ }
700
1079
  async registerMcp(options) {
701
1080
  const sid = this.getSessionId();
702
1081
  const url = `${this.apiBase}/api/agent-play/mcp/register?sid=${encodeURIComponent(sid)}`;
@@ -719,7 +1098,7 @@ var RemotePlayWorld = class {
719
1098
  } catch {
720
1099
  throw new Error("registerMcp: invalid JSON");
721
1100
  }
722
- if (!isRecord2(json) || typeof json.id !== "string") {
1101
+ if (!isRecord(json) || typeof json.id !== "string") {
723
1102
  throw new Error("registerMcp: invalid response");
724
1103
  }
725
1104
  return json.id;
@@ -737,12 +1116,103 @@ var RemotePlayWorld = class {
737
1116
  throw new Error(`rpc ${op}: ${res.status} ${t}`);
738
1117
  }
739
1118
  }
1119
+ async heartbeatPlayerConnection(input) {
1120
+ const sid = this.getSessionId();
1121
+ const url = `${this.apiBase}/api/agent-play/players/heartbeat?sid=${encodeURIComponent(sid)}`;
1122
+ const bodyJson = JSON.stringify({
1123
+ playerId: input.playerId,
1124
+ connectionId: input.connectionId,
1125
+ leaseTtlSeconds: input.leaseTtlSeconds
1126
+ });
1127
+ let lastError = new Error("heartbeat: no attempts completed");
1128
+ for (let attempt = 1; attempt <= PLAYER_CONNECTION_HEARTBEAT_MAX_ATTEMPTS; attempt += 1) {
1129
+ const attemptStartedAt = Date.now();
1130
+ try {
1131
+ const res = await fetch(url, {
1132
+ method: "POST",
1133
+ headers: this.jsonHeaders(),
1134
+ body: bodyJson
1135
+ });
1136
+ const text = await res.text();
1137
+ if (res.ok) {
1138
+ if (attempt > 1) {
1139
+ console.info("[agent-play:RemotePlayWorld] heartbeat:retry_recovered", {
1140
+ playerId: input.playerId,
1141
+ connectionId: input.connectionId,
1142
+ leaseTtlSeconds: input.leaseTtlSeconds,
1143
+ sid,
1144
+ url,
1145
+ successfulAttempt: attempt,
1146
+ attemptsUsed: attempt,
1147
+ maxAttempts: PLAYER_CONNECTION_HEARTBEAT_MAX_ATTEMPTS,
1148
+ durationMs: Date.now() - attemptStartedAt
1149
+ });
1150
+ }
1151
+ return;
1152
+ }
1153
+ lastError = new Error(`heartbeat: ${res.status} ${text}`);
1154
+ } catch (err) {
1155
+ lastError = err instanceof Error ? err : new Error(String(err));
1156
+ }
1157
+ const willRetry = attempt < PLAYER_CONNECTION_HEARTBEAT_MAX_ATTEMPTS;
1158
+ console.warn("[agent-play:RemotePlayWorld] heartbeat:attempt_failed", {
1159
+ phase: "player_connection_lease_refresh",
1160
+ playerId: input.playerId,
1161
+ connectionId: input.connectionId,
1162
+ leaseTtlSeconds: input.leaseTtlSeconds,
1163
+ sid,
1164
+ url,
1165
+ attempt,
1166
+ maxAttempts: PLAYER_CONNECTION_HEARTBEAT_MAX_ATTEMPTS,
1167
+ remainingAttemptsAfterThisFailure: PLAYER_CONNECTION_HEARTBEAT_MAX_ATTEMPTS - attempt,
1168
+ errorMessage: lastError.message,
1169
+ requestBodyBytes: bodyJson.length,
1170
+ attemptDurationMs: Date.now() - attemptStartedAt,
1171
+ nextAction: willRetry ? `sleep ${PLAYER_CONNECTION_HEARTBEAT_RETRY_DELAY_MS}ms then retry` : "no more retries; will throw",
1172
+ retryDelayMs: willRetry ? PLAYER_CONNECTION_HEARTBEAT_RETRY_DELAY_MS : 0
1173
+ });
1174
+ if (!willRetry) {
1175
+ throw lastError;
1176
+ }
1177
+ await new Promise((resolve) => {
1178
+ setTimeout(resolve, PLAYER_CONNECTION_HEARTBEAT_RETRY_DELAY_MS);
1179
+ });
1180
+ }
1181
+ }
1182
+ async disconnectPlayerConnection(input) {
1183
+ const sid = this.getSessionId();
1184
+ const url = `${this.apiBase}/api/agent-play/players/disconnect?sid=${encodeURIComponent(sid)}`;
1185
+ await fetch(url, {
1186
+ method: "POST",
1187
+ headers: this.jsonHeaders(),
1188
+ body: JSON.stringify({
1189
+ playerId: input.playerId,
1190
+ connectionId: input.connectionId
1191
+ })
1192
+ });
1193
+ }
740
1194
  };
1195
+
1196
+ // src/index.ts
1197
+ import {
1198
+ loadAgentPlayCredentialsFileFromPath,
1199
+ loadAgentPlayCredentialsFileFromPathSync as loadAgentPlayCredentialsFileFromPathSync2,
1200
+ loadRootKey as loadRootKey2,
1201
+ nodeCredentialsMaterialFromHumanPassphrase as nodeCredentialsMaterialFromHumanPassphrase2,
1202
+ parseAgentPlayCredentialsJson,
1203
+ resolveAgentPlayCredentialsPath as resolveAgentPlayCredentialsPath2
1204
+ } from "@agent-play/node-tools";
741
1205
  export {
1206
+ MINIMUM_PLAY_WORLD_BOUNDS,
742
1207
  PLAYER_ADDED_EVENT,
743
1208
  PLAYER_CHAIN_GENESIS_STABLE_KEY,
744
1209
  PLAYER_CHAIN_HEADER_STABLE_KEY,
745
1210
  RemotePlayWorld,
1211
+ SESSION_CLOSED_EVENT,
1212
+ SESSION_CONNECTED_EVENT,
1213
+ SESSION_INVALID_EVENT,
1214
+ SESSION_SSE_ERROR_EVENT,
1215
+ SESSION_SSE_OPEN_EVENT,
746
1216
  WORLD_AGENT_SIGNAL_EVENT,
747
1217
  WORLD_INTERACTION_EVENT,
748
1218
  WORLD_JOURNEY_EVENT,
@@ -750,13 +1220,21 @@ export {
750
1220
  boundsContain,
751
1221
  clampWorldPosition,
752
1222
  configureAgentPlayDebug,
1223
+ expandBoundsToMinimumPlayArea,
1224
+ intercomResultRecordFromLangChainInvokeOutput,
753
1225
  isAgentPlayDebugEnabled,
754
1226
  langchainRegistration,
1227
+ loadAgentPlayCredentialsFileFromPath,
1228
+ loadAgentPlayCredentialsFileFromPathSync2 as loadAgentPlayCredentialsFileFromPathSync,
1229
+ loadRootKey2 as loadRootKey,
755
1230
  mergeSnapshotWithPlayerChainNode,
1231
+ nodeCredentialsMaterialFromHumanPassphrase2 as nodeCredentialsMaterialFromHumanPassphrase,
1232
+ parseAgentPlayCredentialsJson,
756
1233
  parsePlayerChainFanoutNotify,
757
1234
  parsePlayerChainFanoutNotifyFromSsePayload,
758
1235
  parsePlayerChainNodeRpcBody,
759
1236
  resetAgentPlayDebug,
1237
+ resolveAgentPlayCredentialsPath2 as resolveAgentPlayCredentialsPath,
760
1238
  sortNodeRefsForSerializedFetch
761
1239
  };
762
1240
  //# sourceMappingURL=index.js.map