@reclaimprotocol/agent 0.1.26 → 0.1.28

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 (92) hide show
  1. package/README.md +5 -5
  2. package/lib/authoring/tools.js +2 -1
  3. package/lib/consts.d.ts +1 -1
  4. package/lib/consts.js +1 -1
  5. package/lib/live/tunnel.d.ts +10 -7
  6. package/lib/live/tunnel.js +78 -56
  7. package/lib/mcp/api/operations.gen.d.ts +12 -8
  8. package/lib/mcp/api/operations.gen.js +378 -580
  9. package/lib/mcp/api/tools.d.ts +1 -1
  10. package/lib/mcp/instructions.js +7 -3
  11. package/lib/mcp/remap_errors.js +9 -5
  12. package/lib/mcp/start.js +1 -1
  13. package/lib/mcp/tools/agent/attach.d.ts +2 -2
  14. package/lib/mcp/tools/agent/attach.js +4 -0
  15. package/lib/mcp/tools/agent/build.d.ts +1 -1
  16. package/lib/mcp/tools/agent/live-view-url.d.ts +2 -0
  17. package/lib/mcp/tools/agent/live-view-url.js +11 -0
  18. package/lib/mcp/tools/agent/live-view.d.ts +4 -2
  19. package/lib/mcp/tools/agent/live-view.js +40 -54
  20. package/lib/mcp/tools/agent/propose.js +10 -4
  21. package/lib/mcp/tools/agent/publish.d.ts +1 -1
  22. package/lib/mcp/tools/agent/publish.js +49 -26
  23. package/lib/mcp/tools/agent/shared.js +0 -9
  24. package/lib/mcp/tools/authenticate/authenticate.d.ts +1 -1
  25. package/lib/mcp/tools/authenticate/build.d.ts +1 -1
  26. package/lib/mcp/tools/authenticate/builder-access.d.ts +1 -1
  27. package/lib/mcp/tools/verification/result.js +12 -13
  28. package/lib/old/login-cdp.d.ts +17 -6
  29. package/lib/old/login-cdp.js +28 -11
  30. package/lib/old/to-register.d.ts +6 -0
  31. package/lib/old/to-register.js +35 -5
  32. package/lib/old/tools/authenticate.d.ts +5 -4
  33. package/lib/old/tools/authenticate.js +9 -5
  34. package/lib/old/tools/publish.d.ts +2 -2
  35. package/lib/old/tools/publish.js +47 -21
  36. package/lib/proof/attestor-auth.d.ts +13 -0
  37. package/lib/proof/attestor-auth.js +16 -0
  38. package/lib/proof/attestor.d.ts +7 -4
  39. package/lib/proof/attestor.js +7 -4
  40. package/lib/proof/index.d.ts +1 -0
  41. package/lib/proof/index.js +1 -0
  42. package/lib/proof/pod-prover.d.ts +2 -0
  43. package/lib/proof/pod-prover.js +5 -5
  44. package/lib/provider/injection-doc.d.ts +2 -0
  45. package/lib/provider/injection-doc.js +7 -1
  46. package/lib/provider/post-publish-browser.d.ts +12 -0
  47. package/lib/provider/post-publish-browser.js +40 -0
  48. package/lib/provider/to-version.d.ts +19 -7
  49. package/lib/provider/to-version.js +67 -25
  50. package/lib/tools/registry.d.ts +1 -1
  51. package/lib/tools/registry.js +7 -4
  52. package/package.json +2 -2
  53. package/skill/SKILL.builder.md +134 -13
  54. package/skill/SKILL.common.md +30 -19
  55. package/skill/SKILL.old.md +22 -12
  56. package/skill/builder-mode.md +19 -9
  57. package/skill/community-contributions.md +108 -0
  58. package/skill/hash-validation.md +8 -5
  59. package/skill/interception.md +35 -22
  60. package/skill/old-mode.md +11 -5
  61. package/skill/user-script.md +41 -10
  62. package/src/authoring/tools.ts +2 -1
  63. package/src/consts.ts +1 -2
  64. package/src/live/tunnel.ts +112 -66
  65. package/src/mcp/api/operations.gen.ts +384 -584
  66. package/src/mcp/api/tools.ts +1 -1
  67. package/src/mcp/instructions.ts +7 -3
  68. package/src/mcp/remap_errors.ts +9 -7
  69. package/src/mcp/start.ts +1 -1
  70. package/src/mcp/tools/agent/attach.ts +6 -2
  71. package/src/mcp/tools/agent/build.ts +1 -1
  72. package/src/mcp/tools/agent/live-view-url.ts +13 -0
  73. package/src/mcp/tools/agent/live-view.ts +42 -54
  74. package/src/mcp/tools/agent/propose.ts +10 -4
  75. package/src/mcp/tools/agent/publish.ts +63 -28
  76. package/src/mcp/tools/agent/shared.ts +5 -0
  77. package/src/mcp/tools/authenticate/authenticate.ts +1 -1
  78. package/src/mcp/tools/authenticate/build.ts +1 -1
  79. package/src/mcp/tools/authenticate/builder-access.ts +1 -1
  80. package/src/mcp/tools/verification/result.ts +11 -13
  81. package/src/old/login-cdp.ts +38 -11
  82. package/src/old/to-register.ts +56 -7
  83. package/src/old/tools/authenticate.ts +9 -5
  84. package/src/old/tools/publish.ts +64 -22
  85. package/src/proof/attestor-auth.ts +33 -0
  86. package/src/proof/attestor.ts +14 -8
  87. package/src/proof/index.ts +4 -0
  88. package/src/proof/pod-prover.ts +7 -5
  89. package/src/provider/injection-doc.ts +9 -1
  90. package/src/provider/post-publish-browser.ts +56 -0
  91. package/src/provider/to-version.ts +104 -27
  92. package/src/tools/registry.ts +12 -9
