@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/README.md +1 -0
- package/dist/.root +1 -0
- package/dist/browser-C8UKcztD.d.ts +341 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +23 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunk-G2WV7OYM.js +260 -0
- package/dist/chunk-G2WV7OYM.js.map +1 -0
- package/dist/index.d.ts +122 -307
- package/dist/index.js +729 -251
- package/dist/index.js.map +1 -1
- package/examples/01-remote-web-ui-langchain.ts +77 -16
- package/examples/02-remote-two-players-langchain.ts +57 -20
- package/examples/README.md +6 -4
- package/package.json +10 -3
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] = {
|
|
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/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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/
|
|
184
|
-
function
|
|
185
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
223
|
+
return JSON.stringify(content);
|
|
240
224
|
}
|
|
241
|
-
function
|
|
242
|
-
if (
|
|
243
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
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
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
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:
|
|
338
|
-
""
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
|
268
|
+
function isRecord(v) {
|
|
348
269
|
return typeof v === "object" && v !== null;
|
|
349
270
|
}
|
|
350
271
|
function parseBounds(raw) {
|
|
351
|
-
if (!
|
|
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 (!
|
|
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 (!
|
|
375
|
-
throw new Error(
|
|
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 === "
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
418
|
-
throw new Error("
|
|
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("
|
|
346
|
+
throw new Error("registerAgent: registeredAgent.agentId and name required");
|
|
422
347
|
}
|
|
423
348
|
if (!Array.isArray(raw.toolNames)) {
|
|
424
|
-
throw new Error("
|
|
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("
|
|
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("
|
|
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
|
-
|
|
455
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
488
|
-
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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:
|
|
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
|
-
|
|
631
|
-
|
|
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
|
-
|
|
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
|
-
|
|
653
|
-
|
|
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(`
|
|
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("
|
|
787
|
+
throw new Error("addAgent: invalid JSON");
|
|
665
788
|
}
|
|
666
|
-
if (!
|
|
667
|
-
throw new Error("
|
|
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("
|
|
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 (!
|
|
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
|