@rubytech/create-realagent 1.0.853 → 1.0.855

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 (42) hide show
  1. package/dist/index.js +9 -3
  2. package/package.json +1 -1
  3. package/payload/platform/lib/persistent-components/dist/index.d.ts +21 -0
  4. package/payload/platform/lib/persistent-components/dist/index.d.ts.map +1 -0
  5. package/payload/platform/lib/persistent-components/dist/index.js +32 -0
  6. package/payload/platform/lib/persistent-components/dist/index.js.map +1 -0
  7. package/payload/platform/lib/persistent-components/src/index.ts +28 -0
  8. package/payload/platform/lib/persistent-components/tsconfig.json +8 -0
  9. package/payload/platform/package.json +2 -2
  10. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  11. package/payload/platform/plugins/admin/hooks/__tests__/playwright-file-guard.test.sh +278 -0
  12. package/payload/platform/plugins/admin/hooks/playwright-file-guard.sh +204 -20
  13. package/payload/platform/plugins/admin/mcp/dist/index.js +40 -1
  14. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  15. package/payload/platform/plugins/cloudflare/references/manual-setup.md +2 -0
  16. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +39 -10
  17. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +112 -20
  18. package/payload/platform/plugins/docs/references/cloudflare.md +1 -0
  19. package/payload/platform/plugins/docs/references/deployment.md +2 -0
  20. package/payload/platform/plugins/docs/references/getting-started.md +2 -0
  21. package/payload/platform/plugins/docs/references/platform.md +1 -1
  22. package/payload/platform/plugins/docs/references/troubleshooting.md +10 -0
  23. package/payload/platform/scripts/admin-persist-audit.ts +191 -0
  24. package/payload/platform/scripts/component-knowledgedoc-backfill.ts +214 -0
  25. package/payload/platform/scripts/installer-device-verify.sh +17 -4
  26. package/payload/platform/templates/specialists/agents/content-producer.md +2 -2
  27. package/payload/server/chunk-CFNSKDGA.js +667 -0
  28. package/payload/server/chunk-DC6DWYZJ.js +1603 -0
  29. package/payload/server/chunk-LTB5SSQW.js +10889 -0
  30. package/payload/server/chunk-MN2LGNUB.js +2143 -0
  31. package/payload/server/client-pool-AMT2W3II.js +34 -0
  32. package/payload/server/cloudflare-task-tracker-LJ4SMK2D.js +20 -0
  33. package/payload/server/maxy-edge.js +3 -3
  34. package/payload/server/public/assets/admin-Cpk5cT4I.js +352 -0
  35. package/payload/server/public/assets/public-DApUXgoq.js +5 -0
  36. package/payload/server/public/assets/useVoiceRecorder-CI8GpxfU.js +36 -0
  37. package/payload/server/public/index.html +2 -2
  38. package/payload/server/public/public.html +2 -2
  39. package/payload/server/server.js +543 -354
  40. package/payload/server/public/assets/admin-Dyl8uNxX.js +0 -352
  41. package/payload/server/public/assets/public-B_PNZUph.js +0 -5
  42. package/payload/server/public/assets/useVoiceRecorder-fD0IWzJj.js +0 -36
@@ -1,30 +1,214 @@
1
1
  #!/usr/bin/env bash
2
- # PreToolUse hook — intercepts browser_navigate with file:// URLs.
2
+ # PreToolUse hook — rewrites file:// browser_navigate calls to a backgrounded
3
+ # loopback HTTP server, so Playwright never sees a file:// URL (which it
4
+ # blocks behind a 2-minute MCP timeout).
3
5
  #
4
- # Scoped to mcp__plugin_playwright_playwright__browser_navigate via
5
- # the settings.json matcher. Blocks file:// attempts with actionable
6
- # guidance so the agent self-corrects on the first try instead of
7
- # retrying into Playwright's cryptic error + 2-minute MCP timeout.
6
+ # Scoped to mcp__plugin_playwright_playwright__browser_navigate via the
7
+ # settings.json matcher.
8
8
  #
