@elizaos/app-hyperscape 1.0.0 → 2.0.0-beta.1

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.d.ts CHANGED
@@ -1,20 +1,13 @@
1
- import { Plugin } from '@elizaos/core';
2
-
1
+ import type { Plugin } from "@elizaos/core";
3
2
  /**
4
- * @elizaos/app-hyperscape
3
+ * `@elizaos/app-hyperscape` plugin entry point.
5
4
  *
6
- * ElizaOS app for connecting AI agents to Hyperscape 3D multiplayer RPG worlds.
7
- * Agents can explore, fight, gather resources, craft items, bank, and chat.
8
- *
9
- * This is a "connect" type app — the agent plugin connects to an external
10
- * Hyperscape game server via WebSocket. The player opens the Hyperscape client
11
- * in their browser to watch their agent play.
12
- *
13
- * When the full @hyperscape/plugin-hyperscape is published to npm, this package
14
- * will re-export it. Until then, this provides the registry-compatible wrapper
15
- * with app metadata and basic configuration.
5
+ * Provides session resolvers for the Hyperscape game integration.
6
+ * The route module (`./routes.ts`) handles live session resolution
7
+ * by fetching data from the Hyperscape API.
16
8
  */
17
-
18
- declare const appHyperscape: Plugin;
19
-
20
- export { appHyperscape, appHyperscape as default };
9
+ declare const hyperscapePlugin: Plugin;
10
+ export default hyperscapePlugin;
11
+ export * from "./routes.js";
12
+ export * from "./ui/index.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;GAMG;AACH,QAAA,MAAM,gBAAgB,EAAE,MAIvB,CAAC;AAEF,eAAe,gBAAgB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,51 +1,11 @@
1
- // src/index.ts
2
- import { logger } from "@elizaos/core";
3
- function env(key, fallback = "") {
4
- const v = process.env[key]?.trim();
5
- return v && v !== "undefined" && v !== "null" ? v : fallback;
6
- }
7
- var appHyperscape = {
1
+ const hyperscapePlugin = {
8
2
  name: "@elizaos/app-hyperscape",
9
- description: "Connect AI agents to Hyperscape 3D multiplayer RPG \u2014 explore, fight, gather, craft, and socialize",
10
- config: {
11
- HYPERSCAPE_SERVER_URL: env("HYPERSCAPE_SERVER_URL", "ws://localhost:5555/ws"),
12
- HYPERSCAPE_AUTO_RECONNECT: env("HYPERSCAPE_AUTO_RECONNECT", "true"),
13
- HYPERSCAPE_AUTH_TOKEN: env("HYPERSCAPE_AUTH_TOKEN"),
14
- HYPERSCAPE_AUTONOMY_MODE: env("HYPERSCAPE_AUTONOMY_MODE", "llm")
15
- },
16
- init: async (_config, runtime) => {
17
- const serverUrl = runtime.getSetting("HYPERSCAPE_SERVER_URL") || process.env.HYPERSCAPE_SERVER_URL || "ws://localhost:5555/ws";
18
- logger.info(`[app-hyperscape] Configured for server: ${serverUrl}`);
19
- },
20
- app: {
21
- displayName: "Hyperscape",
22
- category: "game",
23
- launchType: "connect",
24
- launchUrl: "https://hyperscape.ai",
25
- capabilities: [
26
- "combat",
27
- "skills",
28
- "inventory",
29
- "banking",
30
- "social-chat",
31
- "exploration",
32
- "crafting"
33
- ],
34
- viewer: {
35
- url: "http://localhost:3333",
36
- embedParams: {
37
- embedded: "true",
38
- mode: "spectator",
39
- quality: "medium"
40
- },
41
- postMessageAuth: true,
42
- sandbox: "allow-scripts allow-same-origin allow-popups allow-forms"
43
- }
44
- }
3
+ description: "Hyperscape game session resolvers \u2014 spectate-and-steer agent sessions with live data from the Hyperscape API."
45
4
  };
46
- var index_default = appHyperscape;
5
+ var index_default = hyperscapePlugin;
6
+ export * from "./routes.js";
7
+ export * from "./ui/index.js";
47
8
  export {
48
- appHyperscape,
49
9
  index_default as default
50
10
  };
51
11
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @elizaos/app-hyperscape\n *\n * ElizaOS app for connecting AI agents to Hyperscape 3D multiplayer RPG worlds.\n * Agents can explore, fight, gather resources, craft items, bank, and chat.\n *\n * This is a \"connect\" type app — the agent plugin connects to an external\n * Hyperscape game server via WebSocket. The player opens the Hyperscape client\n * in their browser to watch their agent play.\n *\n * When the full @hyperscape/plugin-hyperscape is published to npm, this package\n * will re-export it. Until then, this provides the registry-compatible wrapper\n * with app metadata and basic configuration.\n */\n\nimport type { Plugin, IAgentRuntime } from \"@elizaos/core\";\nimport { logger } from \"@elizaos/core\";\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\n/** Read an env value, treating \"undefined\"/\"null\"/empty as missing. */\nfunction env(key: string, fallback = \"\"): string {\n const v = process.env[key]?.trim();\n return v && v !== \"undefined\" && v !== \"null\" ? v : fallback;\n}\n\n// ── Plugin Definition ──────────────────────────────────────────────────────\n\nexport const appHyperscape = {\n name: \"@elizaos/app-hyperscape\",\n description:\n \"Connect AI agents to Hyperscape 3D multiplayer RPGexplore, fight, gather, craft, and socialize\",\n\n config: {\n HYPERSCAPE_SERVER_URL: env(\"HYPERSCAPE_SERVER_URL\", \"ws://localhost:5555/ws\"),\n HYPERSCAPE_AUTO_RECONNECT: env(\"HYPERSCAPE_AUTO_RECONNECT\", \"true\"),\n HYPERSCAPE_AUTH_TOKEN: env(\"HYPERSCAPE_AUTH_TOKEN\"),\n HYPERSCAPE_AUTONOMY_MODE: env(\"HYPERSCAPE_AUTONOMY_MODE\", \"llm\"),\n },\n\n init: async (_config: Record<string, string>, runtime: IAgentRuntime) => {\n const serverUrl =\n runtime.getSetting(\"HYPERSCAPE_SERVER_URL\") ||\n process.env.HYPERSCAPE_SERVER_URL ||\n \"ws://localhost:5555/ws\";\n logger.info(`[app-hyperscape] Configured for server: ${serverUrl}`);\n },\n\n app: {\n displayName: \"Hyperscape\",\n category: \"game\",\n launchType: \"connect\",\n launchUrl: \"https://hyperscape.ai\",\n capabilities: [\n \"combat\",\n \"skills\",\n \"inventory\",\n \"banking\",\n \"social-chat\",\n \"exploration\",\n \"crafting\",\n ],\n viewer: {\n url: \"http://localhost:3333\",\n embedParams: {\n embedded: \"true\",\n mode: \"spectator\",\n quality: \"medium\",\n },\n postMessageAuth: true,\n sandbox: \"allow-scripts allow-same-origin allow-popups allow-forms\",\n },\n },\n} as Plugin;\n\nexport default appHyperscape;\n"],"mappings":";AAgBA,SAAS,cAAc;AAKvB,SAAS,IAAI,KAAa,WAAW,IAAY;AAC/C,QAAM,IAAI,QAAQ,IAAI,GAAG,GAAG,KAAK;AACjC,SAAO,KAAK,MAAM,eAAe,MAAM,SAAS,IAAI;AACtD;AAIO,IAAM,gBAAgB;AAAA,EAC3B,MAAM;AAAA,EACN,aACE;AAAA,EAEF,QAAQ;AAAA,IACN,uBAAuB,IAAI,yBAAyB,wBAAwB;AAAA,IAC5E,2BAA2B,IAAI,6BAA6B,MAAM;AAAA,IAClE,uBAAuB,IAAI,uBAAuB;AAAA,IAClD,0BAA0B,IAAI,4BAA4B,KAAK;AAAA,EACjE;AAAA,EAEA,MAAM,OAAO,SAAiC,YAA2B;AACvE,UAAM,YACJ,QAAQ,WAAW,uBAAuB,KAC1C,QAAQ,IAAI,yBACZ;AACF,WAAO,KAAK,2CAA2C,SAAS,EAAE;AAAA,EACpE;AAAA,EAEA,KAAK;AAAA,IACH,aAAa;AAAA,IACb,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,cAAc;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,KAAK;AAAA,MACL,aAAa;AAAA,QACX,UAAU;AAAA,QACV,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,MACA,iBAAiB;AAAA,MACjB,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Plugin } from \"@elizaos/core\";\n\n/**\n * `@elizaos/app-hyperscape` plugin entry point.\n *\n * Provides session resolvers for the Hyperscape game integration.\n * The route module (`./routes.ts`) handles live session resolution\n * by fetching data from the Hyperscape API.\n */\nconst hyperscapePlugin: Plugin = {\n name: \"@elizaos/app-hyperscape\",\n description:\n \"Hyperscape game session resolversspectate-and-steer agent sessions with live data from the Hyperscape API.\",\n};\n\nexport default hyperscapePlugin;\nexport * from \"./routes.js\";\nexport * from \"./ui/index.js\";\n"],"mappings":"AASA,MAAM,mBAA2B;AAAA,EAC/B,MAAM;AAAA,EACN,aACE;AACJ;AAEA,IAAO,gBAAQ;AACf,cAAc;AACd,cAAc;","names":[]}
package/dist/routes.d.ts CHANGED
@@ -1,18 +1,22 @@
1
- import * as http from 'node:http';
2
-
3
- interface HyperscapeRelayOptions {
4
- rawBodyOverride?: string;
5
- contentTypeOverride?: string;
6
- }
7
- interface AppsHyperscapeRouteContext {
8
- req: http.IncomingMessage;
9
- res: http.ServerResponse;
10
- method: string;
11
- pathname: string;
12
- readJsonBody: <T extends object>(req: http.IncomingMessage, res: http.ServerResponse) => Promise<T | null>;
13
- error: (res: http.ServerResponse, message: string, status?: number) => void;
14
- relayHyperscapeApi: (method: "GET" | "POST", path: string, options?: HyperscapeRelayOptions) => Promise<void>;
15
- }
16
- declare function handleAppsHyperscapeRoutes(ctx: AppsHyperscapeRouteContext): Promise<boolean>;
17
-
18
- export { type AppsHyperscapeRouteContext, type HyperscapeRelayOptions, handleAppsHyperscapeRoutes };
1
+ import type { IAgentRuntime } from "@elizaos/core";
2
+ import type { AppLaunchDiagnostic, AppLaunchPreparation, AppLaunchResult, AppLaunchSessionContext, AppRunSessionContext, AppSessionState, AppViewerAuthMessage } from "@elizaos/shared";
3
+ export declare function prepareLaunch(ctx: {
4
+ runtime: IAgentRuntime | null;
5
+ }): Promise<AppLaunchPreparation>;
6
+ export declare function resolveViewerAuthMessage(ctx: {
7
+ runtime: IAgentRuntime | null;
8
+ }): Promise<AppViewerAuthMessage | null>;
9
+ export declare function collectLaunchDiagnostics(ctx: {
10
+ viewer: AppLaunchResult["viewer"] | null;
11
+ }): Promise<AppLaunchDiagnostic[]>;
12
+ export declare function resolveLaunchSession(ctx: AppLaunchSessionContext): Promise<AppSessionState | null>;
13
+ /**
14
+ * Called by the host app-manager when the user stops the Hyperscape run.
15
+ * Hyperscape is a stateless session resolver against an external API —
16
+ * there are no local resources (WebSockets, timers, processes) to tear
17
+ * down. Iframe unmount is sufficient. This hook is present so the
18
+ * app-manager lifecycle path stays uniform across all game apps.
19
+ */
20
+ export declare function stopRun(): Promise<void>;
21
+ export declare function refreshRunSession(ctx: AppRunSessionContext): Promise<AppSessionState | null>;
22
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EAEpB,eAAe,EACf,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AAmczB,wBAAsB,aAAa,CAAC,GAAG,EAAE;IACvC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;CAC/B,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAGhC;AAED,wBAAsB,wBAAwB,CAAC,GAAG,EAAE;IAClD,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;CAC/B,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAsBvC;AAED,wBAAsB,wBAAwB,CAAC,GAAG,EAAE;IAClD,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;CAC1C,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAYjC;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,uBAAuB,GAC3B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA6BjC;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAE7C;AAED,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,oBAAoB,GACxB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAsBjC"}
package/dist/routes.js CHANGED
@@ -1,88 +1,375 @@
1
- // src/routes.ts
2
- async function handleAppsHyperscapeRoutes(ctx) {
3
- const {
4
- req,
5
- res,
6
- method,
7
- pathname,
8
- relayHyperscapeApi,
9
- readJsonBody,
10
- error
11
- } = ctx;
12
- if (method === "GET" && pathname === "/api/apps/hyperscape/embedded-agents") {
13
- await relayHyperscapeApi("GET", "/api/embedded-agents");
14
- return true;
15
- }
16
- if (method === "POST" && pathname === "/api/apps/hyperscape/embedded-agents") {
17
- await relayHyperscapeApi("POST", "/api/embedded-agents");
18
- return true;
19
- }
20
- if (method === "POST") {
21
- const embeddedActionMatch = pathname.match(
22
- /^\/api\/apps\/hyperscape\/embedded-agents\/([^/]+)\/(start|stop|pause|resume|command)$/
23
- );
24
- if (embeddedActionMatch) {
25
- const characterId = decodeURIComponent(embeddedActionMatch[1]);
26
- const action = embeddedActionMatch[2];
27
- await relayHyperscapeApi(
28
- "POST",
29
- `/api/embedded-agents/${encodeURIComponent(characterId)}/${action}`
30
- );
31
- return true;
32
- }
33
- const messageMatch = pathname.match(
34
- /^\/api\/apps\/hyperscape\/agents\/([^/]+)\/message$/
35
- );
36
- if (messageMatch) {
37
- const agentId = decodeURIComponent(messageMatch[1]);
38
- const body = await readJsonBody(req, res);
39
- if (!body) return true;
40
- const content = body.content?.trim();
41
- if (!content) {
42
- error(res, "content is required");
43
- return true;
1
+ import { logger } from "@elizaos/core";
2
+ const FETCH_TIMEOUT_MS = 8e3;
3
+ const HYPERSCAPE_WALLET_AUTH_TIMEOUT_MS = 5e3;
4
+ const THOUGHTS_LIMIT = 5;
5
+ const HYPERSCAPE_SESSION_MODE = "spectate-and-steer";
6
+ function normalizeStringSetting(value) {
7
+ return typeof value === "string" && value.trim() ? value.trim() : null;
8
+ }
9
+ function toAppSessionJsonValue(value, depth = 0) {
10
+ if (depth > 6) return null;
11
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
12
+ return value;
13
+ }
14
+ if (Array.isArray(value)) {
15
+ return value.map((entry) => toAppSessionJsonValue(entry, depth + 1)).filter((entry) => entry !== void 0);
16
+ }
17
+ if (typeof value === "object") {
18
+ const record = {};
19
+ for (const [key, entry] of Object.entries(value)) {
20
+ const next = toAppSessionJsonValue(entry, depth + 1);
21
+ if (next !== void 0) {
22
+ record[key] = next;
44
23
  }
45
- await relayHyperscapeApi(
46
- "POST",
47
- `/api/embedded-agents/${encodeURIComponent(agentId)}/command`,
48
- {
49
- rawBodyOverride: JSON.stringify({
50
- command: "chat",
51
- data: { message: content }
52
- }),
53
- contentTypeOverride: "application/json"
54
- }
55
- );
56
- return true;
57
24
  }
25
+ return record;
26
+ }
27
+ return void 0;
28
+ }
29
+ function resolveSettingLike(runtime, key) {
30
+ const fromRuntime = typeof runtime?.getSetting === "function" ? runtime.getSetting(key) : null;
31
+ if (typeof fromRuntime === "string" && fromRuntime.trim().length > 0) {
32
+ return fromRuntime.trim();
33
+ }
34
+ const fromEnv = process.env[key];
35
+ if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
36
+ return fromEnv.trim();
37
+ }
38
+ return void 0;
39
+ }
40
+ function resolveApiBase(runtime) {
41
+ const rawCandidates = [
42
+ typeof runtime?.getSetting === "function" ? runtime.getSetting("HYPERSCAPE_API_URL") : null,
43
+ process.env.HYPERSCAPE_API_URL,
44
+ typeof runtime?.getSetting === "function" ? runtime.getSetting("HYPERSCAPE_CLIENT_URL") : null,
45
+ process.env.HYPERSCAPE_CLIENT_URL
46
+ ];
47
+ for (const raw of rawCandidates) {
48
+ const candidate = normalizeStringSetting(raw);
49
+ if (!candidate) continue;
50
+ try {
51
+ const parsed = new URL(candidate);
52
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") continue;
53
+ return candidate.replace(/\/+$/, "");
54
+ } catch {
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ function resolveAgentId(runtime, viewer) {
60
+ const authMsg = viewer?.authMessage;
61
+ const fromViewer = typeof authMsg?.agentId === "string" ? authMsg.agentId : null;
62
+ const fromRuntime = typeof runtime?.agentId === "string" && runtime.agentId.trim() ? runtime.agentId.trim() : null;
63
+ return fromViewer || fromRuntime;
64
+ }
65
+ function resolveCharacterId(runtime, viewer) {
66
+ const authMsg = viewer?.authMessage;
67
+ const fromViewer = typeof authMsg?.characterId === "string" ? authMsg.characterId : null;
68
+ if (fromViewer) return fromViewer;
69
+ return normalizeStringSetting(
70
+ typeof runtime?.getSetting === "function" ? runtime.getSetting("HYPERSCAPE_CHARACTER_ID") : null
71
+ );
72
+ }
73
+ function normalizeAbsoluteHttpUrl(raw) {
74
+ if (!raw) return null;
75
+ try {
76
+ const parsed = new URL(raw);
77
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
78
+ return null;
79
+ }
80
+ return raw.replace(/\/+$/, "");
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+ function persistCredential(runtime, key, value, secret = false) {
86
+ if (!runtime) {
87
+ return;
88
+ }
89
+ try {
90
+ runtime.setSetting?.(key, value, secret);
91
+ } catch (err) {
92
+ logger.error(`[hyperscape] Failed to persist credential "${key}": ${err}`);
93
+ }
94
+ const character = runtime.character;
95
+ if (!character.settings) {
96
+ character.settings = {};
97
+ }
98
+ if (!character.settings.secrets) {
99
+ character.settings.secrets = {};
100
+ }
101
+ character.settings.secrets[key] = value;
102
+ if (!character.secrets) {
103
+ character.secrets = {};
58
104
  }
59
- if (method === "GET") {
60
- const goalMatch = pathname.match(
61
- /^\/api\/apps\/hyperscape\/agents\/([^/]+)\/goal$/
105
+ character.secrets[key] = value;
106
+ }
107
+ function resolveApiBaseUrl(runtime) {
108
+ const configuredUrl = resolveSettingLike(runtime, "HYPERSCAPE_API_URL");
109
+ const normalized = normalizeAbsoluteHttpUrl(configuredUrl);
110
+ if (configuredUrl && !normalized) {
111
+ logger.warn(
112
+ "[hyperscape] Ignoring invalid HYPERSCAPE_API_URL; expected an absolute http/https URL."
62
113
  );
63
- if (goalMatch) {
64
- const agentId = decodeURIComponent(goalMatch[1]);
65
- await relayHyperscapeApi(
66
- "GET",
67
- `/api/agents/${encodeURIComponent(agentId)}/goal`
68
- );
69
- return true;
114
+ }
115
+ return normalized;
116
+ }
117
+ async function resolveRuntimeEvmAddress(runtime) {
118
+ let agent;
119
+ try {
120
+ if (typeof runtime.getAgent === "function" && runtime.agentId) {
121
+ agent = await runtime.getAgent(runtime.agentId);
70
122
  }
71
- const quickActionsMatch = pathname.match(
72
- /^\/api\/apps\/hyperscape\/agents\/([^/]+)\/quick-actions$/
123
+ } catch {
124
+ agent = null;
125
+ }
126
+ const walletAddresses = agent && typeof agent === "object" ? agent.walletAddresses : void 0;
127
+ const evm = walletAddresses?.evm?.trim();
128
+ if (evm) {
129
+ return evm;
130
+ }
131
+ const existingPk = resolveSettingLike(runtime, "EVM_PRIVATE_KEY")?.trim() || process.env.EVM_PRIVATE_KEY?.trim();
132
+ if (!existingPk) {
133
+ return null;
134
+ }
135
+ const walletApiModule = "@elizaos/agent";
136
+ const { deriveEvmAddress } = await import(
137
+ /* webpackIgnore: true */
138
+ walletApiModule
139
+ );
140
+ return deriveEvmAddress(existingPk);
141
+ }
142
+ async function prepareWalletAuthFromRuntime(runtime) {
143
+ if (!runtime) {
144
+ return;
145
+ }
146
+ if (resolveSettingLike(runtime, "HYPERSCAPE_AUTH_TOKEN")) {
147
+ return;
148
+ }
149
+ const base = resolveApiBaseUrl(runtime);
150
+ if (!base) {
151
+ return;
152
+ }
153
+ const evm = await resolveRuntimeEvmAddress(runtime);
154
+ if (!evm) {
155
+ logger.info(
156
+ "[hyperscape] Skipping wallet auth: no EVM address or EVM_PRIVATE_KEY is available."
73
157
  );
74
- if (quickActionsMatch) {
75
- const agentId = decodeURIComponent(quickActionsMatch[1]);
76
- await relayHyperscapeApi(
77
- "GET",
78
- `/api/agents/${encodeURIComponent(agentId)}/quick-actions`
158
+ return;
159
+ }
160
+ try {
161
+ const walletAuthUrl = new URL("/api/agents/wallet-auth", `${base}/`);
162
+ const res = await fetch(walletAuthUrl, {
163
+ method: "POST",
164
+ headers: { "content-type": "application/json" },
165
+ body: JSON.stringify({
166
+ walletAddress: evm,
167
+ walletType: "evm",
168
+ agentName: runtime.character?.name,
169
+ agentId: runtime.agentId
170
+ }),
171
+ signal: AbortSignal.timeout(HYPERSCAPE_WALLET_AUTH_TIMEOUT_MS)
172
+ });
173
+ if (!res.ok) {
174
+ return;
175
+ }
176
+ const data = await res.json();
177
+ if (data.success !== true || typeof data.authToken !== "string") {
178
+ return;
179
+ }
180
+ persistCredential(runtime, "HYPERSCAPE_AUTH_TOKEN", data.authToken, true);
181
+ if (typeof data.characterId === "string" && data.characterId.trim()) {
182
+ persistCredential(
183
+ runtime,
184
+ "HYPERSCAPE_CHARACTER_ID",
185
+ data.characterId.trim()
79
186
  );
80
- return true;
81
187
  }
188
+ } catch {
189
+ }
190
+ }
191
+ async function fetchJson(url) {
192
+ try {
193
+ const res = await fetch(url, {
194
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
195
+ });
196
+ if (!res.ok) return null;
197
+ return await res.json();
198
+ } catch {
199
+ return null;
200
+ }
201
+ }
202
+ async function fetchLiveData(base, agentId) {
203
+ const id = encodeURIComponent(agentId);
204
+ const [agentsRes, goalRes, quickActionsRes, thoughtsRes] = await Promise.all([
205
+ fetchJson(
206
+ `${base}/api/embedded-agents`
207
+ ),
208
+ fetchJson(`${base}/api/agents/${id}/goal`),
209
+ fetchJson(`${base}/api/agents/${id}/quick-actions`),
210
+ fetchJson(
211
+ `${base}/api/agents/${id}/thoughts?limit=${THOUGHTS_LIMIT}`
212
+ )
213
+ ]);
214
+ const agents = agentsRes?.agents ?? [];
215
+ const agentRecord = agents.find((a) => a.agentId === agentId) ?? (agents.length === 1 ? agents[0] : null) ?? null;
216
+ return {
217
+ agentRecord,
218
+ goal: goalRes?.goal ?? null,
219
+ goalsPaused: goalRes?.goalsPaused === true,
220
+ availableGoals: goalRes?.availableGoals ?? [],
221
+ quickCommands: quickActionsRes?.quickCommands ?? [],
222
+ nearbyLocations: quickActionsRes?.nearbyLocations ?? [],
223
+ thoughts: thoughtsRes?.thoughts ?? []
224
+ };
225
+ }
226
+ function buildSession(appName, agentId, characterId, data) {
227
+ const {
228
+ agentRecord,
229
+ goal,
230
+ goalsPaused,
231
+ availableGoals,
232
+ quickCommands,
233
+ nearbyLocations,
234
+ thoughts
235
+ } = data;
236
+ const isRunning = agentRecord?.state === "running";
237
+ const controls = isRunning ? ["pause"] : ["resume"];
238
+ const goalLabel = goal?.description ?? null;
239
+ const suggestedPrompts = quickCommands.filter((c) => c.available !== false && typeof c.command === "string").map((c) => c.command);
240
+ const recommendedGoals = availableGoals.map((g, i) => ({
241
+ id: `goal-${i}`,
242
+ type: g.type ?? "general",
243
+ description: g.description ?? "",
244
+ reason: typeof g.reason === "string" ? g.reason : null
245
+ }));
246
+ const recentThoughts = thoughts.slice(0, THOUGHTS_LIMIT).map((t) => ({
247
+ id: t.id,
248
+ type: t.type,
249
+ content: toAppSessionJsonValue(t.content) ?? null,
250
+ timestamp: t.timestamp
251
+ }));
252
+ const telemetry = {
253
+ goalsPaused,
254
+ availableGoalCount: availableGoals.length,
255
+ nearbyLocationCount: nearbyLocations.length
256
+ };
257
+ if (typeof agentRecord?.startedAt === "number") {
258
+ telemetry.startedAt = agentRecord.startedAt;
259
+ }
260
+ if (typeof agentRecord?.lastActivity === "number") {
261
+ telemetry.lastActivity = agentRecord.lastActivity;
262
+ }
263
+ if (recommendedGoals.length > 0) {
264
+ telemetry.recommendedGoals = recommendedGoals;
265
+ }
266
+ if (recentThoughts.length > 0) {
267
+ telemetry.recentThoughts = recentThoughts;
268
+ }
269
+ return {
270
+ sessionId: agentId,
271
+ appName,
272
+ mode: HYPERSCAPE_SESSION_MODE,
273
+ status: isRunning ? "running" : "connecting",
274
+ agentId,
275
+ characterId: characterId ?? void 0,
276
+ followEntity: characterId ?? void 0,
277
+ canSendCommands: true,
278
+ controls,
279
+ summary: isRunning ? null : "Connecting session...",
280
+ goalLabel,
281
+ suggestedPrompts,
282
+ telemetry
283
+ };
284
+ }
285
+ async function prepareLaunch(ctx) {
286
+ await prepareWalletAuthFromRuntime(ctx.runtime ?? null);
287
+ return {};
288
+ }
289
+ async function resolveViewerAuthMessage(ctx) {
290
+ const runtime = ctx.runtime ?? null;
291
+ const authToken = resolveSettingLike(runtime, "HYPERSCAPE_AUTH_TOKEN");
292
+ if (!authToken) {
293
+ return null;
294
+ }
295
+ const agentId = typeof runtime?.agentId === "string" && runtime.agentId.trim().length > 0 ? runtime.agentId.trim() : void 0;
296
+ if (!agentId) {
297
+ return null;
298
+ }
299
+ const characterId = resolveSettingLike(runtime, "HYPERSCAPE_CHARACTER_ID") ?? agentId;
300
+ return {
301
+ type: "HYPERSCAPE_AUTH",
302
+ authToken,
303
+ agentId,
304
+ characterId,
305
+ followEntity: characterId
306
+ };
307
+ }
308
+ async function collectLaunchDiagnostics(ctx) {
309
+ if (ctx.viewer?.postMessageAuth && !ctx.viewer.authMessage) {
310
+ return [
311
+ {
312
+ code: "hyperscape-auth-unavailable",
313
+ severity: "error",
314
+ message: "Hyperscape postMessage auth requires HYPERSCAPE_AUTH_TOKEN and a runtime agent id."
315
+ }
316
+ ];
317
+ }
318
+ return [];
319
+ }
320
+ async function resolveLaunchSession(ctx) {
321
+ const { appName, runtime, viewer } = ctx;
322
+ const base = resolveApiBase(runtime);
323
+ if (!base) {
324
+ logger.debug(
325
+ "[hyperscape] HYPERSCAPE_API_URL not configured; skipping live session resolution"
326
+ );
327
+ return null;
328
+ }
329
+ const agentId = resolveAgentId(runtime, viewer);
330
+ if (!agentId) {
331
+ logger.debug(
332
+ "[hyperscape] No agentId available; skipping live session resolution"
333
+ );
334
+ return null;
335
+ }
336
+ const characterId = resolveCharacterId(runtime, viewer);
337
+ try {
338
+ const data = await fetchLiveData(base, agentId);
339
+ return buildSession(appName, agentId, characterId, data);
340
+ } catch (err) {
341
+ logger.warn(
342
+ `[hyperscape] Failed to resolve live session: ${err instanceof Error ? err.message : String(err)}`
343
+ );
344
+ return null;
345
+ }
346
+ }
347
+ async function stopRun() {
348
+ }
349
+ async function refreshRunSession(ctx) {
350
+ const { appName, runtime, viewer, session } = ctx;
351
+ if (!session) return null;
352
+ const base = resolveApiBase(runtime);
353
+ if (!base) return null;
354
+ const agentId = session.agentId ?? resolveAgentId(runtime, viewer);
355
+ if (!agentId) return null;
356
+ const characterId = session.characterId ?? resolveCharacterId(runtime, viewer);
357
+ try {
358
+ const data = await fetchLiveData(base, agentId);
359
+ return buildSession(appName, agentId, characterId, data);
360
+ } catch (err) {
361
+ logger.warn(
362
+ `[hyperscape] Failed to refresh run session: ${err instanceof Error ? err.message : String(err)}`
363
+ );
364
+ return null;
82
365
  }
83
- return false;
84
366
  }
85
367
  export {
86
- handleAppsHyperscapeRoutes
368
+ collectLaunchDiagnostics,
369
+ prepareLaunch,
370
+ refreshRunSession,
371
+ resolveLaunchSession,
372
+ resolveViewerAuthMessage,
373
+ stopRun
87
374
  };
88
375
  //# sourceMappingURL=routes.js.map