agentfootprint 9.7.0 → 9.9.0

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 (104) hide show
  1. package/dist/adapters/identity/vault.js +336 -0
  2. package/dist/adapters/identity/vault.js.map +1 -0
  3. package/dist/adapters/observability/audit.js +2 -28
  4. package/dist/adapters/observability/audit.js.map +1 -1
  5. package/dist/adapters/observability/file.js +307 -0
  6. package/dist/adapters/observability/file.js.map +1 -0
  7. package/dist/adapters/observability/githubBugReporter.js +461 -0
  8. package/dist/adapters/observability/githubBugReporter.js.map +1 -0
  9. package/dist/adapters/observability/githubDeviceSignIn.js +263 -0
  10. package/dist/adapters/observability/githubDeviceSignIn.js.map +1 -0
  11. package/dist/esm/adapters/identity/vault.d.ts +146 -0
  12. package/dist/esm/adapters/identity/vault.js +332 -0
  13. package/dist/esm/adapters/identity/vault.js.map +1 -0
  14. package/dist/esm/adapters/observability/audit.js +1 -27
  15. package/dist/esm/adapters/observability/audit.js.map +1 -1
  16. package/dist/esm/adapters/observability/file.d.ts +145 -0
  17. package/dist/esm/adapters/observability/file.js +303 -0
  18. package/dist/esm/adapters/observability/file.js.map +1 -0
  19. package/dist/esm/adapters/observability/githubBugReporter.d.ts +154 -0
  20. package/dist/esm/adapters/observability/githubBugReporter.js +457 -0
  21. package/dist/esm/adapters/observability/githubBugReporter.js.map +1 -0
  22. package/dist/esm/adapters/observability/githubDeviceSignIn.d.ts +131 -0
  23. package/dist/esm/adapters/observability/githubDeviceSignIn.js +259 -0
  24. package/dist/esm/adapters/observability/githubDeviceSignIn.js.map +1 -0
  25. package/dist/esm/identity.d.ts +1 -0
  26. package/dist/esm/identity.js +4 -0
  27. package/dist/esm/identity.js.map +1 -1
  28. package/dist/esm/lib/bug-report/build.d.ts +103 -0
  29. package/dist/esm/lib/bug-report/build.js +648 -0
  30. package/dist/esm/lib/bug-report/build.js.map +1 -0
  31. package/dist/esm/lib/bug-report/index.d.ts +14 -0
  32. package/dist/esm/lib/bug-report/index.js +13 -0
  33. package/dist/esm/lib/bug-report/index.js.map +1 -0
  34. package/dist/esm/lib/bug-report/transcript.d.ts +61 -0
  35. package/dist/esm/lib/bug-report/transcript.js +124 -0
  36. package/dist/esm/lib/bug-report/transcript.js.map +1 -0
  37. package/dist/esm/lib/bug-report/types.d.ts +206 -0
  38. package/dist/esm/lib/bug-report/types.js +12 -0
  39. package/dist/esm/lib/bug-report/types.js.map +1 -0
  40. package/dist/esm/lib/bug-report/zip.d.ts +71 -0
  41. package/dist/esm/lib/bug-report/zip.js +202 -0
  42. package/dist/esm/lib/bug-report/zip.js.map +1 -0
  43. package/dist/esm/lib/libraryVersion.d.ts +23 -0
  44. package/dist/esm/lib/libraryVersion.js +46 -0
  45. package/dist/esm/lib/libraryVersion.js.map +1 -0
  46. package/dist/esm/lib/trace-toolpack/openRecording.d.ts +17 -0
  47. package/dist/esm/lib/trace-toolpack/openRecording.js +8 -2
  48. package/dist/esm/lib/trace-toolpack/openRecording.js.map +1 -1
  49. package/dist/esm/observability-providers.d.ts +6 -1
  50. package/dist/esm/observability-providers.js +16 -1
  51. package/dist/esm/observability-providers.js.map +1 -1
  52. package/dist/esm/observe.d.ts +1 -0
  53. package/dist/esm/observe.js +6 -0
  54. package/dist/esm/observe.js.map +1 -1
  55. package/dist/identity.js +6 -1
  56. package/dist/identity.js.map +1 -1
  57. package/dist/lib/bug-report/build.js +656 -0
  58. package/dist/lib/bug-report/build.js.map +1 -0
  59. package/dist/lib/bug-report/index.js +18 -0
  60. package/dist/lib/bug-report/index.js.map +1 -0
  61. package/dist/lib/bug-report/transcript.js +128 -0
  62. package/dist/lib/bug-report/transcript.js.map +1 -0
  63. package/dist/lib/bug-report/types.js +13 -0
  64. package/dist/lib/bug-report/types.js.map +1 -0
  65. package/dist/lib/bug-report/zip.js +207 -0
  66. package/dist/lib/bug-report/zip.js.map +1 -0
  67. package/dist/lib/libraryVersion.js +51 -0
  68. package/dist/lib/libraryVersion.js.map +1 -0
  69. package/dist/lib/trace-toolpack/openRecording.js +9 -2
  70. package/dist/lib/trace-toolpack/openRecording.js.map +1 -1
  71. package/dist/observability-providers.js +20 -2
  72. package/dist/observability-providers.js.map +1 -1
  73. package/dist/observe.js +14 -6
  74. package/dist/observe.js.map +1 -1
  75. package/dist/types/adapters/identity/vault.d.ts +147 -0
  76. package/dist/types/adapters/identity/vault.d.ts.map +1 -0
  77. package/dist/types/adapters/observability/audit.d.ts.map +1 -1
  78. package/dist/types/adapters/observability/file.d.ts +146 -0
  79. package/dist/types/adapters/observability/file.d.ts.map +1 -0
  80. package/dist/types/adapters/observability/githubBugReporter.d.ts +155 -0
  81. package/dist/types/adapters/observability/githubBugReporter.d.ts.map +1 -0
  82. package/dist/types/adapters/observability/githubDeviceSignIn.d.ts +132 -0
  83. package/dist/types/adapters/observability/githubDeviceSignIn.d.ts.map +1 -0
  84. package/dist/types/identity.d.ts +1 -0
  85. package/dist/types/identity.d.ts.map +1 -1
  86. package/dist/types/lib/bug-report/build.d.ts +104 -0
  87. package/dist/types/lib/bug-report/build.d.ts.map +1 -0
  88. package/dist/types/lib/bug-report/index.d.ts +15 -0
  89. package/dist/types/lib/bug-report/index.d.ts.map +1 -0
  90. package/dist/types/lib/bug-report/transcript.d.ts +62 -0
  91. package/dist/types/lib/bug-report/transcript.d.ts.map +1 -0
  92. package/dist/types/lib/bug-report/types.d.ts +207 -0
  93. package/dist/types/lib/bug-report/types.d.ts.map +1 -0
  94. package/dist/types/lib/bug-report/zip.d.ts +72 -0
  95. package/dist/types/lib/bug-report/zip.d.ts.map +1 -0
  96. package/dist/types/lib/libraryVersion.d.ts +24 -0
  97. package/dist/types/lib/libraryVersion.d.ts.map +1 -0
  98. package/dist/types/lib/trace-toolpack/openRecording.d.ts +17 -0
  99. package/dist/types/lib/trace-toolpack/openRecording.d.ts.map +1 -1
  100. package/dist/types/observability-providers.d.ts +6 -1
  101. package/dist/types/observability-providers.d.ts.map +1 -1
  102. package/dist/types/observe.d.ts +1 -0
  103. package/dist/types/observe.d.ts.map +1 -1
  104. package/package.json +1 -1