9
- # Error philosophy: fail open. This is a guidance hook, not a security
10
- # boundary. If parsing fails, the tool proceeds normally and the agent
11
- # gets the standard Playwright error.
9
+ # Modes:
10
+ # default (stdin = PreToolUse JSON):
11
+ # - Sweeps stale /tmp/playwright-file-guard.*.pid (>1h old) opportunistically.
12
+ # - If tool_input.url is file://, picks a free port, backgrounds
13
+ # `python3 -m http.server <port>` rooted at the file's parent dir,
14
+ # connect-verifies the server within 1s, writes PID to
15
+ # /tmp/playwright-file-guard.<port>.pid, and emits a JSON object on
16
+ # stdout setting hookSpecificOutput.updatedInput.url to
17
+ # http://127.0.0.1:<port>/<basename>.
18
+ # - Other URLs: passthrough log to stderr, exit 0, no stdout.
19
+ #
20
+ # `cleanup` argv: only runs the stale-PID sweep; does not read stdin.
21
+ #
22
+ # Error philosophy: fail open. Any internal failure → exit 0 with a stderr
23
+ # diagnostic, letting Playwright handle the original URL (the legacy
24
+ # 2-minute timeout) rather than block on a parser bug.
25
+ #
26
+ # Observability: every action emits one stderr line of the shape
27
+ # [playwright-file-guard] action=<rewrite|passthrough|cleanup|fail> ...
28
+
29
+ set -u
12
30
 
13
- if [ -t 0 ]; then
31
+ MODE="${1:-rewrite}"
32
+
33
+ # Terminal stdin guard. Cleanup mode never reads stdin.
34
+ if [[ "$MODE" != "cleanup" ]] && [ -t 0 ]; then
14
35
  exit 0
15
36
  fi
16
- INPUT=$(cat)
17
-
18
- if echo "$INPUT" | grep -qiE '"url"\s*:\s*"file://'; then
19
- cat >&2 << 'EOF'
20
- file:// URLs are blocked by Playwright. To view a local HTML file in the browser:
21
37
 
22
- 1. Start a local HTTP server: python3 -m http.server 8080
23
- 2. Navigate to http://localhost:8080/filename.html instead
38
+ # Without python3 we cannot do anything useful. Drain stdin (so the upstream
39
+ # tool dispatcher does not get SIGPIPE) and fail open.
40
+ if ! command -v python3 >/dev/null 2>&1; then
41
+ if [[ "$MODE" != "cleanup" ]]; then
42
+ cat >/dev/null
43
+ fi
44
+ echo "[playwright-file-guard] action=fail reason=python3-missing" >&2
45
+ exit 0
46
+ fi
24
47
 
25
- Do not retry with file:// it will always fail.
26
- EOF
27
- exit 2
48
+ if [[ "$MODE" == "cleanup" ]]; then
49
+ INPUT=""
50
+ else
51
+ INPUT=$(cat)
28
52
  fi
29
53
 
