From 5884ee6816e7a89cf93c3b67b235c605352d6ae7 Mon Sep 17 00:00:00 2001 From: David Brailovsky Date: Thu, 22 Oct 2026 10:58:13 +0200 Subject: [PATCH] feat(coding-agent): let /ir import a session file from a .jsonl URL --- .pi/extensions/import-repro.ts | 38 +++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/.pi/extensions/import-repro.ts b/.pi/extensions/import-repro.ts index cb11ca049..94dc4197f 100644 --- a/.pi/extensions/import-repro.ts +++ b/.pi/extensions/import-repro.ts @@ -1,16 +1,18 @@ /** * Import a pi session shared as a gist by the issue-analysis CI workflow - * (.github/workflows/issue-analysis.yml) and switch to it. + * (.github/workflows/issue-analysis.yml), or a .jsonl session file served at a URL, + * and switch to it. * - * The CI job runs in a high-entropy checkout directory; this command rewrites - * the recorded cwd to the local checkout, installs the session file into the - * current session directory, and switches to it. + * The session ran in another checkout (the CI job's high-entropy directory, or a + * research VM); this command rewrites the recorded cwd to the local checkout, + * installs the session file into the current session directory, and switches to it. * * Usage: * /ir b4d100022aefb12f25dd2d8485e0a82a * /ir https://gist.github.com/mitsuhiko/b4d100022aefb12f25dd2d8485e0a82a * /ir https://pi.dev/session/#b4d100022aefb12f25dd2d8485e0a82a * /ir https://github.com/earendil-works/pi/issues/123 + * /ir https://example.com/path/session.jsonl * * pi "/ir " */ @@ -58,11 +60,11 @@ interface IssueComment { function parseRef( ref: string, cwd: string, -): { type: "gist"; id: string } | { type: "file"; path: string } | { type: "issue"; owner: string; repo: string; issue: string } { - if (ref.endsWith(".html") || ref.endsWith(".jsonl")) { - return { type: "file", path: isAbsolute(ref) ? ref : resolve(cwd, ref) }; - } - +): + | { type: "gist"; id: string } + | { type: "file"; path: string } + | { type: "issue"; owner: string; repo: string; issue: string } + | { type: "url"; url: URL } { const shareMatch = ref.match(SHARE_URL_RE); if (shareMatch) return { type: "gist", id: shareMatch[1] }; @@ -72,9 +74,17 @@ function parseRef( const issueMatch = ref.match(ISSUE_URL_RE); if (issueMatch) return { type: "issue", owner: issueMatch[1], repo: issueMatch[2], issue: issueMatch[3] }; + if (/^https?:\/\/.*\.jsonl$/.test(ref)) return { type: "url", url: new URL(ref) }; + + if (ref.endsWith(".html") || ref.endsWith(".jsonl")) { + return { type: "file", path: isAbsolute(ref) ? ref : resolve(cwd, ref) }; + } + if (GIST_ID_RE.test(ref)) return { type: "gist", id: ref }; - throw new Error(`expected a gist ID, gist URL, pi.dev share URL, issue URL, .html file, or .jsonl file: ${ref}`); + throw new Error( + `expected a gist ID, gist URL, pi.dev share URL, issue URL, .jsonl URL, .html file, or .jsonl file: ${ref}`, + ); } function parseSessionJsonl(raw: string): { header: SessionHeader; jsonl: string } { @@ -282,11 +292,12 @@ async function fetchGistSession(gistId: string): Promise<{ header: SessionHeader export default function (pi: ExtensionAPI) { pi.registerCommand("ir", { - description: "Import a CI issue-analysis session from a gist ID, share URL, or issue URL and switch to it", + description: + "Import a CI issue-analysis session from a gist ID, share URL, issue URL, or .jsonl URL and switch to it", handler: async (args: string, ctx: ExtensionCommandContext) => { const ref = args.trim(); if (!ref) { - ctx.ui.notify("Usage: /ir ", "error"); + ctx.ui.notify("Usage: /ir ", "error"); return; } @@ -306,6 +317,9 @@ export default function (pi: ExtensionAPI) { const gistId = await findIssueGistId(parsedRef.owner, parsedRef.repo, parsedRef.issue); decoded = await fetchGistSession(gistId); sourceName = `${gistId}.jsonl`; + } else if (parsedRef.type === "url") { + decoded = parseSessionJsonl(await fetchText(parsedRef.url.href)); + sourceName = basename(parsedRef.url.pathname); } else { if (!existsSync(parsedRef.path)) throw new Error(`session file not found: ${parsedRef.path}`); const raw = readFileSync(parsedRef.path, "utf8");