@rubytech/create-realagent 1.0.621 → 1.0.622

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.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/device-url/dist/index.d.ts +44 -0
  3. package/payload/platform/lib/device-url/dist/index.d.ts.map +1 -0
  4. package/payload/platform/lib/device-url/dist/index.js +68 -0
  5. package/payload/platform/lib/device-url/dist/index.js.map +1 -0
  6. package/payload/platform/lib/device-url/src/index.ts +78 -0
  7. package/payload/platform/lib/device-url/tsconfig.json +8 -0
  8. package/payload/platform/package.json +2 -2
  9. package/payload/platform/plugins/admin/mcp/dist/index.js +12 -5
  10. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  11. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +45 -29
  12. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
  13. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +30 -17
  14. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
  15. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +78 -34
  16. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
  17. package/payload/platform/plugins/cloudflare/references/setup-guide.md +5 -6
  18. package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +3 -4
  19. package/payload/platform/plugins/docs/references/cloudflare.md +1 -1
  20. package/payload/server/public/assets/admin-BxVuKRJZ.js +352 -0
  21. package/payload/server/public/assets/{public-ZM0fHAOE.js → public-Bgm9WQFZ.js} +2 -2
  22. package/payload/server/public/assets/useVoiceRecorder-BORuG_su.css +1 -0
  23. package/payload/server/public/assets/useVoiceRecorder-CiYPZu3g.js +44 -0
  24. package/payload/server/public/index.html +3 -3
  25. package/payload/server/public/public.html +3 -3
  26. package/payload/server/server.js +259 -15
  27. package/payload/server/public/assets/admin-D7LRdkYB.js +0 -352
  28. package/payload/server/public/assets/useVoiceRecorder-CaFVzk8y.css +0 -1
  29. package/payload/server/public/assets/useVoiceRecorder-OB_Gtr0e.js +0 -43
@@ -5,11 +5,11 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Real Agent</title>
7
7
  <link rel="icon" href="/favicon.ico">
8
- <script type="module" crossorigin src="/assets/admin-D7LRdkYB.js"></script>
8
+ <script type="module" crossorigin src="/assets/admin-BxVuKRJZ.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/chunk-Be6NvmcD.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-rov5CBGT.js">
11
- <link rel="modulepreload" crossorigin href="/assets/useVoiceRecorder-OB_Gtr0e.js">
12
- <link rel="stylesheet" crossorigin href="/assets/useVoiceRecorder-CaFVzk8y.css">
11
+ <link rel="modulepreload" crossorigin href="/assets/useVoiceRecorder-CiYPZu3g.js">
12
+ <link rel="stylesheet" crossorigin href="/assets/useVoiceRecorder-BORuG_su.css">
13
13
  <link rel="stylesheet" href="/brand-defaults.css">
14
14
  </head>
15
15
  <body>
@@ -5,11 +5,11 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Real Agent</title>
7
7
  <link rel="icon" href="/favicon.ico">
8
- <script type="module" crossorigin src="/assets/public-ZM0fHAOE.js"></script>
8
+ <script type="module" crossorigin src="/assets/public-Bgm9WQFZ.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/chunk-Be6NvmcD.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-rov5CBGT.js">
11
- <link rel="modulepreload" crossorigin href="/assets/useVoiceRecorder-OB_Gtr0e.js">
12
- <link rel="stylesheet" crossorigin href="/assets/useVoiceRecorder-CaFVzk8y.css">
11
+ <link rel="modulepreload" crossorigin href="/assets/useVoiceRecorder-CiYPZu3g.js">
12
+ <link rel="stylesheet" crossorigin href="/assets/useVoiceRecorder-BORuG_su.css">
13
13
  <link rel="stylesheet" href="/brand-defaults.css">
14
14
  </head>
15
15
  <body>