@@ -24,42 +24,27 @@ import { type CloudflaredBinary, resolveCloudflared } from './cloudflared.ts'
24
24
 
25
25
  /** How long to wait for cloudflared to report its hostname before giving up. */
26
26
  const URL_TIMEOUT_MS = 30_000
27
- /** After the hostname is printed, Cloudflare needs a moment before the edge
28
- * routes to it — measured at ~3s. Wait for it so the caller never hands out a
29
- * URL that errors, but don't fail the share if it's slow. */
27
+ /** After the hostname is printed, Cloudflare needs time before the edge routes
28
+ * to it. Probe briefly, then return the still-running tunnel as propagating so
29
+ * slow DNS or edge setup does not destroy an otherwise healthy share. */
30
30
  const READY_TIMEOUT_MS = 20_000
31
31
  const READY_POLL_MS = 1_000
32
+ const STOP_TIMEOUT_MS = 5_000
32
33
 
33
34
  /** `https://<something>.trycloudflare.com`, as printed on cloudflared's log
34
35
  * stream. Anchored to the scheme so a hostname inside prose can't match. */
35
36
  const QUICK_TUNNEL_URL = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i
36
37
 
37
- /**
38
- * cloudflared says this once per edge connection it establishes, and until it
39
- * does, the hostname resolves but routes nowhere.
40
- *
41
- * This is the signal to trust, because it is LOCAL. Asking whether we can fetch
42
- * the public URL ourselves conflates two questions — is the tunnel up, and does
43
- * this machine have egress to Cloudflare's edge — and reports "not ready" when
44
- * only the second fails. Observed: a tunnel printed its hostname and registered
45
- * nothing for 12s, which is indistinguishable from a blocked fetch unless you
46
- * read cloudflared's own account of it.
47
- */
48
- const REGISTERED = /Registered tunnel connection|connIndex=/i
49
-
50
38
  /** Tail of cloudflared's log kept for error messages. */
51
39
  const LOG_TAIL_BYTES = 4096
52
40
 
