@alfe.ai/openclaw-remote 0.0.4 → 0.0.6

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.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as RemotePluginConfig, t as plugin } from "./plugin.cjs";
1
+ import { i as RemotePluginConfig, r as plugin } from "./plugin.cjs";
2
2
 
3
3
  //#region src/ssrf.d.ts
4
4
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as RemotePluginConfig, t as plugin } from "./plugin.js";
1
+ import { i as RemotePluginConfig, r as plugin } from "./plugin.js";
2
2
 
3
3
  //#region src/ssrf.d.ts
4
4
 
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as buildIsNavigationAllowed, t as plugin } from "./plugin2.js";
1
+ import { i as buildIsNavigationAllowed, r as plugin } from "./plugin2.js";
2
2
  export { buildIsNavigationAllowed, plugin as default };
package/dist/plugin.cjs CHANGED
@@ -1,2 +1,8 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
1
5
  const require_plugin = require("./plugin2.cjs");
2
- module.exports = require_plugin.plugin;
6
+ exports.buildControlUrl = require_plugin.buildControlUrl;
7
+ exports.default = require_plugin.plugin;
8
+ exports.deriveDashboardBaseUrl = require_plugin.deriveDashboardBaseUrl;
package/dist/plugin.d.cts CHANGED
@@ -64,6 +64,22 @@ interface OpenClawPluginApi {
64
64
  }
65
65
  //#endregion
66
66
  //#region src/plugin.d.ts
67
+ /**
68
+ * Derive the dashboard (control-plane) base URL from the agent's cloud apiUrl,
69
+ * mirroring `deriveRemoteWsUrl` so it's per-stage automatically:
70
+ * https://api.dev.alfe.ai → https://app.dev.alfe.ai
71
+ * https://api.alfe.ai → https://app.alfe.ai (prod)
72
+ * The `api.` → `app.` swap matches config.*.ts `dashboardDomain` for every
73
+ * stage. Returns undefined for a non-`api.` host (e.g. localhost/dev override)
74
+ * so the caller can fall back to a session-only link.
75
+ */
76
+ declare function deriveDashboardBaseUrl(apiUrl: string): string | undefined;
77
+ /**
78
+ * Build the dashboard deep-link ("control URL") the human opens to take over.
79
+ * Prefers the full agent-scoped path; if the dashboard host or agentId is
80
+ * unavailable, degrades gracefully (session-only path, then null).
81
+ */
82
+ declare function buildControlUrl(dashboardBaseUrl: string | undefined, agentId: string | undefined, sessionId: string): string | undefined;
67
83
  declare const plugin: {
68
84
  id: string;
69
85
  name: string;
@@ -73,4 +89,4 @@ declare const plugin: {
73
89
  deactivate(api: OpenClawPluginApi): void;
74
90
  };
75
91
  //#endregion
76
- export { RemotePluginConfig as n, plugin as t };
92
+ export { RemotePluginConfig as i, deriveDashboardBaseUrl as n, plugin as r, buildControlUrl as t };
package/dist/plugin.d.ts CHANGED
@@ -64,6 +64,22 @@ interface OpenClawPluginApi {
64
64
  }
65
65
  //#endregion
66
66
  //#region src/plugin.d.ts
67
+ /**
68
+ * Derive the dashboard (control-plane) base URL from the agent's cloud apiUrl,
69
+ * mirroring `deriveRemoteWsUrl` so it's per-stage automatically:
70
+ * https://api.dev.alfe.ai → https://app.dev.alfe.ai
71
+ * https://api.alfe.ai → https://app.alfe.ai (prod)
72
+ * The `api.` → `app.` swap matches config.*.ts `dashboardDomain` for every
73
+ * stage. Returns undefined for a non-`api.` host (e.g. localhost/dev override)
74
+ * so the caller can fall back to a session-only link.
75
+ */
76
+ declare function deriveDashboardBaseUrl(apiUrl: string): string | undefined;
77
+ /**
78
+ * Build the dashboard deep-link ("control URL") the human opens to take over.
79
+ * Prefers the full agent-scoped path; if the dashboard host or agentId is
80
+ * unavailable, degrades gracefully (session-only path, then null).
81
+ */
82
+ declare function buildControlUrl(dashboardBaseUrl: string | undefined, agentId: string | undefined, sessionId: string): string | undefined;
67
83
  declare const plugin: {
68
84
  id: string;
69
85
  name: string;
@@ -73,4 +89,4 @@ declare const plugin: {
73
89
  deactivate(api: OpenClawPluginApi): void;
74
90
  };
75
91
  //#endregion
76
- export { RemotePluginConfig as n, plugin as t };
92
+ export { RemotePluginConfig as i, deriveDashboardBaseUrl as n, plugin as r, buildControlUrl as t };
package/dist/plugin.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as plugin } from "./plugin2.js";
2
- export { plugin as default };
1
+ import { n as deriveDashboardBaseUrl, r as plugin, t as buildControlUrl } from "./plugin2.js";
2
+ export { buildControlUrl, plugin as default, deriveDashboardBaseUrl };
package/dist/plugin2.cjs CHANGED
@@ -125,11 +125,42 @@ function deriveRemoteWsUrl(apiUrl) {
125
125
  return;
126
126
  }
127
127
  }