@@ -10574,24 +10574,64 @@ function defaultRules() {
10574
10574
  suggestedAction: "This laptop is signed into a Cloudflare account that does not own the hostnames the tunnel is configured to serve. Run `tunnel-status` to confirm, then tell the operator verbatim: 'The tunnel is running on this laptop but nothing from the internet is reaching it. The Cloudflare account this laptop is signed into doesn't own your domain. Open Cloudflare in your browser \u2014 is the account name in the top-left the one that owns your domain? If not, switch to the correct one, then tell me and I will re-sign-in.' When the operator confirms the correct account is selected, run `tunnel-login force=true`."
10575
10575
  },
10576
10576
  {
10577
- // Task 541: tunnel-login's browser-launch failure class — cloudflared
10578
- // reports "Failed to fetch resource" into cloudflared-login.log when
10579
- // its browser launcher cannot reach the default browser, and the login
10580
- // process lingers with a live PID holding the log open. Prior to Task
10581
- // 541, `tunnel-login` inferred liveness from PID alone and kept
10582
- // reporting "Sign-in in progress" against a dead auth flow. The MCP
10583
- // handler now parses the log and emits this line on the first call
10584
- // that detects the failure; the rule surfaces it to the admin agent on
10585
- // the next turn so the 44-tool-call self-discovery loop (session
10586
- // 0e30b69e) cannot reopen.
10577
+ // Task 545: tunnel-login's terminal-failure class — cloudflared's
10578
+ // login process died without writing cert.pem. Covers every reason
10579
+ // the handler emits on the `failed` branch: either an unknown exit
10580
+ // (`-without-cert`), an exit preceded by the courtesy browser-launch
10581
+ // marker (`-with-marker`), auth URL never produced (`-timeout`), or
10582
+ // crashed before producing it at all. Task 541's original pattern
10583
+ // matched `reason=browser-launch-fetch-error` Task 545 retired
10584
+ // that reason because the marker alone is no longer terminal
10585
+ // (cloudflared keeps its OAuth-callback loop alive after emitting
10586
+ // it). Use this pattern for any new terminal reason the handler
10587
+ // gains: extend the alternation rather than adding parallel rules.
10587
10588
  id: "cloudflare-tunnel-login-failed",
10588
- name: "Cloudflare tunnel-login failed (browser launcher fetch error)",
10589
+ name: "Cloudflare tunnel-login process terminated without writing cert",
10589
10590
  type: "silent-catch",
10590
10591
  logSource: "any",
10591
- pattern: "\\[cloudflare:tunnel-login:failed\\] reason=browser-launch-fetch-error",
10592
+ pattern: "\\[cloudflare:tunnel-login:failed\\] reason=(login-process-exited-without-cert|login-process-exited-with-marker|auth-url-timeout|process-exited-before-auth-url)",
10592
10593
  thresholdCount: 0,
10593
10594
  thresholdWindowMinutes: 0,
10594
- suggestedAction: "cloudflared's browser launcher failed to open the sign-in URL on the laptop. The tunnel-login tool has already detected this and restarted the login \u2014 relay the new sign-in URL to the operator and wait for them to confirm authorization in the VNC browser. Do not open the Cloudflare dashboard in any other surface; the only path is the sign-in URL the tool produces."
10595
+ suggestedAction: "The cloudflared login process died before cert.pem landed on disk. For `login-process-exited-*` reasons the tunnel-login tool detected the dead process on the next call and has already respawned it \u2014 relay the new sign-in URL and wait for the operator to authorize in the VNC browser. For `auth-url-timeout` / `process-exited-before-auth-url`, the tool's most recent call returned an error and did not respawn \u2014 call `tunnel-login` again to spawn a fresh attempt. Never open the Cloudflare dashboard in any other surface; the only auth path is the sign-in URL the tool produces."
10596
+ },
10597
+ {
10598
+ // Task 545: non-terminal advisory. cloudflared's browser-launch
10599
+ // subcommand failed (DISPLAY unreachable, xdg-open absent, etc.) but
10600
+ // its OAuth-callback listener is still running — the login can still
10601
+ // complete if a human opens the URL. This rule fires so the admin
10602
+ // agent can relay "open the URL yourself" to the operator the moment
10603
+ // the condition appears, rather than waiting for the operator to
10604
+ // notice nothing is happening in their browser. Task 546 will
10605
+ // obsolete the advisory by rendering auth URLs that auto-open in
10606
+ // the VNC browser.
10607
+ id: "cloudflare-tunnel-login-browser-launch-failed",
10608
+ name: "cloudflared couldn't open the sign-in URL (login still live)",
10609
+ type: "silent-catch",
10610
+ logSource: "any",
10611
+ pattern: "\\[cloudflare:tunnel-login:browser-launch-failed\\]",
10612
+ thresholdCount: 0,
10613
+ thresholdWindowMinutes: 0,
10614
+ suggestedAction: "cloudflared's browser-launch courtesy failed on this laptop, but the sign-in URL is still live \u2014 the process is waiting for the OAuth callback. Tell the operator to open the sign-in URL in the VNC browser themselves and complete the authorization. The tunnel-login tool already includes the URL and the advisory in its most recent response; do not restart the login (that would invalidate the URL the operator is about to open)."
10615
+ },
10616
+ {
10617
+ // Task 545: raw-log surface. cloudflared-login.log is now read by
10618
+ // the review-detector's `cloudflared` source (see sources.ts). The
10619
+ // literal "Failed to fetch resource" is emitted by cloudflared
10620
+ // itself when its browser-launch subcommand can't reach a display.
10621
+ // This rule catches the cloudflared-side event even if the MCP
10622
+ // handler's classification drifts or the platform is restarting
10623
+ // mid-login and the advisory log line is not yet written. Keeping
10624
+ // this rule on a different source (log-line, not MCP stderr) makes
10625
+ // the detection redundant in the "defence in depth" sense — if the
10626
+ // MCP classification ever regresses, this still fires.
10627
+ id: "cloudflared-login-browser-launch-failed-raw",
10628
+ name: "cloudflared login \u2014 browser-launch fetch error in log",
10629
+ type: "silent-catch",
10630
+ logSource: "cloudflared",
10631
+ pattern: "Failed to fetch resource",
10632
+ thresholdCount: 0,
10633
+ thresholdWindowMinutes: 0,
10634
+ suggestedAction: "cloudflared-login.log shows the browser-launch courtesy failed. The sign-in URL from the last tunnel-login call is still live \u2014 the cloudflared process waits for the OAuth callback regardless of whether it could open the browser itself. Tell the operator to open the sign-in URL manually in the VNC browser on the device."
10595
10635
  },