53
41
  export interface Tunnel {
54
42
  /** The public HTTPS origin Cloudflare assigned. */
55
43
  url: string
56
- /** Whether the tunnel was carrying traffic before {@link READY_TIMEOUT_MS}
57
- * elapsed. False means it may still be propagating rather than broken — say
58
- * so rather than treating the share as failed. */
44
+ /** Whether the public viewer answered during the initial readiness probe. */
59
45
  ready: boolean
60
- /** cloudflared's own last words, for when `ready` is false and someone has
61
- * to work out why. Absent when the tunnel came up normally. */
62
- diagnostics?: string
46
+ /** Bounded cloudflared output when the edge is still propagating. */
47
+ cloudflaredLog?: string
63
48
  /** Terminate the tunnel. Safe to call more than once. */
64
49
  stop: () => Promise<void>
65
50
  /** Which cloudflared ran, and whether we had to fetch it. */
@@ -69,37 +54,43 @@ export interface Tunnel {
69
54
  /**
70
55
  * Wait until the tunnel is actually carrying traffic.
71
56
  *
72
- * Two independent signals, either of which is proof:
73
- * - cloudflared reported an edge registration (see {@link REGISTERED}). Local,
74
- * so it works regardless of this machine's own egress.
75
- * - the public origin answered. ANY HTTP response counts, including a 404:
76
- * the live-view server 404s unknown paths, so a status code at all proves
77
- * the hop is live. Kept as a second opinion in case a cloudflared release
78
- * rewords its log.
57
+ * A cloudflared "Registered tunnel connection" log is not enough: Cloudflare
58
+ * can report one while the assigned hostname still returns DNS NXDOMAIN. The
59
+ * public viewer itself must return a successful response.
79
60
  */
80
61
  async function waitUntilRouted(
81
62
  url: string,
82
- isRegistered: () => boolean,
63
+ {
64
+ isRunning,
65
+ pollMs,
66
+ timeoutMs,
67
+ }: {
68
+ isRunning: () => boolean
69
+ pollMs: number
70
+ timeoutMs: number
71
+ },
83
72
  ): Promise<boolean> {
84
- const deadline = Date.now() + READY_TIMEOUT_MS
73
+ const deadline = Date.now() + timeoutMs
85
74
  while(Date.now() < deadline) {
86
- if(isRegistered()) {
87
- return true
75
+ if(!isRunning()) {
76
+ return false
88
77
  }
89
78
 
90
79
  try {
91
- await fetch(url, {
80
+ const response = await fetch(url, {
92
81
  method: 'GET',
93
82
  redirect: 'manual',
94
- signal: AbortSignal.timeout(READY_POLL_MS * 2),
83
+ signal: AbortSignal.timeout(pollMs * 2),
95
84
  })
96
- return true
97
- } catch{
98
- await new Promise((r) => setTimeout(r, READY_POLL_MS))
99
- }
85
+ if(response.ok) {
86
+ return true
87
+ }
88
+ } catch{}
89
+
90
+ await new Promise((r) => setTimeout(r, pollMs))
100
91
  }
101
92
 
102
- return isRegistered()
93
+ return false
103
94
  }
104
95
 
105
96
  /**
@@ -111,7 +102,20 @@ async function waitUntilRouted(
111
102
  */
112
103
  export async function startTunnel(
113
104
  port: number,
114
- { autoInstall = true }: { autoInstall?: boolean } = {},
105
+ {
106
+ autoInstall = true,
107
+ probePath = '/',
108
+ readyPollMs = READY_POLL_MS,
109
+ readyTimeoutMs = READY_TIMEOUT_MS,
110
+ }: {
111
+ autoInstall?: boolean
112
+ /** Public path that must answer before the tunnel is returned. */
113
+ probePath?: string
114
+ /** Test-only timing override. */
115
+ readyPollMs?: number
116
+ /** Test-only timing override. */
117
+ readyTimeoutMs?: number
118
+ } = {},
115
119
  ): Promise<Tunnel> {
116
120
  // Resolves an existing cloudflared, or fetches one on first use. Throws
117
121
  // with an actionable message when neither is possible.
@@ -121,25 +125,14 @@ export async function startTunnel(
121
125
  '--no-autoupdate',
122
126
  '--url', `http://127.0.0.1:${port}`,
123
127
  ], { stdio: ['ignore', 'pipe', 'pipe'] })
128
+ const exited = new Promise<void>((resolve) => {
129
+ child.once('exit', () => resolve())
130
+ })
124
131
 
125
- const stop = async() => {
126
- if(!child.killed) {
127
- child.kill('SIGTERM')
128
- }
129
- }
132
+ let stopPromise: Promise<void> | undefined
133
+ const stop = () => stopPromise ??= stopChild()
130
134
 
131
135
  return new Promise<Tunnel>((resolve, reject) => {
132
- const timer = setTimeout(() => {
133
- void stop()
134
- reject(new Error(
135
- 'cloudflared did not report a tunnel hostname within '
136
- + `${URL_TIMEOUT_MS / 1000}s. Check network egress to Cloudflare, `
137
- + 'or run `cloudflared tunnel --url http://127.0.0.1:'
138
- + `${port}` + '` by hand to see its output.',
139
- ))
140
- }, URL_TIMEOUT_MS)
141
-
142
- let registered = false
143
136
  let tail = ''
144
137
  let settled = false
145
138
  const finish = (fn: () => void) => {
@@ -152,30 +145,60 @@ export async function startTunnel(
152
145
  fn()
153
146
  }
154
147
 
148
+ const timer = setTimeout(() => {
149
+ finish(() => {
150
+ void stop().then(() => reject(new Error(
151
+ 'cloudflared did not report a tunnel hostname within '
152
+ + `${URL_TIMEOUT_MS / 1000}s. Check network egress to `
153
+ + 'Cloudflare, or run `cloudflared tunnel --url '
154
+ + `http://127.0.0.1:${port}` + '` by hand to see its output.',
155
+ )))
156
+ })
157
+ }, URL_TIMEOUT_MS)
158
+
155
159
  // cloudflared logs the assigned hostname to stderr, not stdout. Watch
156
160
  // both so a future version that switches streams still works.
157
161
  const onChunk = (chunk: Buffer) => {
158
162
  const text = chunk.toString()
159
- // Keep watching after the hostname: registration lands a beat later, and
160
- // on a bad run it never lands at all, which is the case worth reporting.
163
+ // Keep watching after the hostname so a readiness failure can include
164
+ // cloudflared's account of what happened.
161
165
  tail = (tail + text).slice(-LOG_TAIL_BYTES)
162
- if(REGISTERED.test(text)) {
163
- registered = true
164
- }
165
166
 
166
- const match = QUICK_TUNNEL_URL.exec(text)
167
+ // Search the accumulated tail so a URL split across stream chunks is not
168
+ // missed. The tail is bounded and the URL appears near startup.
169
+ const match = QUICK_TUNNEL_URL.exec(tail)
167
170
  if(match) {
168
171
  const url = match[0]
169
172
  finish(() => {
170
- void waitUntilRouted(url, () => registered).then((ready) => {
173
+ const probeUrl = new URL(probePath, `${url}/`).toString()
174
+ void waitUntilRouted(probeUrl, {
175
+ isRunning: () => child.exitCode === null
176
+ && child.signalCode === null,
177
+ pollMs: readyPollMs,
178
+ timeoutMs: readyTimeoutMs,
179
+ }).then((ready) => {
171
180
  if(ready) {
172
181
  resolve({ url, ready, stop, binary })
173
182
  return
174
183
  }
175
184
 
185
+ if(child.exitCode !== null || child.signalCode !== null) {
186
+ reject(new Error(
187
+ 'cloudflared exited before the public viewer became reachable. '
188
+ + 'cloudflared output:\n' + tail,
189
+ ))
190
+ return
191
+ }
192
+
176
193
  LOGGER.warn({ url, tail },
177
- 'tunnel never registered with an edge; returning anyway')
178
- resolve({ url, ready, stop, binary, diagnostics: tail })
194
+ 'tunnel still propagating; returning the live hostname')
195
+ resolve({
196
+ url,
197
+ ready,
198
+ cloudflaredLog: tail,
199
+ stop,
200
+ binary,
201
+ })
179
202
  })
180
203
  })
181
204
  }
@@ -205,4 +228,27 @@ export async function startTunnel(
205
228
  )))
206
229
  })
207
230
  })
231
+
232
+ async function stopChild() {
233
+ if(child.exitCode !== null || child.signalCode !== null) {
234
+ return
235
+ }
236
+
237
+ let timeout: NodeJS.Timeout | undefined
238
+ const timedOut = new Promise<void>((resolve) => {
239
+ timeout = setTimeout(resolve, STOP_TIMEOUT_MS)
240
+ })
241
+ child.kill('SIGTERM')
242
+ await Promise.race([
243
+ exited,
244
+ timedOut,
245
+ ])
246
+ clearTimeout(timeout)
247
+ if(child.exitCode !== null || child.signalCode !== null) {
248
+ return
249
+ }
250
+
251
+ child.kill('SIGKILL')
252
+ await exited
253
+ }
208
254
  }