128
+ /**
129
+ * Derive the dashboard (control-plane) base URL from the agent's cloud apiUrl,
130
+ * mirroring `deriveRemoteWsUrl` so it's per-stage automatically:
131
+ * https://api.dev.alfe.ai → https://app.dev.alfe.ai
132
+ * https://api.alfe.ai → https://app.alfe.ai (prod)
133
+ * The `api.` → `app.` swap matches config.*.ts `dashboardDomain` for every
134
+ * stage. Returns undefined for a non-`api.` host (e.g. localhost/dev override)
135
+ * so the caller can fall back to a session-only link.
136
+ */
137
+ function deriveDashboardBaseUrl(apiUrl) {
138
+ try {
139
+ const url = new URL(apiUrl);
140
+ if (!url.hostname.startsWith("api.")) return void 0;
141
+ return `https://${url.hostname.replace("api.", "app.")}`;
142
+ } catch {
143
+ return;
144
+ }
145
+ }
146
+ /**
147
+ * Build the dashboard deep-link ("control URL") the human opens to take over.
148
+ * Prefers the full agent-scoped path; if the dashboard host or agentId is
149
+ * unavailable, degrades gracefully (session-only path, then null).
150
+ */
151
+ function buildControlUrl(dashboardBaseUrl, agentId, sessionId) {
152
+ if (!dashboardBaseUrl) return void 0;
153
+ const session = encodeURIComponent(sessionId);
154
+ if (agentId) return `${dashboardBaseUrl}/agents/${encodeURIComponent(agentId)}?tab=browser&session=${session}`;
155
+ return `${dashboardBaseUrl}/agents?tab=browser&session=${session}`;
156
+ }
128
157
  let remoteClient = null;
129
158
  let browserSurface = null;
130
159
  let terminalSurface = null;
131
160
  let apiClient = null;
132
161
  let handoffTimeoutMs = DEFAULT_HANDOFF_TIMEOUT_MS;
162
+ let dashboardBaseUrl;
163
+ let selfAgentId;
133
164
  const sessionSurfaces = /* @__PURE__ */ new Map();
134
165
  function g() {
135
166
  return globalThis;
@@ -193,6 +224,12 @@ function startService(pluginConfig, ssrfPolicy, workspaceDir, log) {
193
224
  apiKey,
194
225
  apiUrl
195
226
  });
227
+ dashboardBaseUrl = deriveDashboardBaseUrl(apiUrl);
228
+ apiClient.whoami().then(({ agentId }) => {
229
+ selfAgentId = agentId;
230
+ }).catch((err) => {
231
+ log.debug(`whoami failed — takeover links will omit agentId: ${err.message}`);
232
+ });
196
233
  const sendFrame = (buf) => {
197
234
  remoteClient?.sendFrame(buf);
198
235
  };
@@ -231,6 +268,8 @@ function stopService(log) {
231
268
  terminalSurface?.shutdown();
232
269
  terminalSurface = null;
233
270
  apiClient = null;
271
+ dashboardBaseUrl = void 0;
272
+ selfAgentId = void 0;
234
273
  sessionSurfaces.clear();
235
274
  log.info("Remote plugin stopped");
236
275
  }