@@ -0,0 +1,457 @@
1
+ /**
2
+ * githubBugReporter — file a bug report, with the run attached, into GitHub.
3
+ *
4
+ * import { exportBugReport, githubBugReporter } from 'agentfootprint/observe';
5
+ *
6
+ * const reporter = githubBugReporter({
7
+ * issueRepo: 'acme/checkout-agent', // where the ISSUE goes
8
+ * evidenceRepo: 'acme/agent-evidence', // where the ZIP goes (default: issueRepo)
9
+ * }); // token: GITHUB_TOKEN, or `token`
10
+ *
11
+ * const { issueUrl, zipUrl } = await reporter.file(report);
12
+ *
13
+ * Two HTTP calls and no SDK: `PUT /repos/{evidenceRepo}/contents/{path}` commits
14
+ * the zip, `POST /repos/{issueRepo}/issues` files the issue with a manifest
15
+ * table and a link to the committed bundle. Plain `fetch`, zero dependencies,
16
+ * `apiBase` for GitHub Enterprise Server — so this works unchanged on a network
17
+ * that never reaches github.com.
18
+ *
19
+ * ## TWIN TARGETS: the issue and the evidence may live in different repos
20
+ *
21
+ * The case this exists for: a field tester finds a bug in a LIBRARY. The issue
22
+ * belongs in the library's public repo, where the maintainers and the next
23
+ * person to hit it will find it. The evidence — a real run, with real prompts,
24
+ * real tool arguments and real retrieved documents — does not. So the zip goes
25
+ * into a PRIVATE repo the maintainers can read, and the issue links it and says
26
+ * plainly that the evidence is private.
27
+ *
28
+ * ```ts
29
+ * githubBugReporter({
30
+ * issueRepo: 'footprintjs/agentfootprint', // public — the conversation
31
+ * evidenceRepo: 'acme/af-bug-evidence', // private — the run
32
+ * });
33
+ * ```
34
+ *
35
+ * ## DEFAULT-TARGET DOCTRINE
36
+ *
37
+ * **File into the application's OWN repo.** That is the default (`evidenceRepo`
38
+ * defaults to `issueRepo`) and it is the right default: the run belongs to the
39
+ * organisation that produced it. Sending a run's evidence across an
40
+ * organisational boundary — to a vendor, to an upstream library, to anyone
41
+ * whose access your company did not grant — is a HUMAN act with consequences a
42
+ * library cannot weigh. This adapter will do it, because a field tester filing
43
+ * upstream is a real and valuable thing; it will not do it quietly. The
44
+ * consent manifest (`describeBugReport`) exists so a person sees exactly what
45
+ * would leave before it does, and this reporter refuses to commit evidence to a
46
+ * PUBLIC repo unless the caller says `acknowledgePublicEvidence: true` out loud.
47
+ *
48
+ * ## Provisioning the token: fine-grained, and scoped to two repos
49
+ *
50
+ * Use a **fine-grained personal access token** (GitHub → Settings → Developer
51
+ * settings → Fine-grained tokens), scoped to ONLY `issueRepo` and
52
+ * `evidenceRepo`, with exactly two permissions — **Contents: read and write**
53
+ * (to commit the zip) and **Issues: read and write** (to file the issue) — and
54
+ * an expiry date. Put it in the server's environment as `GITHUB_TOKEN`, or pass
55
+ * it as `token`.
56
+ *
57
+ * The contrast matters: a CLASSIC PAT's `repo` scope is coarse — it grants
58
+ * read/write across every repository the account can reach, so a leaked
59
+ * bug-report token is a leaked key to the whole account. With a fine-grained
60
+ * token scoped as above, the blast radius of a leak is filing bug reports and
61
+ * committing files to one evidence repo, and nothing else. GitHub App
62
+ * installation tokens (short-lived, org-installed, revocable centrally) are the
63
+ * next rung for an organisation that wants one; this adapter does not mint
64
+ * them — hand it the token your app already obtained.
65
+ *
66
+ * ## Secrecy (the two-clause law)
67
+ *
68
+ * The token appears in no message, no error and no log this adapter can
69
+ * produce, and neither does the bundle's content. A failed request is reported
70
+ * as **the status and GitHub's own `message` field** — never the request, never
71
+ * the headers, never the body that carried the token, never a byte of
72
+ * evidence. Transport failures are re-wrapped rather than rethrown, because a
73
+ * `fetch` implementation is free to put the request (headers included) into the
74
+ * error it throws. Nothing here writes to a console. Pinned by a suite that
75
+ * forces every failure path and greps the message, the stack and the JSON
76
+ * projection for the token.
77
+ *
78
+ * @example A server route (the app's own repo, the default target)
79
+ * ```ts
80
+ * const reporter = githubBugReporter({ issueRepo: 'acme/checkout-agent' });
81
+ * app.post('/bug-report', async (req, res) => {
82
+ * const report = exportBugReport(recordings.get(req.body.runId), req.body.fields);
83
+ * res.json(await reporter.file(report));
84
+ * });
85
+ * ```
86
+ */
87
+ import { formatBytes } from '../../lib/bug-report/build.js';
88
+ /** GitHub's own ceiling for a file committed through the contents API is 100 MB,
89
+ * but an issue attachment that large is not a bug report anybody opens. 24 MB
90
+ * keeps a bundle inside every practical limit including the 25 MB the web UI
91
+ * accepts for a drag-and-dropped file. */
92
+ const DEFAULT_MAX_ZIP_BYTES = 24 * 1024 * 1024;
93
+ const DEFAULT_API_BASE = 'https://api.github.com';
94
+ const FINE_GRAINED_TOKEN_PAGE = 'Settings → Developer settings → Fine-grained tokens';
95
+ export function githubBugReporter(options) {
96
+ const issueRepo = checkedRepo(options.issueRepo, 'issueRepo');
97
+ const evidenceRepo = options.evidenceRepo
98
+ ? checkedRepo(options.evidenceRepo, 'evidenceRepo')
99
+ : issueRepo;
100
+ const token = options.token ?? readEnv('GITHUB_TOKEN');
101
+ if (!token) {
102
+ throw new TypeError('githubBugReporter: no GitHub token. Pass `token`, or set the GITHUB_TOKEN environment ' +
103
+ 'variable on the server (a token belongs in the environment, not in the option in a ' +
104
+ `committed config file). Make it a FINE-GRAINED token — ${FINE_GRAINED_TOKEN_PAGE} — ` +
105
+ `scoped to just ${evidenceRepo === issueRepo ? issueRepo : `${issueRepo} and ${evidenceRepo}`}, ` +
106
+ 'with Contents: read/write and Issues: read/write, and an expiry date. A classic PAT ' +
107
+ "grants those permissions across every repo the account can reach; this token's " +
108
+ 'blast radius should be filing bug reports and nothing else.');
109
+ }
110
+ const apiBase = (options.apiBase ?? DEFAULT_API_BASE).replace(/\/+$/, '');
111
+ const dir = (options.dir ?? 'bug-reports').replace(/^\/+|\/+$/g, '');
112
+ const maxZipBytes = options.maxZipBytes ?? DEFAULT_MAX_ZIP_BYTES;
113
+ const doFetch = options._fetch ?? ((...args) => fetch(...args));
114
+ return {
115
+ async file(report) {
116
+ if (!report || !(report.zip instanceof Uint8Array) || !report.manifest) {
117
+ throw new TypeError('githubBugReporter.file: expected the object exportBugReport() returns ' +
118
+ '({ manifest, files, zip, filename }).');
119
+ }
120
+ // 1. SIZE, before a byte moves. Refusing after a 24 MB upload wastes the
121
+ // reporter's time and GitHub's, and the manifest already computed the
122
+ // way out.
123
+ if (report.zip.length > maxZipBytes) {
124
+ throw new Error(`githubBugReporter: refusing to upload a ${formatBytes(report.zip.length)} bundle — ` +
125
+ `the ceiling is ${formatBytes(maxZipBytes)}. GitHub accepts far larger files through the ` +
126
+ `contents API, but a bundle this size is not one a maintainer opens. Send fewer ` +
127
+ `units: ${trimAdvice(report)}`);
128
+ }
129
+ // 2. VISIBILITY. The doctrine's teeth: evidence is a real run, and a
130
+ // public repo publishes it permanently.
131
+ const visibility = await readVisibility({ apiBase, evidenceRepo, token, doFetch });
132
+ if (visibility.checked &&
133
+ visibility.isPrivate === false &&
134
+ !options.acknowledgePublicEvidence) {
135
+ throw new Error(`githubBugReporter: refusing to commit evidence to '${evidenceRepo}', which is a ` +
136
+ `PUBLIC repository. A bug-report bundle carries a real run — prompts, tool ` +
137
+ `arguments, retrieved documents — and committing it here publishes all of it ` +
138
+ `permanently. Point \`evidenceRepo\` at a private repo the maintainers can read ` +
139
+ `(the issue can still go to the public one), or pass ` +
140
+ `\`acknowledgePublicEvidence: true\` if this is intended.`);
141
+ }
142
+ // 3. COMMIT the zip.
143
+ const uploaded = await uploadZip({
144
+ apiBase,
145
+ evidenceRepo,
146
+ dir,
147
+ token,
148
+ doFetch,
149
+ report,
150
+ ...(options.branch !== undefined && { branch: options.branch }),
151
+ });
152
+ // 4. FILE the issue, pointing at it.
153
+ const issueUrl = await createIssue({
154
+ apiBase,
155
+ issueRepo,
156
+ token,
157
+ doFetch,
158
+ report,
159
+ zipUrl: uploaded.zipUrl,
160
+ zipPath: uploaded.zipPath,
161
+ evidenceRepo,
162
+ crossRepo: evidenceRepo !== issueRepo,
163
+ evidencePrivate: visibility.checked ? visibility.isPrivate : undefined,
164
+ ...(options.labels !== undefined && { labels: options.labels }),
165
+ });
166
+ return {
167
+ issueUrl,
168
+ zipUrl: uploaded.zipUrl,
169
+ zipPath: uploaded.zipPath,
170
+ evidenceRepo,
171
+ checkedVisibility: visibility.checked,
172
+ ...(visibility.checked && { evidenceRepoPrivate: visibility.isPrivate }),
173
+ };
174
+ },
175
+ };
176
+ }
177
+ /**
178
+ * Read the evidence repo's visibility.
179
+ *
180
+ * Never blocks on a permissions quirk: a token with `contents: write` but no
181
+ * metadata read is a legitimate configuration, and a bug report that cannot be
182
+ * filed because a GUARD could not run has failed at its one job. The result
183
+ * carries `checked: false` so the caller learns the guard was skipped.
184
+ */
185
+ async function readVisibility(args) {
186
+ try {
187
+ const res = await args.doFetch(`${args.apiBase}/repos/${args.evidenceRepo}`, {
188
+ method: 'GET',
189
+ headers: githubHeaders(args.token),
190
+ });
191
+ if (!res.ok)
192
+ return { checked: false, isPrivate: false };
193
+ const body = (await res.json());
194
+ if (typeof body?.private !== 'boolean')
195
+ return { checked: false, isPrivate: false };
196
+ return { checked: true, isPrivate: body.private };
197
+ }
198
+ catch {
199
+ // A transport failure here is not the report's problem, and the error is
200
+ // deliberately not propagated — it is the one place where a thrown fetch
201
+ // could carry request headers into a message.
202
+ return { checked: false, isPrivate: false };
203
+ }
204
+ }
205
+ /** Commit the zip, suffixing the name if that path is already taken. */
206
+ async function uploadZip(args) {
207
+ const base = args.report.filename.replace(/\.zip$/, '');
208
+ const content = toBase64(args.report.zip);
209
+ const title = args.report.manifest.report?.title ?? 'bug report';
210
+ // A name collision is ordinary (two reports of the same bug on the same day),
211
+ // and GitHub answers a create-without-sha on an existing path with 409/422.
212
+ // Suffix and retry rather than overwrite: the earlier bundle is somebody
213
+ // else's evidence.
214
+ for (let attempt = 1; attempt <= 5; attempt++) {
215
+ const path = `${args.dir}/${base}-${shortId()}.zip`;
216
+ const res = await request({
217
+ ...args,
218
+ url: `${args.apiBase}/repos/${args.evidenceRepo}/contents/${encodePath(path)}`,
219
+ method: 'PUT',
220
+ body: {
221
+ message: `bug report: ${title}`,
222
+ content,
223
+ ...(args.branch !== undefined && { branch: args.branch }),
224
+ },
225
+ what: `commit the evidence bundle to '${args.evidenceRepo}'`,
226
+ allowStatuses: [409, 422],
227
+ });
228
+ if (res.ok) {
229
+ const body = res.body;
230
+ const htmlUrl = typeof body?.content?.html_url === 'string'
231
+ ? body.content.html_url
232
+ : `https://github.com/${args.evidenceRepo}/blob/HEAD/${path}`;
233
+ return { zipUrl: htmlUrl, zipPath: path };
234
+ }
235
+ if (attempt === 5) {
236
+ throw new Error(`githubBugReporter: could not commit the evidence bundle to '${args.evidenceRepo}' — ` +
237
+ `five names in '${args.dir}/' were already taken (GitHub answered ${res.status}: ` +
238
+ `${res.message}). Check that the token has Contents: read/write there, or set ` +
239
+ `\`dir\` to somewhere less crowded.`);
240
+ }
241
+ }
242
+ /* istanbul ignore next — the loop always returns or throws. */
243
+ throw new Error('githubBugReporter: unreachable');
244
+ }
245
+ async function createIssue(args) {
246
+ const res = await request({
247
+ ...args,
248
+ url: `${args.apiBase}/repos/${args.issueRepo}/issues`,
249
+ method: 'POST',
250
+ body: {
251
+ title: args.report.manifest.report?.title ?? 'Bug report',
252
+ body: issueBody(args),
253
+ ...(args.labels && args.labels.length > 0 && { labels: [...args.labels] }),
254
+ },
255
+ what: `file the issue on '${args.issueRepo}'`,
256
+ allowStatuses: [],
257
+ });
258
+ const body = res.body;
259
+ return typeof body?.html_url === 'string'
260
+ ? body.html_url
261
+ : `https://github.com/${args.issueRepo}/issues`;
262
+ }
263
+ /**
264
+ * One request.
265
+ *
266
+ * Everything about the secrecy law lives here: the token goes into a header and
267
+ * nowhere else; a thrown fetch is re-wrapped so no implementation can smuggle
268
+ * its request (headers included) into the text; a non-2xx names the status and
269
+ * GitHub's `message` field ONLY — never the request body, which for the upload
270
+ * is the base64 of the evidence and for every call travelled beside the token.
271
+ */
272
+ async function request(args) {
273
+ let res;
274
+ try {
275
+ res = await args.doFetch(args.url, {
276
+ method: args.method,
277
+ headers: { ...githubHeaders(args.token), 'content-type': 'application/json' },
278
+ body: JSON.stringify(args.body),
279
+ });
280
+ }
281
+ catch (err) {
282
+ throw new Error(`githubBugReporter: could not reach GitHub to ${args.what} (${transportReason(err)}). ` +
283
+ `Check the network and \`apiBase\`.`);
284
+ }
285
+ let body;
286
+ try {
287
+ body = await res.json();
288
+ }
289
+ catch {
290
+ body = undefined;
291
+ }
292
+ const message = githubMessage(body);
293
+ if (res.ok)
294
+ return { ok: true, status: res.status, message, body };
295
+ if (args.allowStatuses.includes(res.status)) {
296
+ return { ok: false, status: res.status, message, body };
297
+ }
298
+ throw new Error(`githubBugReporter: GitHub answered ${res.status} when asked to ${args.what}` +
299
+ `${message ? ` — "${message}"` : ''}.${statusHint(res.status)}`);
300
+ }
301
+ /** GitHub's error envelope is `{ message, documentation_url, errors }`. Only
302
+ * `message` is ever surfaced: `errors[]` can quote the request. */
303
+ function githubMessage(body) {
304
+ const message = body?.message;
305
+ return typeof message === 'string' ? message.slice(0, 200) : '';
306
+ }
307
+ function statusHint(status) {
308
+ if (status === 401) {
309
+ return (` The token is missing, expired or revoked — mint a new fine-grained one ` +
310
+ `(${FINE_GRAINED_TOKEN_PAGE}).`);
311
+ }
312
+ if (status === 403) {
313
+ return (` The token is valid but not permitted here. A fine-grained token needs Contents: ` +
314
+ `read/write on the evidence repo and Issues: read/write on the issue repo, and it must ` +
315
+ `list BOTH repositories in its repository access.`);
316
+ }
317
+ if (status === 404) {
318
+ return (` Either the repository does not exist under that name, or the token cannot see it — ` +
319
+ `GitHub answers 404 rather than 403 for a private repo a token has no access to.`);
320
+ }
321
+ if (status === 410)
322
+ return ' Issues are disabled on that repository.';
323
+ return '';
324
+ }
325
+ function githubHeaders(token) {
326
+ return {
327
+ authorization: `Bearer ${token}`,
328
+ accept: 'application/vnd.github+json',
329
+ 'x-github-api-version': '2022-11-28',
330
+ };
331
+ }
332
+ /** A short, payload-free reason. Never the error's own message: a fetch may put
333
+ * the request — headers included — in it. */
334
+ function transportReason(err) {
335
+ if (err instanceof Error) {
336
+ if (err.name === 'TimeoutError' || err.name === 'AbortError')
337
+ return 'the request timed out';
338
+ return err.name || 'network error';
339
+ }
340
+ return 'network error';
341
+ }
342
+ // ─── Small pure helpers ──────────────────────────────────────────────
343
+ function checkedRepo(repo, option) {
344
+ if (typeof repo !== 'string' || !/^[^/\s]+\/[^/\s]+$/.test(repo)) {
345
+ throw new TypeError(`githubBugReporter: \`${option}\` must be 'owner/name' (e.g. 'acme/checkout-agent'), ` +
346
+ `not '${String(repo)}'. \`issueRepo\` is where the ISSUE is filed; \`evidenceRepo\` ` +
347
+ `is where the evidence ZIP is committed, and defaults to \`issueRepo\`.`);
348
+ }
349
+ return repo;
350
+ }
351
+ function readEnv(name) {
352
+ const env = globalThis.process
353
+ ?.env;
354
+ const value = env?.[name];
355
+ return typeof value === 'string' && value !== '' ? value : undefined;
356
+ }
357
+ /** Base64 for the contents API. Node-only, and it says so by name. */
358
+ function toBase64(bytes) {
359
+ const buffer = globalThis.Buffer;
360
+ if (!buffer) {
361
+ throw new Error('githubBugReporter is a server-side reporter and needs Node (it base64-encodes the ' +
362
+ 'bundle with Buffer). In a browser, build the bundle with exportBugReport() and POST ' +
363
+ 'it to your own server, which files it — a browser holding a GitHub token is a token ' +
364
+ 'anyone with the page can read.');
365
+ }
366
+ return buffer.from(bytes).toString('base64');
367
+ }
368
+ /** Six characters of collision resistance in a filename. */
369
+ function shortId() {
370
+ const random = globalThis
371
+ .crypto;
372
+ if (random?.getRandomValues) {
373
+ const bytes = random.getRandomValues(new Uint8Array(4));
374
+ return [...bytes]
375
+ .map((byte) => byte.toString(16).padStart(2, '0'))
376
+ .join('')
377
+ .slice(0, 6);
378
+ }
379
+ return Math.random().toString(36).slice(2, 8);
380
+ }
381
+ const encodePath = (path) => path.split('/').map(encodeURIComponent).join('/');
382
+ /** The manifest already worked out what to drop; repeat it rather than invent. */
383
+ function trimAdvice(report) {
384
+ const hints = report.manifest.oversize?.trimHints;
385
+ if (hints && hints.length > 0)
386
+ return hints.join(' ');
387
+ const biggest = [...report.manifest.units]
388
+ .filter((unit) => unit.kind === 'conversation')
389
+ .sort((left, right) => right.bytes - left.bytes)
390
+ .slice(0, 3);
391
+ return biggest.length > 0
392
+ ? `pass \`include\` to exportBugReport() without ${biggest
393
+ .map((unit) => `${unit.id} (${formatBytes(unit.bytes)})`)
394
+ .join(' / ')}.`
395
+ : 'pass `include` to exportBugReport() with fewer units.';
396
+ }
397
+ // ─── The issue body ──────────────────────────────────────────────────
398
+ function issueBody(args) {
399
+ const { manifest } = args.report;
400
+ const fields = manifest.report;
401
+ const lines = [];
402
+ if (fields) {
403
+ lines.push('### Steps to reproduce', '', fields.stepsToReproduce, '');
404
+ lines.push('### Expected', '', fields.expected, '');
405
+ lines.push('### Actual', '', fields.actual, '');
406
+ }
407
+ lines.push('### The run is attached', '');
408
+ lines.push(`[\`${args.zipPath}\`](${args.zipUrl}) — the recorded run itself.`);
409
+ if (args.crossRepo) {
410
+ lines.push('', args.evidencePrivate === false
411
+ ? `The evidence is committed to \`${args.evidenceRepo}\`, a separate PUBLIC repository.`
412
+ : `The evidence is committed to \`${args.evidenceRepo}\`, a private repository visible ` +
413
+ `to maintainers — this issue links it but does not contain it.`);
414
+ }
415
+ lines.push('', 'Open it with `observeRecording()` (agentfootprint-lens) or the trace tools — ' +
416
+ '`recording.json` is the canon `{ snapshot, events, structure }`.', '');
417
+ lines.push('### What is in the bundle', '');
418
+ lines.push('| file | bytes | events | turns |', '|---|---:|---:|---:|');
419
+ for (const file of manifest.files) {
420
+ lines.push(`| \`${file.name}\` | ${file.bytes} | ${file.eventCount ?? ''} | ${file.turnCount ?? ''} |`);
421
+ }
422
+ lines.push('', `${manifest.counts.conversations} conversation(s), ${manifest.counts.runs} run(s), ` +
423
+ `${manifest.counts.events} event(s), ${manifest.counts.turns} turn(s), ` +
424
+ `${manifest.totalBytes} bytes uncompressed.`, '');
425
+ if (manifest.excluded.conversations > 0 || manifest.excluded.files > 0) {
426
+ lines.push(`**The reporter chose a subset.** Left out: ${manifest.excluded.conversations} ` +
427
+ `conversation(s) (${manifest.excluded.events} events, ${manifest.excluded.turns} ` +
428
+ `turns) and ${manifest.excluded.files} file(s).`, '');
429
+ }
430
+ lines.push('### Redacted keys', '', manifest.redactedKeys.length > 0
431
+ ? `Scrubbed at commit time by the run's redaction policy, listed by name only: ` +
432
+ `${manifest.redactedKeys.map((key) => `\`${key}\``).join(', ')}.`
433
+ : 'None — this run had no redaction policy, so every value in the bundle is real.', '');
434
+ const env = manifest.environment;
435
+ lines.push('### Environment', '');
436
+ lines.push('```');
437
+ lines.push(`agentfootprint ${env.agentfootprint}`);
438
+ lines.push(`footprintjs ${env.footprintjs}`);
439
+ lines.push(`node ${env.node}`);
440
+ lines.push(`platform ${env.platform}/${env.arch}`);
441
+ if (env.appVersion)
442
+ lines.push(`app ${env.appVersion}`);
443
+ lines.push('```');
444
+ if (manifest.warnings.length > 0) {
445
+ lines.push('', '### Warnings', '');
446
+ for (const warning of manifest.warnings)
447
+ lines.push(`- ${warning}`);
448
+ }
449
+ if (manifest.notes.length > 0) {
450
+ lines.push('', '<details><summary>Notes about this bundle</summary>', '');
451
+ for (const note of manifest.notes)
452
+ lines.push(`- ${note}`);
453
+ lines.push('', '</details>');
454
+ }
455
+ return lines.join('\n');
456
+ }
457
+ //# sourceMappingURL=githubBugReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"githubBugReporter.js","sourceRoot":"","sources":["../../../../src/adapters/observability/githubBugReporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * githubDeviceSignIn — let the REPORTER sign in, so the issue is filed as them.
3
+ *
4
+ * import { githubDeviceSignIn } from 'agentfootprint/observe';
5
+ *
6
+ * const signIn = await githubDeviceSignIn({ clientId: 'Iv1.0123456789abcdef' });
7
+ * show(`Go to ${signIn.verificationUri} and enter ${signIn.userCode}`);
8
+ * const { token, login } = await signIn.completed; // resolves on authorize
9
+ *
10
+ * GitHub's OAuth **device flow**, spoken as three plain `fetch` calls and
11
+ * nothing else: request a code, show it to the human, poll until they approve.
12
+ * Zero dependencies, and it runs in a browser as well as on a server — there is
13
+ * no client secret in this flow, which is exactly why it is the one that works
14
+ * from a page.
15
+ *
16
+ * ## Why this exists beside a server token
17
+ *
18
+ * A server-side PAT files every report as the application. That is right for an
19
+ * automated "Report a problem" button: the app owns the repo, the app owns the
20
+ * token. It is wrong when the value is ATTRIBUTION — a field tester filing
21
+ * upstream should appear as themselves, so a maintainer can ask them a
22
+ * follow-up question and so their report counts as theirs.
23
+ *
24
+ * | | server PAT | device sign-in |
25
+ * |---|---|---|
26
+ * | Who the issue is from | the application | the reporter |
27
+ * | Where the token lives | server environment | the reporter's session, in memory |
28
+ * | Human steps | none | one: enter a code, approve |
29
+ * | Right for | in-app "report a problem" | field testers filing upstream |
30
+ *
31
+ * The token this returns is handed to {@link githubBugReporter} as `token`,
32
+ * with no special-casing anywhere: a token is a token.
33
+ *
34
+ * ## Keep it in memory, for the session only
35
+ *
36
+ * **Never `localStorage`, never a cookie, never a log line.** A device-flow
37
+ * token is a live credential for the account that approved it; persisting it in
38
+ * a browser turns one XSS into a lasting account compromise. Hold it in a
39
+ * variable, use it, drop it when the tab closes.
40
+ *
41
+ * ## Scopes are coarse here, and that is GitHub's design
42
+ *
43
+ * The device flow issues a CLASSIC OAuth token, and classic scopes are coarse:
44
+ * `public_repo` (the default here) grants write across every public repository
45
+ * the account can reach, and `repo` grants it across private ones too. There is
46
+ * no fine-grained equivalent in this flow. That trade buys attribution — the
47
+ * issue is really from that person — and it is the reason the default stops at
48
+ * `public_repo`: filing an issue needs no more, and the token disappears with
49
+ * the session. Where least privilege matters more than attribution, use a
50
+ * fine-grained PAT on a server instead (see {@link githubBugReporter}).
51
+ *
52
+ * ## The collaborator caveat, stated rather than discovered
53
+ *
54
+ * A reporter who signs in as themselves can file an issue on a public repo, and
55
+ * can commit evidence ONLY to a repository they can write to. Pointing
56
+ * `evidenceRepo` at a private repo the reporter is not a collaborator on will
57
+ * fail with a 404 (GitHub hides private repos from tokens that cannot see
58
+ * them). Either add the reporter as a collaborator, or let them attach the zip
59
+ * to the issue by hand — `exportBugReport` gives them the file either way.
60
+ *
61
+ * ## Secrecy
62
+ *
63
+ * The token appears in no message this module can produce. The three failure
64
+ * shapes of the flow — the human denied it, the code expired, the poll was
65
+ * aborted — are named plainly, with GitHub's `error_description` only, never a
66
+ * response body or a request.
67
+ */
68
+ export interface GithubDeviceSignInOptions {
69
+ /**
70
+ * The OAuth App's client id. **Public by design** — the device flow has no
71
+ * client secret, so this belongs in your front-end code. Create the app once
72
+ * under the organisation, tick "Enable Device Flow", and copy the id.
73
+ */
74
+ readonly clientId: string;
75
+ /**
76
+ * Classic OAuth scopes. Default `['public_repo']` — enough to file an issue
77
+ * and commit to a public evidence repo, and no more. `['repo']` is what a
78
+ * private evidence repo needs, and it is a much larger grant; ask for it only
79
+ * when the flow really commits there.
80
+ */
81
+ readonly scopes?: readonly string[];
82
+ /** Cancel the polling (a closed dialog, an unmounted component). */
83
+ readonly signal?: AbortSignal;
84
+ /** GitHub's web origin. GHES: `https://github.your-company.com`. */
85
+ readonly authBase?: string;
86
+ /** GitHub's API root. GHES: `https://github.your-company.com/api/v3`. */
87
+ readonly apiBase?: string;
88
+ /** Test seam — inject `fetch`. Bypasses the network entirely. */
89
+ readonly _fetch?: typeof fetch;
90
+ /** Test seam — inject the wait between polls. */
91
+ readonly _sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
92
+ }
93
+ /** Who signed in, and with what. */
94
+ export interface GithubDeviceIdentity {
95
+ /** The access token. Memory-only; see the module docs. */
96
+ readonly token: string;
97
+ /** Usually `bearer`. */
98
+ readonly tokenType: string;
99
+ /** The scopes GitHub actually granted — not necessarily the ones asked for. */
100
+ readonly scopes: readonly string[];
101
+ /** The GitHub login the issue will be filed as. `undefined` if `/user` refused. */
102
+ readonly login?: string;
103
+ }
104
+ /** The code to show a human, and the promise that resolves when they approve. */
105
+ export interface GithubDeviceSignIn {
106
+ /** The code the human types — show it verbatim, it is case-sensitive. */
107
+ readonly userCode: string;
108
+ /** The page they type it into. */
109
+ readonly verificationUri: string;
110
+ /** Seconds until `userCode` stops working. */
111
+ readonly expiresIn: number;
112
+ /** Seconds GitHub asked us to wait between polls. */
113
+ readonly interval: number;
114
+ /**
115
+ * Resolves when the human approves, rejects when they deny it, when the code
116
+ * expires, or when `signal` aborts. Polling starts immediately — awaiting
117
+ * this later does not miss an approval.
118
+ */
119
+ readonly completed: Promise<GithubDeviceIdentity>;
120
+ }
121
+ /**
122
+ * Start a device-flow sign-in.
123
+ *
124
+ * Resolves as soon as GitHub hands back a code — that is the point, because
125
+ * the human cannot approve a code they have not been shown. The waiting happens
126
+ * on the returned `completed` promise.
127
+ *
128
+ * @throws TypeError when `clientId` is missing (naming where it comes from).
129
+ * @throws Error naming the status when GitHub refuses to issue a code.
130
+ */
131
+ export declare function githubDeviceSignIn(options: GithubDeviceSignInOptions): Promise<GithubDeviceSignIn>;