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