@agent-play/sdk 3.0.2 → 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,229 +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 === 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
245
  }
335
- };
246
+ }
247
+ return { mode: "chat", output: o };
336
248
  }
337
249
 
338
250
  // src/lib/remote-play-world.ts
339
- function formatMissingApiKeyError() {
251
+ var PLAYER_CONNECTION_HEARTBEAT_MAX_ATTEMPTS = 10;
252
+ var PLAYER_CONNECTION_HEARTBEAT_RETRY_DELAY_MS = 1e4;
253
+ function formatMissingCredentialsError() {
340
254
  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");
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(" ");
347
264
  }
348
265
  function normalizeBaseUrl(url) {
349
266
  return url.replace(/\/$/, "");
350
267
  }
351
- function isRecord2(v) {
268
+ function isRecord(v) {
352
269
  return typeof v === "object" && v !== null;
353
270
  }
354
271
  function parseBounds(raw) {
355
- if (!isRecord2(raw)) {
272
+ if (!isRecord(raw)) {
356
273
  throw new Error("getWorldSnapshot: worldMap.bounds must be an object");
357
274
  }
358
275
  const { minX, minY, maxX, maxY } = raw;
@@ -364,7 +281,7 @@ function parseBounds(raw) {
364
281
  return { minX, minY, maxX, maxY };
365
282
  }
366
283
  function parseWorldMap(raw) {
367
- if (!isRecord2(raw)) {
284
+ if (!isRecord(raw)) {
368
285
  throw new Error("getWorldSnapshot: worldMap must be an object");
369
286
  }
370
287
  const bounds = parseBounds(raw.bounds);
@@ -375,8 +292,10 @@ function parseWorldMap(raw) {
375
292
  const occupants = [];
376
293
  const coordKeys = /* @__PURE__ */ new Set();
377
294
  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");
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
+ );
380
299
  }
381
300
  const xy = typeof row.x === "number" && typeof row.y === "number" ? `${row.x},${row.y}` : "";
382
301
  if (xy.length === 0) {
@@ -386,7 +305,9 @@ function parseWorldMap(raw) {
386
305
  throw new Error("getWorldSnapshot: duplicate world map coordinate");
387
306
  }
388
307
  coordKeys.add(xy);
389
- if (row.kind === "agent") {
308
+ if (row.kind === "human") {
309
+ occupants.push(parseHumanOccupantRow(row));
310
+ } else if (row.kind === "agent") {
390
311
  occupants.push(parseAgentOccupantRow(row));
391
312
  } else {
392
313
  occupants.push(parseMcpOccupantRow(row));
@@ -395,7 +316,7 @@ function parseWorldMap(raw) {
395
316
  return { bounds, occupants };
396
317
  }
397
318
  function parseAgentPlaySnapshot(snapshot) {
398
- if (!isRecord2(snapshot) || typeof snapshot.sid !== "string") {
319
+ if (!isRecord(snapshot) || typeof snapshot.sid !== "string") {
399
320
  throw new Error("getWorldSnapshot: invalid snapshot");
400
321
  }
401
322
  const worldMap = parseWorldMap(snapshot.worldMap);
@@ -403,7 +324,7 @@ function parseAgentPlaySnapshot(snapshot) {
403
324
  if ("mcpServers" in snapshot && Array.isArray(snapshot.mcpServers)) {
404
325
  const servers = [];
405
326
  for (const m of snapshot.mcpServers) {
406
- if (!isRecord2(m) || typeof m.id !== "string" || typeof m.name !== "string") {
327
+ if (!isRecord(m) || typeof m.id !== "string" || typeof m.name !== "string") {
407
328
  continue;
408
329
  }
409
330
  const row = {
@@ -418,24 +339,24 @@ function parseAgentPlaySnapshot(snapshot) {
418
339
  return out;
419
340
  }
420
341
  function parseRegisteredAgentSummary(raw) {
421
- if (!isRecord2(raw)) {
422
- throw new Error("addPlayer: registeredAgent missing");
342
+ if (!isRecord(raw)) {
343
+ throw new Error("registerAgent: registeredAgent missing");
423
344
  }
424
345
  if (typeof raw.agentId !== "string" || typeof raw.name !== "string") {
425
- throw new Error("addPlayer: registeredAgent.agentId and name required");
346
+ throw new Error("registerAgent: registeredAgent.agentId and name required");
426
347
  }
427
348
  if (!Array.isArray(raw.toolNames)) {
428
- throw new Error("addPlayer: registeredAgent.toolNames must be an array");
349
+ throw new Error("registerAgent: registeredAgent.toolNames must be an array");
429
350
  }
430
351
  const toolNames = [];
431
352
  for (const t of raw.toolNames) {
432
353
  if (typeof t !== "string") {
433
- throw new Error("addPlayer: registeredAgent.toolNames must be strings");
354
+ throw new Error("registerAgent: registeredAgent.toolNames must be strings");
434
355
  }
435
356
  toolNames.push(t);
436
357
  }
437
358
  if (typeof raw.zoneCount !== "number" || typeof raw.yieldCount !== "number" || typeof raw.flagged !== "boolean") {
438
- throw new Error("addPlayer: registeredAgent counters invalid");
359
+ throw new Error("registerAgent: registeredAgent counters invalid");
439
360
  }
440
361
  return {
441
362
  agentId: raw.agentId,
@@ -453,20 +374,89 @@ function formatInvalidHoldSecondsError() {
453
374
  " Example: await world.hold().for(3600)"
454
375
  ].join("\n");
455
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
+ }
456
408
  var RemotePlayWorld = class {
457
409
  apiBase;
458
- apiKey;
459
- 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;
460
417
  sid = null;
461
418
  closed = false;
462
419
  closeListeners = /* @__PURE__ */ new Set();
463
- constructor(options) {
464
- if (typeof options.apiKey !== "string" || options.apiKey.trim().length === 0) {
465
- 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;
466
442
  }
467
- this.apiBase = normalizeBaseUrl(options.baseUrl);
468
- this.apiKey = options.apiKey.trim();
469
- 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);
470
460
  }
471
461
  onClose(handler) {
472
462
  this.closeListeners.add(handler);
@@ -488,8 +478,10 @@ var RemotePlayWorld = class {
488
478
  };
489
479
  }
490
480
  authHeaders() {
491
- if (this.authToken === void 0) return {};
492
- return { Authorization: `Bearer ${this.authToken}` };
481
+ return {
482
+ "x-node-id": this.derivedNodeId,
483
+ "x-node-passw": this.password
484
+ };
493
485
  }
494
486
  jsonHeaders() {
495
487
  return {
@@ -505,7 +497,51 @@ var RemotePlayWorld = class {
505
497
  }
506
498
  return fetch(input, { ...init, headers });
507
499
  }
508
- 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
+ }
509
545
  const res = await fetch(`${this.apiBase}/api/agent-play/session`, {
510
546
  headers: this.authHeaders()
511
547
  });
@@ -513,16 +549,38 @@ var RemotePlayWorld = class {
513
549
  throw new Error(`session failed: ${res.status}`);
514
550
  }
515
551
  const json = await res.json();
516
- if (!isRecord2(json) || typeof json.sid !== "string" || json.sid.length === 0) {
552
+ if (!isRecord(json) || typeof json.sid !== "string" || json.sid.length === 0) {
517
553
  throw new Error("session: invalid response");
518
554
  }
519
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
+ });
520
564
  }
521
565
  async close() {
522
566
  if (this.closed) {
523
567
  return;
524
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();
525
582
  this.closed = true;
583
+ this.emitSessionEvent({ name: SESSION_CLOSED_EVENT });
526
584
  for (const handler of Array.from(this.closeListeners)) {
527
585
  try {
528
586
  handler();
@@ -557,7 +615,7 @@ var RemotePlayWorld = class {
557
615
  } catch {
558
616
  throw new Error("getWorldSnapshot: invalid JSON");
559
617
  }
560
- if (!isRecord2(json) || !("snapshot" in json)) {
618
+ if (!isRecord(json) || !("snapshot" in json)) {
561
619
  throw new Error("getWorldSnapshot: invalid response shape");
562
620
  }
563
621
  return parseAgentPlaySnapshot(json.snapshot);
@@ -600,29 +658,65 @@ var RemotePlayWorld = class {
600
658
  const { createEventSource } = await import("eventsource-client");
601
659
  let snapshot = await this.getWorldSnapshot();
602
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 });
603
665
  const source = createEventSource({
604
- url: `${this.apiBase}/api/agent-play/events?sid=${encodeURIComponent(
605
- this.getSessionId()
606
- )}`,
666
+ url: sseUrl,
607
667
  fetch: (input, init) => this.mergeAuthFetch(input, init)
608
668
  });
609
669
  closeSource = () => {
610
670
  source.close();
611
671
  };
612
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)";
613
674
  if (typeof msg.data !== "string") {
675
+ this.logTransport("sse:worldState:skip", {
676
+ reason: "data_not_string",
677
+ eventType
678
+ });
614
679
  continue;
615
680
  }
681
+ this.logTransport("sse:worldState:message", {
682
+ eventType,
683
+ dataLength: msg.data.length,
684
+ dataPreview: this.truncateForLog(msg.data)
685
+ });
616
686
  let data;
617
687
  try {
618
688
  data = JSON.parse(msg.data);
619
689
  } catch {
690
+ this.logTransport("sse:worldState:parseJson", {
691
+ reason: "invalid_json",
692
+ eventType
693
+ });
620
694
  continue;
621
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
+ }
622
712
  const notify = parsePlayerChainFanoutNotifyFromSsePayload(data);
623
713
  if (notify === void 0 || notify.nodes.length === 0) {
624
714
  continue;
625
715
  }
716
+ this.logTransport("sse:worldState:playerChainNotify", {
717
+ nodeCount: notify.nodes.length,
718
+ stableKeys: notify.nodes.map((n) => n.stableKey)
719
+ });
626
720
  const ordered = sortNodeRefsForSerializedFetch(notify.nodes);
627
721
  for (const ref of ordered) {
628
722
  const node = await this.getPlayerChainNode(ref.stableKey);
@@ -631,21 +725,43 @@ var RemotePlayWorld = class {
631
725
  callbacks.onSnapshot(snapshot);
632
726
  }
633
727
  } catch (e) {
634
- callbacks.onError?.(
635
- e instanceof Error ? e : new Error(String(e))
636
- );
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);
637
733
  }
638
734
  })();
639
735
  return {
640
736
  close: () => {
737
+ this.logTransport("subscribeWorldState:close", {});
641
738
  closeSource?.();
642
739
  void task;
643
740
  }
644
741
  };
645
742
  }
646
- 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) {
647
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
+ );
648
762
  const url = `${this.apiBase}/api/agent-play/players?sid=${encodeURIComponent(sid)}`;
763
+ const connectionId = randomUUID();
764
+ const leaseTtlSeconds = 45;
649
765
  const res = await fetch(url, {
650
766
  method: "POST",
651
767
  headers: this.jsonHeaders(),
@@ -653,29 +769,60 @@ var RemotePlayWorld = class {
653
769
  name: input.name,
654
770
  type: input.type,
655
771
  agent: input.agent,
656
- apiKey: this.apiKey,
657
- agentId: input.agentId
772
+ mainNodeId: effectiveMainNodeId,
773
+ password: this.password,
774
+ agentId: input.nodeId,
775
+ connectionId,
776
+ leaseTtlSeconds
658
777
  })
659
778
  });
660
779
  const bodyText = await res.text();
661
780
  if (!res.ok) {
662
- throw new Error(`addPlayer: ${res.status} ${bodyText}`);
781
+ throw new Error(`addAgent: ${res.status} ${bodyText}`);
663
782
  }
664
783
  let body;
665
784
  try {
666
785
  body = JSON.parse(bodyText);
667
786
  } catch {
668
- throw new Error("addPlayer: invalid JSON");
787
+ throw new Error("addAgent: invalid JSON");
669
788
  }
670
- if (!isRecord2(body)) {
671
- throw new Error("addPlayer: invalid response shape");
789
+ if (!isRecord(body)) {
790
+ throw new Error("addAgent: invalid response shape");
672
791
  }
673
792
  const playerId = body.playerId;
674
793
  const previewUrl = body.previewUrl;
675
794
  if (typeof playerId !== "string" || typeof previewUrl !== "string") {
676
- throw new Error("addPlayer: missing playerId or previewUrl");
795
+ throw new Error("addAgent: missing playerId or previewUrl");
677
796
  }
678
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
+ });
679
826
  const now = /* @__PURE__ */ new Date();
680
827
  return {
681
828
  id: playerId,
@@ -684,8 +831,34 @@ var RemotePlayWorld = class {
684
831
  createdAt: now,
685
832
  updatedAt: now,
686
833
  previewUrl,
687
- 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
688
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);
689
862
  }
690
863
  async recordInteraction(input) {
691
864
  await this.rpc("recordInteraction", {
@@ -697,6 +870,210 @@ var RemotePlayWorld = class {
697
870
  async recordJourney(playerId, journey) {
698
871
  await this.rpc("recordJourney", { playerId, journey });
699
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
+ }
700
1077
  async registerMcp(options) {
701
1078
  const sid = this.getSessionId();
702
1079
  const url = `${this.apiBase}/api/agent-play/mcp/register?sid=${encodeURIComponent(sid)}`;
@@ -719,7 +1096,7 @@ var RemotePlayWorld = class {
719
1096
  } catch {
720
1097
  throw new Error("registerMcp: invalid JSON");
721
1098
  }
722
- if (!isRecord2(json) || typeof json.id !== "string") {
1099
+ if (!isRecord(json) || typeof json.id !== "string") {
723
1100
  throw new Error("registerMcp: invalid response");
724
1101
  }
725
1102
  return json.id;
@@ -737,12 +1114,102 @@ var RemotePlayWorld = class {
737
1114
  throw new Error(`rpc ${op}: ${res.status} ${t}`);
738
1115
  }
739
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
+ }
740
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";
741
1203
  export {
742
1204
  PLAYER_ADDED_EVENT,
743
1205
  PLAYER_CHAIN_GENESIS_STABLE_KEY,
744
1206
  PLAYER_CHAIN_HEADER_STABLE_KEY,
745
1207
  RemotePlayWorld,
1208
+ SESSION_CLOSED_EVENT,
1209
+ SESSION_CONNECTED_EVENT,
1210
+ SESSION_INVALID_EVENT,
1211
+ SESSION_SSE_ERROR_EVENT,
1212
+ SESSION_SSE_OPEN_EVENT,
746
1213
  WORLD_AGENT_SIGNAL_EVENT,
747
1214
  WORLD_INTERACTION_EVENT,
748
1215
  WORLD_JOURNEY_EVENT,
@@ -750,13 +1217,20 @@ export {
750
1217
  boundsContain,
751
1218
  clampWorldPosition,
752
1219
  configureAgentPlayDebug,
1220
+ intercomResultRecordFromLangChainInvokeOutput,
753
1221
  isAgentPlayDebugEnabled,
754
1222
  langchainRegistration,
1223
+ loadAgentPlayCredentialsFileFromPath,
1224
+ loadAgentPlayCredentialsFileFromPathSync2 as loadAgentPlayCredentialsFileFromPathSync,
1225
+ loadRootKey2 as loadRootKey,
755
1226
  mergeSnapshotWithPlayerChainNode,
1227
+ nodeCredentialsMaterialFromHumanPassphrase2 as nodeCredentialsMaterialFromHumanPassphrase,
1228
+ parseAgentPlayCredentialsJson,
756
1229
  parsePlayerChainFanoutNotify,
757
1230
  parsePlayerChainFanoutNotifyFromSsePayload,
758
1231
  parsePlayerChainNodeRpcBody,
759
1232
  resetAgentPlayDebug,
1233
+ resolveAgentPlayCredentialsPath2 as resolveAgentPlayCredentialsPath,
760
1234
  sortNodeRefsForSerializedFetch
761
1235
  };
762
1236
  //# sourceMappingURL=index.js.map