30
- exit 0
54
+ MODE="$MODE" INPUT="$INPUT" python3 <<'PY' || exit 0
55
+ import os, sys, json, glob, time, socket, subprocess
56
+ from urllib.parse import unquote
57
+
58
+ MODE = os.environ.get('MODE', 'rewrite')
59
+ INPUT = os.environ.get('INPUT', '')
60
+ NOW = time.time()
61
+ STALE_THRESHOLD = 3600 # 1h
62
+ PID_GLOB = '/tmp/playwright-file-guard.*.pid'
63
+
64
+ def log(line):
65
+ print(line, file=sys.stderr, flush=True)
66
+
67
+ def port_from_pidfile(path):
68
+ parts = os.path.basename(path).split('.')
69
+ return parts[1] if len(parts) >= 3 else 'unknown'
70
+
71
+ def sweep_stale():
72
+ for pid_file in glob.glob(PID_GLOB):
73
+ try:
74
+ mtime = os.stat(pid_file).st_mtime
75
+ except OSError:
76
+ continue
77
+ if NOW - mtime < STALE_THRESHOLD:
78
+ continue
79
+ port = port_from_pidfile(pid_file)
80
+ try:
81
+ with open(pid_file) as f:
82
+ pid = int(f.read().strip())
83
+ except Exception:
84
+ try: os.remove(pid_file)
85
+ except OSError: pass
86
+ log(f'[playwright-file-guard] action=cleanup port={port} status=unparseable-pid')
87
+ continue
88
+ cmd = subprocess.run(
89
+ ['ps', '-p', str(pid), '-o', 'command='],
90
+ capture_output=True, text=True,
91
+ )
92
+ cmdline = cmd.stdout if cmd.returncode == 0 else ''
93
+ # macOS ps resolves /usr/local/bin/python3 → Python.app binary "Python";
94
+ # Linux/Pi ps shows "python3". Match either via case-insensitive
95
+ # `python` substring + the unambiguous `http.server` invocation.
96
+ cmdline_lower = cmdline.lower()
97
+ if 'python' in cmdline_lower and 'http.server' in cmdline_lower:
98
+ subprocess.run(['kill', str(pid)], capture_output=True)
99
+ log(f'[playwright-file-guard] action=cleanup port={port} pid={pid} status=killed')
100
+ else:
101
+ log(f'[playwright-file-guard] action=cleanup port={port} pid={pid} status=pid-reused-skip')
102
+ try: os.remove(pid_file)
103
+ except OSError: pass
104
+
105
+ # Opportunistic sweep on every invocation — closes the gap that nothing
106
+ # currently calls cleanup on a periodic schedule.
107
+ sweep_stale()
108
+
109
+ if MODE == 'cleanup':
110
+ sys.exit(0)
111
+
112
+ try:
113
+ payload = json.loads(INPUT) if INPUT else {}
114
+ except Exception:
115
+ sys.exit(0)
116
+
117
+ tool_input = payload.get('tool_input') or {}
118
+ url = tool_input.get('url')
119
+ if not isinstance(url, str) or not url:
120
+ sys.exit(0)
121
+
122
+ clean_url = url.split('#', 1)[0].split('?', 1)[0]
123
+
124
+ if '://' not in clean_url:
125
+ log('[playwright-file-guard] action=passthrough scheme=none')
126
+ sys.exit(0)
127
+
128
+ scheme, rest = clean_url.split('://', 1)
129
+ scheme = scheme.lower()
130
+ if scheme != 'file':
131
+ log(f'[playwright-file-guard] action=passthrough scheme={scheme}')
132
+ sys.exit(0)
133
+
134
+ # file:///abs/path → /abs/path; file://host/path → /path (host ignored).
135
+ if rest.startswith('/'):
136
+ file_path = rest
137
+ elif '/' in rest:
138
+ file_path = '/' + rest.split('/', 1)[1]
139
+ else:
140
+ file_path = '/'
141
+
142
+ file_path = unquote(file_path)
143
+ abs_path = os.path.abspath(file_path)
144
+
145
+ if not os.path.isfile(abs_path):
146
+ log(f'[playwright-file-guard] action=fail reason=file-not-found path={abs_path}')
147
+ sys.exit(0)
148
+
149
+ served_dir = os.path.dirname(abs_path)
150
+ basename = os.path.basename(abs_path)
151
+
152
+ try:
153
+ s = socket.socket()
154
+ s.bind(('127.0.0.1', 0))
155
+ port = s.getsockname()[1]
156
+ s.close()
157
+ except Exception as e:
158
+ log(f'[playwright-file-guard] action=fail reason=port-pick-failed err={type(e).__name__}')
159
+ sys.exit(0)
160
+
161
+ try:
162
+ proc = subprocess.Popen(
163
+ [sys.executable, '-m', 'http.server', str(port), '--bind', '127.0.0.1'],
164
+ cwd=served_dir,
165
+ stdout=subprocess.DEVNULL,
166
+ stderr=subprocess.DEVNULL,
167
+ start_new_session=True,
168
+ )
169
+ except Exception as e:
170
+ log(f'[playwright-file-guard] action=fail reason=spawn-failed err={type(e).__name__}')
171
+ sys.exit(0)
172
+
173
+ pid_file = f'/tmp/playwright-file-guard.{port}.pid'
174
+ try:
175
+ with open(pid_file, 'w') as f:
176
+ f.write(str(proc.pid))
177
+ except Exception:
178
+ pass
179
+
180
+ # Connect-verify within 1s closes the TOCTOU window between bind(0) and the
181
+ # http.server actually accepting connections.
182
+ deadline = time.time() + 1.0
183
+ ready = False
184
+ while time.time() < deadline:
185
+ try:
186
+ sock = socket.create_connection(('127.0.0.1', port), timeout=0.2)
187
+ sock.close()
188
+ ready = True
189
+ break
190
+ except OSError:
191
+ time.sleep(0.05)
192
+
193
+ if not ready:
194
+ try: proc.kill()
195
+ except Exception: pass
196
+ try: os.remove(pid_file)
197
+ except OSError: pass
198
+ log(f'[playwright-file-guard] action=fail reason=server-not-ready port={port}')
199
+ sys.exit(0)
200
+
201
+ new_url = f'http://127.0.0.1:{port}/{basename}'
202
+ updated_input = dict(tool_input)
203
+ updated_input['url'] = new_url
204
+
205
+ print(json.dumps({
206
+ 'hookSpecificOutput': {
207
+ 'hookEventName': 'PreToolUse',
208
+ 'permissionDecision': 'allow',
209
+ 'permissionDecisionReason': 'file:// rewritten to local http.server',
210
+ 'updatedInput': updated_input,
211
+ },
212
+ }))
213
+ log(f'[playwright-file-guard] action=rewrite original=file://{abs_path} served_dir={served_dir} port={port} pid={proc.pid}')
214
+ PY
@@ -9,6 +9,7 @@ import { execFileSync } from "node:child_process";
9
9
  import { appendFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
10
10
  import { writeKey, validateKey, hasKey, keyFilePath, deleteKey } from "../../../../lib/anthropic-key/dist/index.js";
11
11
  import { writeAdminEntry, removeAdminFromAccount } from "../../../../lib/admins-write/dist/index.js";
12
+ import { isPersistentComponent } from "../../../../lib/persistent-components/dist/index.js";
12
13
  import { deviceUrlBlock } from "../../../../lib/device-url/dist/index.js";
13
14
  import { substituteBrandPlaceholders } from "../../../../lib/brand-templating/dist/index.js";
14
15
  import { resolveEntitlement } from "../../../../lib/entitlement/dist/index.js";
@@ -1631,12 +1632,50 @@ server.tool("plugin-read", "Read a plugin definition (PLUGIN.md) or one of its r
1631
1632
  };
1632
1633
  }