10596
10636
  {
10597
10637
  // Task 540: cloudflared.log is the one file most likely to carry the
@@ -10629,6 +10669,33 @@ function defaultRules() {
10629
10669
  thresholdWindowMinutes: 60,
10630
10670
  scope: "session",
10631
10671
  suggestedAction: 'Agent emitted a choice-fork question ("Want me to X, or Y?") instead of a one-sided question or the prescribed action. Review Task 543 IDENTITY \xA7 Questions \u2014 the agent asked an opposing-axis question, or degraded a deterministic tool signal into a menu. The log sample shows the offending turn verbatim.'
10672
+ },
10673
+ {
10674
+ // Task 546: fires when the operator clicks a device-bound URL affordance
10675
+ // and the chat UI cannot drive the device browser — either CDP is
10676
+ // unreachable, the navigation timed out, or CDP returned an error. The
10677
+ // log line carries intent, hostname, and navigateResult so the admin
10678
+ // agent can name the affected flow and hostname verbatim on its next
10679
+ // turn. Every occurrence is worth surfacing (thresholdCount: 0) because
10680
+ // this is the exact class of silent failure Task 546 exists to close:
10681
+ // the operator clicked, nothing happened on the device, and if we don't
10682
+ // review the click telemetry the agent has no way to know the flow is
10683
+ // stuck.
10684
+ id: "device-url-click-failed",
10685
+ name: "Device-bound URL click failed to drive the VNC browser",
10686
+ type: "silent-catch",
10687
+ logSource: "server",
10688
+ // Enumerate the NavigateResult union explicitly rather than relying
10689
+ // on a (?!ok) negative lookahead anchored to a specific token order.
10690
+ // If a new member is added to the union in cdp-client.ts, this rule
10691
+ // must be updated in the same commit — the pattern is order-agnostic
10692
+ // (browser= and navigateResult= can appear in either order) and the
10693
+ // enumerated list compile-fails the source if it ever drifts from
10694
+ // the shared type in device-url-schema.ts.
10695
+ pattern: "\\[device-url:click\\][^\\n]*(?:browser=fallback|navigateResult=(?:timeout|cdp-unreachable|error))",
10696
+ thresholdCount: 0,
10697
+ thresholdWindowMinutes: 0,
10698
+ suggestedAction: "A device-bound URL click failed to drive Chromium on the device's VNC display. Identify the `intent` and `hostname` from the log line, then check the VNC surface: read `vnc-boot.log` and confirm Chromium on :99 is responding on CDP port 9222. If CDP is unreachable, the operator needs to restart the VNC stack; if CDP is reachable but navigation errored, the URL itself may be malformed upstream \u2014 grep the stream log for the originating `[device-url:render]` line."
10632
10699
  }
10633
10700
  ];
