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.
- package/dist/adapters/identity/vault.js +336 -0
- package/dist/adapters/identity/vault.js.map +1 -0
- package/dist/adapters/observability/audit.js +2 -28
- package/dist/adapters/observability/audit.js.map +1 -1
- package/dist/adapters/observability/file.js +307 -0
- package/dist/adapters/observability/file.js.map +1 -0
- package/dist/adapters/observability/githubBugReporter.js +461 -0
- package/dist/adapters/observability/githubBugReporter.js.map +1 -0
- package/dist/adapters/observability/githubDeviceSignIn.js +263 -0
- package/dist/adapters/observability/githubDeviceSignIn.js.map +1 -0
- package/dist/esm/adapters/identity/vault.d.ts +146 -0
- package/dist/esm/adapters/identity/vault.js +332 -0
- package/dist/esm/adapters/identity/vault.js.map +1 -0
- package/dist/esm/adapters/observability/audit.js +1 -27
- package/dist/esm/adapters/observability/audit.js.map +1 -1
- package/dist/esm/adapters/observability/file.d.ts +145 -0
- package/dist/esm/adapters/observability/file.js +303 -0
- package/dist/esm/adapters/observability/file.js.map +1 -0
- package/dist/esm/adapters/observability/githubBugReporter.d.ts +154 -0
- package/dist/esm/adapters/observability/githubBugReporter.js +457 -0
- package/dist/esm/adapters/observability/githubBugReporter.js.map +1 -0
- package/dist/esm/adapters/observability/githubDeviceSignIn.d.ts +131 -0
- package/dist/esm/adapters/observability/githubDeviceSignIn.js +259 -0
- package/dist/esm/adapters/observability/githubDeviceSignIn.js.map +1 -0
- package/dist/esm/identity.d.ts +1 -0
- package/dist/esm/identity.js +4 -0
- package/dist/esm/identity.js.map +1 -1
- package/dist/esm/lib/bug-report/build.d.ts +103 -0
- package/dist/esm/lib/bug-report/build.js +648 -0
- package/dist/esm/lib/bug-report/build.js.map +1 -0
- package/dist/esm/lib/bug-report/index.d.ts +14 -0
- package/dist/esm/lib/bug-report/index.js +13 -0
- package/dist/esm/lib/bug-report/index.js.map +1 -0
- package/dist/esm/lib/bug-report/transcript.d.ts +61 -0
- package/dist/esm/lib/bug-report/transcript.js +124 -0
- package/dist/esm/lib/bug-report/transcript.js.map +1 -0
- package/dist/esm/lib/bug-report/types.d.ts +206 -0
- package/dist/esm/lib/bug-report/types.js +12 -0
- package/dist/esm/lib/bug-report/types.js.map +1 -0
- package/dist/esm/lib/bug-report/zip.d.ts +71 -0
- package/dist/esm/lib/bug-report/zip.js +202 -0
- package/dist/esm/lib/bug-report/zip.js.map +1 -0
- package/dist/esm/lib/libraryVersion.d.ts +23 -0
- package/dist/esm/lib/libraryVersion.js +46 -0
- package/dist/esm/lib/libraryVersion.js.map +1 -0
- package/dist/esm/lib/trace-toolpack/openRecording.d.ts +17 -0
- package/dist/esm/lib/trace-toolpack/openRecording.js +8 -2
- package/dist/esm/lib/trace-toolpack/openRecording.js.map +1 -1
- package/dist/esm/observability-providers.d.ts +6 -1
- package/dist/esm/observability-providers.js +16 -1
- package/dist/esm/observability-providers.js.map +1 -1
- package/dist/esm/observe.d.ts +1 -0
- package/dist/esm/observe.js +6 -0
- package/dist/esm/observe.js.map +1 -1
- package/dist/identity.js +6 -1
- package/dist/identity.js.map +1 -1
- package/dist/lib/bug-report/build.js +656 -0
- package/dist/lib/bug-report/build.js.map +1 -0
- package/dist/lib/bug-report/index.js +18 -0
- package/dist/lib/bug-report/index.js.map +1 -0
- package/dist/lib/bug-report/transcript.js +128 -0
- package/dist/lib/bug-report/transcript.js.map +1 -0
- package/dist/lib/bug-report/types.js +13 -0
- package/dist/lib/bug-report/types.js.map +1 -0
- package/dist/lib/bug-report/zip.js +207 -0
- package/dist/lib/bug-report/zip.js.map +1 -0
- package/dist/lib/libraryVersion.js +51 -0
- package/dist/lib/libraryVersion.js.map +1 -0
- package/dist/lib/trace-toolpack/openRecording.js +9 -2
- package/dist/lib/trace-toolpack/openRecording.js.map +1 -1
- package/dist/observability-providers.js +20 -2
- package/dist/observability-providers.js.map +1 -1
- package/dist/observe.js +14 -6
- package/dist/observe.js.map +1 -1
- package/dist/types/adapters/identity/vault.d.ts +147 -0
- package/dist/types/adapters/identity/vault.d.ts.map +1 -0
- package/dist/types/adapters/observability/audit.d.ts.map +1 -1
- package/dist/types/adapters/observability/file.d.ts +146 -0
- package/dist/types/adapters/observability/file.d.ts.map +1 -0
- package/dist/types/adapters/observability/githubBugReporter.d.ts +155 -0
- package/dist/types/adapters/observability/githubBugReporter.d.ts.map +1 -0
- package/dist/types/adapters/observability/githubDeviceSignIn.d.ts +132 -0
- package/dist/types/adapters/observability/githubDeviceSignIn.d.ts.map +1 -0
- package/dist/types/identity.d.ts +1 -0
- package/dist/types/identity.d.ts.map +1 -1
- package/dist/types/lib/bug-report/build.d.ts +104 -0
- package/dist/types/lib/bug-report/build.d.ts.map +1 -0
- package/dist/types/lib/bug-report/index.d.ts +15 -0
- package/dist/types/lib/bug-report/index.d.ts.map +1 -0
- package/dist/types/lib/bug-report/transcript.d.ts +62 -0
- package/dist/types/lib/bug-report/transcript.d.ts.map +1 -0
- package/dist/types/lib/bug-report/types.d.ts +207 -0
- package/dist/types/lib/bug-report/types.d.ts.map +1 -0
- package/dist/types/lib/bug-report/zip.d.ts +72 -0
- package/dist/types/lib/bug-report/zip.d.ts.map +1 -0
- package/dist/types/lib/libraryVersion.d.ts +24 -0
- package/dist/types/lib/libraryVersion.d.ts.map +1 -0
- package/dist/types/lib/trace-toolpack/openRecording.d.ts +17 -0
- package/dist/types/lib/trace-toolpack/openRecording.d.ts.map +1 -1
- package/dist/types/observability-providers.d.ts +6 -1
- package/dist/types/observability-providers.d.ts.map +1 -1
- package/dist/types/observe.d.ts +1 -0
- package/dist/types/observe.d.ts.map +1 -1
- 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>;
|