1633
1634
  });
1635
+ // Task 940 — `"rendered"` is a sentinel, not a persistence ack. The platform
1636
+ // UI's stream-parser intercepts this tool_use upstream of the SDK reply
1637
+ // (platform/ui/app/lib/claude-agent/stream-parser.ts:362) and emits a typed
1638
+ // `component` event that admin-agent.ts's persistMessage flushes to Neo4j as
1639
+ // a sibling :Component node.
1640
+ //
1641
+ // Task 942 — render-component is also the *server commitment surface* for
1642
+ // PERSISTENT_COMPONENTS (action-list, document-editor, rich-content-editor,
1643
+ // grid-editor). The actual file write + :KnowledgeDocument projection lives
1644
+ // in the admin server process (stream-parser path) because the SDK delivers
1645
+ // `tool_use.input` to the UI in the assistant message but the MCP handler's
1646
+ // return reaches only the agent — there is no path back to persistMessage
1647
+ // from here. This handler emits a one-line observability marker per
1648
+ // persistence-eligible call so operators can grep for the commitment, and
1649
+ // returns richer JSON so the doctrine grep
1650
+ // `render-component.*"rendered"` resolves to a persistence-aware handler
1651
+ // rather than a bare stub.
1634
1652
  server.tool("render-component", "Render a pre-built UI component inline in the conversation. " +
1635
1653
  "Call this instead of describing a UI — the component handles input collection. " +
1636
1654
  "Wait for the user's response before continuing.", {
1637
1655
  name: z.string().describe("Component name from the UI suite (e.g. single-select, confirm, info-card, form, progress)"),
1638
1656
  data: z.record(z.string(), z.unknown()),
1639
- }, async () => ({ content: [{ type: "text", text: "rendered" }] }));
1657
+ }, async ({ name, data }) => {
1658
+ if (isPersistentComponent(name)) {
1659
+ // Persistence-eligible: log the commitment so the live + audit grep
1660
+ // (`[render-component-persist]`) can correlate the MCP call against
1661
+ // the downstream :KnowledgeDocument MERGE. Byte counts come from
1662
+ // data.html (preferred) or data.content; both being absent is a
1663
+ // legitimate render of an empty editor and the projection is skipped
1664
+ // server-side.
1665
+ const html = typeof data?.html === "string" ? data.html : "";
1666
+ const content = typeof data?.content === "string" ? data.content : "";
1667
+ const bytes = html.length > 0 ? html.length : content.length;
1668
+ const mimeType = html.length > 0 ? "text/html" : (content.length > 0 ? "text/markdown" : "");
1669
+ console.error(`[render-component-persist] componentName=${name} bytes=${bytes} mimeType=${mimeType || "none"}`);
1670
+ return {
1671
+ content: [{
1672
+ type: "text",
1673
+ text: JSON.stringify({ rendered: true, name, persistent: true, bytes, mimeType }),
1674
+ }],
1675
+ };
1676
+ }
1677
+ return { content: [{ type: "text", text: "rendered" }] };
1678
+ });
1640
1679
  server.tool("session-reset", "Reset the current session. Compacts conversation history to memory, clears the visible conversation, " +
1641
1680
  "and starts a fresh session with a new greeting. Call when the user asks to start a new session, " +
1642
1681
  "clear the conversation, or start fresh.", {}, async () => ({ content: [{ type: "text", text: "reset" }] }));