10634
10701
  }
@@ -10858,8 +10925,12 @@ function discoverSourceFiles(configDir2, accountLogDir2, logicalSource) {
10858
10925
  return existsSync8(p) ? [{ logicalSource: "vnc", filepath: p }] : [];
10859
10926
  }
10860
10927
  if (logicalSource === "cloudflared") {
10861
- const p = resolve8(configDir2, "logs", "cloudflared.log");
10862
- return existsSync8(p) ? [{ logicalSource: "cloudflared", filepath: p }] : [];
10928
+ const files2 = [];
10929
+ const daemon = resolve8(configDir2, "logs", "cloudflared.log");
10930
+ if (existsSync8(daemon)) files2.push({ logicalSource: "cloudflared", filepath: daemon });
10931
+ const login = resolve8(configDir2, "logs", "cloudflared-login.log");
10932
+ if (existsSync8(login)) files2.push({ logicalSource: "cloudflared", filepath: login });
10933
+ return files2;
10863
10934
  }
10864
10935
  const prefix = {
10865
10936
  system: "claude-agent-stream-",
@@ -32133,6 +32204,174 @@ async function POST27(req, remoteAddress) {
32133
32204
  }
32134
32205
  }
32135
32206
 
32207
+ // app/lib/cdp-client.ts
32208
+ var CDP_HOST = "127.0.0.1";
32209
+ var CDP_PORT = 9222;
32210
+ var DEFAULT_TIMEOUT_MS = 5e3;
32211
+ async function cdpNavigateNewTab(url2, opts = {}) {
32212
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
32213
+ const endpoint = `http://${CDP_HOST}:${CDP_PORT}/json/new?${encodeURIComponent(url2)}`;
32214
+ let res;
32215
+ try {
32216
+ res = await fetch(endpoint, {
32217
+ method: "PUT",
32218
+ signal: AbortSignal.timeout(timeoutMs)
32219
+ });
32220
+ } catch (err) {
32221
+ const msg = err instanceof Error ? err.message : String(err);
32222
+ if (msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("ENOTFOUND")) {
32223
+ return { result: "cdp-unreachable", detail: msg };
32224
+ }
32225
+ if (err instanceof Error && err.name === "TimeoutError") {
32226
+ return { result: "timeout", detail: msg };
32227
+ }
32228
+ return { result: "error", detail: msg };
32229
+ }
32230
+ if (!res.ok) {
32231
+ const body = await res.text().catch(() => "<unreadable>");
32232
+ return {
32233
+ result: "error",
32234
+ detail: `CDP /json/new returned ${res.status}: ${body.slice(0, 200)}`
32235
+ };
32236
+ }
32237
+ let target;
32238
+ try {
32239
+ target = await res.json();
32240
+ } catch (err) {
32241
+ const msg = err instanceof Error ? err.message : String(err);
32242
+ return { result: "error", detail: `CDP /json/new response not JSON: ${msg}` };
32243
+ }
32244
+ return { result: "ok", targetId: target?.id };
32245
+ }
32246
+
32247
+ // app/api/admin/device-browser/navigate/route.ts
32248
+ async function POST28(req, remoteAddress) {
32249
+ const TAG18 = "[device-url:click]";
32250
+ let body;
32251
+ try {
32252
+ body = await req.json();
32253
+ } catch (err) {
32254
+ const detail = err instanceof Error ? err.message : String(err);
32255
+ console.error(`${TAG18} reject reason=body-not-json detail=${detail} browser=fallback navigateResult=error`);
32256
+ return Response.json(
32257
+ { ok: false, navigateResult: "error", browser: "fallback", detail: "Request body was not valid JSON" },
32258
+ { status: 400 }
32259
+ );
32260
+ }
32261
+ const url2 = typeof body.url === "string" ? body.url : "";
32262
+ const intent = typeof body.intent === "string" ? body.intent : "";
32263
+ const hostname4 = typeof body.hostname === "string" ? body.hostname : "";
32264
+ if (!url2) {
32265
+ console.error(`${TAG18} reject reason=missing-url intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`);
32266
+ return Response.json(
32267
+ { ok: false, navigateResult: "error", browser: "fallback", detail: "url field is required" },
32268
+ { status: 400 }
32269
+ );
32270
+ }
32271
+ let parsed;
32272
+ try {
32273
+ parsed = new URL(url2);
32274
+ } catch {
32275
+ console.error(`${TAG18} reject reason=url-malformed intent=${JSON.stringify(intent)} url=${url2} browser=fallback navigateResult=error`);
32276
+ return Response.json(
32277
+ { ok: false, navigateResult: "error", browser: "fallback", detail: "url is not a valid URL" },
32278
+ { status: 400 }
32279
+ );
32280
+ }
32281
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
32282
+ console.error(
32283
+ `${TAG18} reject reason=scheme-not-allowed scheme=${parsed.protocol} intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`
32284
+ );
32285
+ return Response.json(
32286
+ {
32287
+ ok: false,
32288
+ navigateResult: "error",
32289
+ browser: "fallback",
32290
+ detail: `URL scheme ${parsed.protocol} is not permitted; only http and https are accepted`
32291
+ },
32292
+ { status: 400 }
32293
+ );
32294
+ }
32295
+ const transport = resolveBrowserTransport(req, remoteAddress);
32296
+ const cdpOk = await ensureCdp(transport);
32297
+ if (!cdpOk) {
32298
+ console.error(
32299
+ `${TAG18} intent=${JSON.stringify(intent)} browser=fallback navigateResult=cdp-unreachable hostname=${JSON.stringify(hostname4)}`
32300
+ );
32301
+ return Response.json(
32302
+ {
32303
+ ok: false,
32304
+ navigateResult: "cdp-unreachable",
32305
+ browser: "fallback",
32306
+ detail: "Device browser (Chromium/CDP on :9222) did not start"
32307
+ },
32308
+ { status: 502 }
32309
+ );
32310
+ }
32311
+ const outcome = await cdpNavigateNewTab(parsed.toString());
32312
+ const browser = outcome.result === "ok" ? "vnc" : "fallback";
32313
+ const detailStr = outcome.detail ? ` detail=${JSON.stringify(outcome.detail.length > 230 ? outcome.detail.slice(0, 227) + "..." : outcome.detail)}` : "";
32314
+ console.error(
32315
+ `${TAG18} intent=${JSON.stringify(intent)} browser=${browser} navigateResult=${outcome.result} hostname=${JSON.stringify(hostname4)} targetId=${outcome.targetId ?? "none"}${detailStr}`
32316
+ );
32317
+ if (outcome.result !== "ok") {
32318
+ return Response.json(
32319
+ {
32320
+ ok: false,
32321
+ navigateResult: outcome.result,
32322
+ browser: "fallback",
32323
+ detail: outcome.detail
32324
+ },
32325
+ { status: 502 }
32326
+ );
32327
+ }
32328
+ return Response.json({
32329
+ ok: true,
32330
+ navigateResult: "ok",
32331
+ browser: "vnc",
32332
+ targetId: outcome.targetId
32333
+ });
32334
+ }
32335
+
32336
+ // app/api/admin/events/route.ts
32337
+ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
32338
+ "device-url:render",
32339
+ "device-url:fallback-shown",
32340
+ "device-url:vnc-surface-shown",
32341
+ "device-url:malformed"
32342
+ ]);
32343
+ async function POST29(req) {
32344
+ const TAG18 = "[admin:events]";
32345
+ let body;
32346
+ try {
32347
+ body = await req.json();
32348
+ } catch (err) {
32349
+ const detail = err instanceof Error ? err.message : String(err);
32350
+ console.error(`${TAG18} reject reason=body-not-json detail=${detail}`);
32351
+ return Response.json({ ok: false, detail: "Request body was not valid JSON" }, { status: 400 });
32352
+ }
32353
+ const event = typeof body.event === "string" ? body.event : "";
32354
+ if (!ALLOWED_EVENTS.has(event)) {
32355
+ console.error(`${TAG18} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
32356
+ return Response.json({ ok: false, detail: `Event "${event}" is not allowed` }, { status: 400 });
32357
+ }
32358
+ const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
32359
+ const safeFields = {};
32360
+ for (const [key, value] of Object.entries(rawFields)) {
32361
+ if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,39}$/.test(key)) continue;
32362
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
32363
+ safeFields[key] = value;
32364
+ }
32365
+ }
32366
+ const formatted = Object.entries(safeFields).map(([k, v]) => {
32367
+ if (typeof v !== "string") return `${k}=${String(v)}`;
32368
+ const trunc = v.length > 230 ? v.slice(0, 227) + "..." : v;
32369
+ return `${k}=${JSON.stringify(trunc)}`;
32370
+ }).join(" ");
32371
+ console.error(`[${event}] ${formatted}`);
32372
+ return Response.json({ ok: true });
32373
+ }
32374
+
32136
32375
  // server/index.ts
32137
32376
  var PLATFORM_ROOT12 = process.env.MAXY_PLATFORM_ROOT || "";
32138
32377
  var BRAND_JSON_PATH = PLATFORM_ROOT12 ? join13(PLATFORM_ROOT12, "config", "brand.json") : "";
@@ -32523,6 +32762,11 @@ app.delete(
32523
32762
  (c) => DELETE2(c.req.raw, { params: Promise.resolve({ slug: c.req.param("slug") }) })
32524
32763
  );
32525
32764
  app.post("/api/admin/browser/launch", (c) => POST27(c.req.raw, c.env?.incoming?.socket?.remoteAddress));
32765
+ app.post(
32766
+ "/api/admin/device-browser/navigate",
32767
+ (c) => POST28(c.req.raw, c.env?.incoming?.socket?.remoteAddress)
32768
+ );
32769
+ app.post("/api/admin/events", (c) => POST29(c.req.raw));
32526
32770
  app.get("/api/admin/sessions", (c) => GET15(c.req.raw));
32527
32771
  app.post("/api/admin/sessions/new", (c) => POST24(c.req.raw));
32528
32772
  app.delete(