@@ -341,7 +380,7 @@ function registerTools(api) {
341
380
  api.registerTool({
342
381
  name: "request_browser_takeover",
343
382
  label: "request_browser_takeover",
344
- description: "Ask a human to take over the browser you're currently looking at and complete a step you can't do yourself — logging in, solving a captcha, or clicking through a manual flow. The human sees your current live page, completes the instructions, and hands control back; you then resume on the same authenticated page. Blocks until the human is done or the request times out.",
383
+ description: "Ask a human to take over the browser you're currently looking at and complete a step you can't do yourself — logging in, solving a captcha, or clicking through a manual flow. The human sees your current live page, completes the instructions, and hands control back; you then resume on the same authenticated page. Returns a control URL — SHARE IT WITH THE USER so they can open the takeover. Blocks until the human is done or the request times out.",
345
384
  parameters: {
346
385
  type: "object",
347
386
  properties: {
@@ -368,9 +407,13 @@ function registerTools(api) {
368
407
  url: optStr(params.url),
369
408
  conversationId: optStr(params.conversationId)
370
409
  });
410
+ const controlUrl = buildControlUrl(dashboardBaseUrl, selfAgentId, sessionId);
411
+ const message = controlUrl ? `Browser takeover requested (session ${sessionId}). Ask the user to open this link to take control and complete: ${controlUrl} — then wait; I'll resume automatically once they hand back.` : `Browser takeover requested (session ${sessionId}). Ask the user to open the Alfe dashboard, go to this agent's Browser tab, and take control — then wait; I'll resume automatically once they hand back.`;
371
412
  try {
372
413
  return {
373
414
  sessionId,
415
+ controlUrl,
416
+ message,
374
417
  ...await surface.requestHandoff(handoffTimeoutMs)
375
418
  };
376
419
  } finally {
@@ -409,12 +452,24 @@ const plugin = {
409
452
  }
410
453
  };
411
454
  //#endregion
455
+ Object.defineProperty(exports, "buildControlUrl", {
456
+ enumerable: true,
457
+ get: function() {
458
+ return buildControlUrl;
459
+ }
460
+ });
412
461
  Object.defineProperty(exports, "buildIsNavigationAllowed", {
413
462
  enumerable: true,
414
463
  get: function() {
415
464
  return buildIsNavigationAllowed;
416
465
  }
417
466
  });
467
+ Object.defineProperty(exports, "deriveDashboardBaseUrl", {
468
+ enumerable: true,
469
+ get: function() {
470
+ return deriveDashboardBaseUrl;
471
+ }
472
+ });
418
473
  Object.defineProperty(exports, "plugin", {
419
474
  enumerable: true,
420
475
  get: function() {
@@ -1,2 +1,2 @@
1
- import { t as plugin } from "./plugin.cjs";
2
- export { plugin as default };
1
+ import { n as deriveDashboardBaseUrl, r as plugin, t as buildControlUrl } from "./plugin.cjs";
2
+ export { buildControlUrl, plugin as default, deriveDashboardBaseUrl };
package/dist/plugin2.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { t as plugin } from "./plugin.js";
2
- export { plugin as default };
1
+ import { n as deriveDashboardBaseUrl, r as plugin, t as buildControlUrl } from "./plugin.js";
2
+ export { buildControlUrl, plugin as default, deriveDashboardBaseUrl };
package/dist/plugin2.js CHANGED
@@ -125,11 +125,42 @@ function deriveRemoteWsUrl(apiUrl) {
125
125
  return;
126
126
  }
127
127
  }
128
+ /**
129
+ * Derive the dashboard (control-plane) base URL from the agent's cloud apiUrl,
130
+ * mirroring `deriveRemoteWsUrl` so it's per-stage automatically:
131
+ * https://api.dev.alfe.ai → https://app.dev.alfe.ai
132
+ * https://api.alfe.ai → https://app.alfe.ai (prod)
133
+ * The `api.` → `app.` swap matches config.*.ts `dashboardDomain` for every
134
+ * stage. Returns undefined for a non-`api.` host (e.g. localhost/dev override)
135
+ * so the caller can fall back to a session-only link.
136
+ */
137
+ function deriveDashboardBaseUrl(apiUrl) {
138
+ try {
139
+ const url = new URL(apiUrl);
140
+ if (!url.hostname.startsWith("api.")) return void 0;
141
+ return `https://${url.hostname.replace("api.", "app.")}`;
142
+ } catch {
143
+ return;
144
+ }
145
+ }
146
+ /**
147
+ * Build the dashboard deep-link ("control URL") the human opens to take over.
148
+ * Prefers the full agent-scoped path; if the dashboard host or agentId is
149
+ * unavailable, degrades gracefully (session-only path, then null).
150
+ */
151
+ function buildControlUrl(dashboardBaseUrl, agentId, sessionId) {
152
+ if (!dashboardBaseUrl) return void 0;
153
+ const session = encodeURIComponent(sessionId);
154
+ if (agentId) return `${dashboardBaseUrl}/agents/${encodeURIComponent(agentId)}?tab=browser&session=${session}`;
155
+ return `${dashboardBaseUrl}/agents?tab=browser&session=${session}`;
156
+ }
128
157
  let remoteClient = null;
129
158
  let browserSurface = null;
130
159
  let terminalSurface = null;
131
160
  let apiClient = null;
132
161
  let handoffTimeoutMs = DEFAULT_HANDOFF_TIMEOUT_MS;
162
+ let dashboardBaseUrl;
163
+ let selfAgentId;
133
164
  const sessionSurfaces = /* @__PURE__ */ new Map();
134
165
  function g() {
135
166
  return globalThis;
@@ -193,6 +224,12 @@ function startService(pluginConfig, ssrfPolicy, workspaceDir, log) {
193
224
  apiKey,
194
225
  apiUrl
195
226
  });
227
+ dashboardBaseUrl = deriveDashboardBaseUrl(apiUrl);
228
+ apiClient.whoami().then(({ agentId }) => {
229
+ selfAgentId = agentId;
230
+ }).catch((err) => {
231
+ log.debug(`whoami failed — takeover links will omit agentId: ${err.message}`);
232
+ });
196
233
  const sendFrame = (buf) => {
197
234
  remoteClient?.sendFrame(buf);
198
235
  };
@@ -231,6 +268,8 @@ function stopService(log) {
231
268
  terminalSurface?.shutdown();
232
269
  terminalSurface = null;
233
270
  apiClient = null;
271
+ dashboardBaseUrl = void 0;
272
+ selfAgentId = void 0;
234
273
  sessionSurfaces.clear();
235
274
  log.info("Remote plugin stopped");
236
275
  }
@@ -341,7 +380,7 @@ function registerTools(api) {
341
380
  api.registerTool({
342
381
  name: "request_browser_takeover",
343
382
  label: "request_browser_takeover",
344
- description: "Ask a human to take over the browser you're currently looking at and complete a step you can't do yourself — logging in, solving a captcha, or clicking through a manual flow. The human sees your current live page, completes the instructions, and hands control back; you then resume on the same authenticated page. Blocks until the human is done or the request times out.",
383
+ description: "Ask a human to take over the browser you're currently looking at and complete a step you can't do yourself — logging in, solving a captcha, or clicking through a manual flow. The human sees your current live page, completes the instructions, and hands control back; you then resume on the same authenticated page. Returns a control URL — SHARE IT WITH THE USER so they can open the takeover. Blocks until the human is done or the request times out.",
345
384
  parameters: {
346
385
  type: "object",
347
386
  properties: {
@@ -368,9 +407,13 @@ function registerTools(api) {
368
407
  url: optStr(params.url),
369
408
  conversationId: optStr(params.conversationId)
370
409
  });
410
+ const controlUrl = buildControlUrl(dashboardBaseUrl, selfAgentId, sessionId);
411
+ const message = controlUrl ? `Browser takeover requested (session ${sessionId}). Ask the user to open this link to take control and complete: ${controlUrl} — then wait; I'll resume automatically once they hand back.` : `Browser takeover requested (session ${sessionId}). Ask the user to open the Alfe dashboard, go to this agent's Browser tab, and take control — then wait; I'll resume automatically once they hand back.`;
371
412
  try {
372
413
  return {
373
414
  sessionId,
415
+ controlUrl,
416
+ message,
374
417
  ...await surface.requestHandoff(handoffTimeoutMs)
375
418
  };
376
419
  } finally {
@@ -409,4 +452,4 @@ const plugin = {
409
452
  }
410
453
  };
411
454
  //#endregion
412
- export { buildIsNavigationAllowed as n, plugin as t };
455
+ export { buildIsNavigationAllowed as i, deriveDashboardBaseUrl as n, plugin as r, buildControlUrl as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-remote",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "OpenClaw plugin for Alfe's interactive remote-control relay — browser co-browse + web terminal surfaces over one outbound WS",
5
5
  "type": "module",
6
6
  "main": "./dist/plugin.js",
@@ -27,7 +27,7 @@
27
27
  "openclaw.plugin.json"
28
28
  ],
29
29
  "dependencies": {
30
- "@alfe.ai/agent-api-client": "^0.5.0",
30
+ "@alfe.ai/agent-api-client": "^0.6.1",
31
31
  "@alfe.ai/browser": "^0.1.0",
32
32
  "@alfe.ai/config": "0.3.0",
33
33
  "@alfe.ai/remote": "^0.1.0",