@agentproto/runtime 2.5.0 → 2.6.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/catalog-models.d.ts +1 -1
- package/dist/command-allowlist.d.ts +70 -0
- package/dist/command-allowlist.mjs +104 -0
- package/dist/command-allowlist.mjs.map +1 -0
- package/dist/config.d.ts +6 -1
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.ts +380 -7
- package/dist/index.mjs +2757 -700
- package/dist/index.mjs.map +1 -1
- package/dist/pr-provenance.d.ts +8 -0
- package/dist/pr-provenance.mjs +8 -2
- package/dist/pr-provenance.mjs.map +1 -1
- package/dist/resume-strategies.d.ts +8 -0
- package/dist/resume-strategies.mjs +17 -3
- package/dist/resume-strategies.mjs.map +1 -1
- package/dist/{spawn-defaults-7uHRnYH1.d.ts → spawn-defaults-CYJoeHeO.d.ts} +0 -13
- package/package.json +17 -11
package/dist/pr-provenance.d.ts
CHANGED
|
@@ -49,6 +49,13 @@ interface FooterProvenance {
|
|
|
49
49
|
source?: string;
|
|
50
50
|
host?: string;
|
|
51
51
|
cwd?: string;
|
|
52
|
+
/** The session's registered workspace slug (`SessionDescriptor.workspaceSlug`,
|
|
53
|
+
* e.g. "ts", "default"). Rendered alongside `cwd`'s leaf directory name so
|
|
54
|
+
* the footer identifies WHICH workspace ran the command, not just an
|
|
55
|
+
* arbitrary trailing path segment — a bare `basename(cwd)` reads as
|
|
56
|
+
* meaningless (or actively misleading) when the session's cwd is a
|
|
57
|
+
* workspace root rather than a per-branch worktree. */
|
|
58
|
+
workspaceSlug?: string;
|
|
52
59
|
}
|
|
53
60
|
interface BuildFooterInput {
|
|
54
61
|
prov?: FooterProvenance;
|
|
@@ -69,6 +76,7 @@ interface FooterSession {
|
|
|
69
76
|
startedAt?: string;
|
|
70
77
|
label?: string;
|
|
71
78
|
cwd?: string;
|
|
79
|
+
workspaceSlug?: string;
|
|
72
80
|
adapterSlug?: string;
|
|
73
81
|
harness?: string;
|
|
74
82
|
model?: string;
|
package/dist/pr-provenance.mjs
CHANGED
|
@@ -10,6 +10,11 @@ var fmtTokens = (n) => {
|
|
|
10
10
|
if (typeof n !== "number" || !Number.isFinite(n)) return null;
|
|
11
11
|
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
|
|
12
12
|
};
|
|
13
|
+
function cwdLabel(cwd, workspaceSlug) {
|
|
14
|
+
const leaf = basename(cwd);
|
|
15
|
+
if (!workspaceSlug || workspaceSlug === leaf) return workspaceSlug ?? leaf;
|
|
16
|
+
return `${workspaceSlug}/${leaf}`;
|
|
17
|
+
}
|
|
13
18
|
var buildFooter = ({
|
|
14
19
|
prov,
|
|
15
20
|
authMode,
|
|
@@ -39,7 +44,7 @@ var buildFooter = ({
|
|
|
39
44
|
if (runId && !showLocalHostCwd) parts.push(`run [${runId}](${runUrl})`);
|
|
40
45
|
if (showLocalHostCwd) {
|
|
41
46
|
if (prov?.host) parts.push(`host \`${prov.host}\``);
|
|
42
|
-
if (prov?.cwd) parts.push(`cwd \`${
|
|
47
|
+
if (prov?.cwd) parts.push(`cwd \`${cwdLabel(prov.cwd, prov.workspaceSlug)}\``);
|
|
43
48
|
}
|
|
44
49
|
if (sha) parts.push(`sha \`${sha.slice(0, 7)}\``);
|
|
45
50
|
return `
|
|
@@ -60,7 +65,8 @@ function sessionFooterProvenance(session, options = {}) {
|
|
|
60
65
|
tokensOut: session.tokensOut,
|
|
61
66
|
source: options.source ?? "daemon",
|
|
62
67
|
host: options.host,
|
|
63
|
-
cwd: session.cwd
|
|
68
|
+
cwd: session.cwd,
|
|
69
|
+
workspaceSlug: session.workspaceSlug
|
|
64
70
|
};
|
|
65
71
|
return { prov, authMode: session.auth?.mode };
|
|
66
72
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pr-provenance.ts"],"names":[],"mappings":"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","file":"pr-provenance.mjs","sourcesContent":["/**\n * PR provenance footer — daemon/MCP lane.\n *\n * This is the SAME footer the runner-side agentflow scripts stamp on the\n * CI-PR, review, and local-`gh_open_pr` lanes; `scripts/lib/provenance-\n * footer.mjs` re-exports {@link buildFooter}/{@link MARKER}/{@link fmtTokens}\n * from HERE so there is exactly one format, one marker, one renderer. The\n * only reason this canonical copy lives in `@agentproto/runtime` rather than\n * in `scripts/` is direction of dependency: the daemon is a package and\n * cannot import a loose script, but a script can import a package's built\n * `dist/` (it already does for `computeProvenance`).\n *\n * What the DAEMON lane adds over the runner lanes: the footer is built from\n * a live {@link SessionDescriptor} (the executor agent-cli session that ran\n * `gh pr create`) plus its supervisor/parent session, rather than from a\n * `computeProvenance` join over the worktree. So this file also owns the\n * mapping from a session descriptor to the footer's provenance shape\n * ({@link sessionFooterProvenance}) and the small pure helpers the\n * command_execute stamp path needs ({@link parseGhPrCreate},\n * {@link pickExecutorSession}, {@link appendFooterOnce}).\n *\n * Pure by construction — only `node:path`, no daemon imports, no I/O — so it\n * bundles to a standalone `dist/pr-provenance.mjs` the scripts can import\n * without pulling the rest of the runtime in. Keep it that way. The\n * side-effecting orchestration (spawning `gh`, editing the PR body, recording\n * the opened PR) lives in `pr-provenance-stamp.ts`.\n */\n\nimport { basename } from \"node:path\"\n\n/** Deterministic marker the runner owns — a reliable native-vs-legacy\n * discriminator across every lane (CI-PR, review, local, daemon). */\nexport const MARKER = \"@agentproto-bot\"\n\nexport const fmtTokens = (n: unknown): string | null => {\n if (typeof n !== \"number\" || !Number.isFinite(n)) return null\n return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n)\n}\n\n/** The footer's provenance shape — field names are the renderer's, not the\n * session registry's (see {@link sessionFooterProvenance} for the mapping). */\nexport interface FooterProvenance {\n sessionId?: string\n label?: string\n /** Adapter / harness slug that ran (\"claude-code\", \"codex\", …). */\n adapter?: string\n model?: string\n /** Bound auth-profile IDENTITY — a label or ref, NEVER the credential. */\n authProfile?: string\n sandboxId?: string\n /** Supervisor / parent session that spawned the executor. */\n parentSessionId?: string\n tokensIn?: number\n tokensOut?: number\n costUsd?: number\n /** \"adapter\" | \"local\" | \"daemon\" — drives host/cwd vs run-link rendering. */\n source?: string\n host?: string\n cwd?: string\n}\n\nexport interface BuildFooterInput {\n prov?: FooterProvenance\n authMode?: string\n runId?: string\n runUrl?: string\n sha?: string\n kind?: string\n}\n\nexport const buildFooter = ({\n prov,\n authMode,\n runId,\n runUrl,\n sha,\n kind = \"review\",\n}: BuildFooterInput): string => {\n const parts = [`🤖 **${MARKER}** — ${kind}`]\n if (prov?.sessionId) {\n parts.push(`session \\`${prov.sessionId}\\`${prov.label ? ` (\\`${prov.label}\\`)` : \"\"}`)\n }\n // Engine label: \"adapter / authMode\" when an adapter ran; \"legacy fallback\n // (authMode)\" when no agent session exists at all (the API-key fallback path);\n // bare authMode only if a session ran without a resolved adapter slug.\n if (prov?.adapter) parts.push([prov.adapter, authMode].filter(Boolean).join(\" / \"))\n else if (!prov?.sessionId) parts.push(`legacy fallback${authMode ? ` (${authMode})` : \"\"}`)\n else if (authMode) parts.push(authMode)\n // Bound auth profile identity (the \"wallet\") — daemon lane; NEVER the secret.\n if (prov?.authProfile) parts.push(`auth-profile \\`${prov.authProfile}\\``)\n if (prov?.model) parts.push(`model \\`${prov.model}\\``)\n if (prov?.sandboxId) parts.push(`e2b \\`${prov.sandboxId}\\``)\n if (prov?.parentSessionId) parts.push(`supervisor \\`${prov.parentSessionId}\\``)\n const tin = fmtTokens(prov?.tokensIn)\n const tout = fmtTokens(prov?.tokensOut)\n if (tin || tout) parts.push(`${tin ?? \"?\"} in / ${tout ?? \"?\"} out`)\n if (typeof prov?.costUsd === \"number\") {\n parts.push(`$${prov.costUsd.toFixed(4)}${prov.source && prov.source !== \"adapter\" ? ` (${prov.source})` : \"\"}`)\n }\n const showLocalHostCwd = prov?.source === \"local\" || prov?.source === \"daemon\" || !runId\n if (runId && !showLocalHostCwd) parts.push(`run [${runId}](${runUrl})`)\n if (showLocalHostCwd) {\n if (prov?.host) parts.push(`host \\`${prov.host}\\``)\n if (prov?.cwd) parts.push(`cwd \\`${basename(prov.cwd)}\\``)\n }\n if (sha) parts.push(`sha \\`${sha.slice(0, 7)}\\``)\n return `\\n\\n---\\n<sub>${parts.join(\" · \")}</sub>`\n}\n\n/** Structural subset of `SessionDescriptor` the footer needs. The runtime's\n * full descriptor satisfies this; keeping it structural is what lets this\n * module stay free of a `./sessions.js` import (and thus standalone). */\nexport interface FooterSession {\n id: string\n kind?: string\n status?: string\n startedAt?: string\n label?: string\n cwd?: string\n adapterSlug?: string\n harness?: string\n model?: string\n parentSessionId?: string\n costUsd?: number\n tokensIn?: number\n tokensOut?: number\n auth?: { mode?: string }\n accessProfile?: { profileRef: string; label?: string }\n}\n\nexport interface SessionFooterOptions {\n /** The supervisor/parent session descriptor, when resolvable. */\n supervisor?: { id: string } | null\n host?: string\n /** Provenance source tag; defaults to \"daemon\". */\n source?: string\n}\n\n/**\n * Map a live executor session (+ its supervisor) onto the footer's provenance\n * shape and the `authMode` the renderer takes separately. The adapter is the\n * canonical harness slug, falling back to `adapterSlug`. The bound auth\n * profile is the profile's human label (or its ref), never the credential.\n */\nexport function sessionFooterProvenance(\n session: FooterSession,\n options: SessionFooterOptions = {},\n): { prov: FooterProvenance; authMode: string | undefined } {\n const prov: FooterProvenance = {\n sessionId: session.id,\n label: session.label,\n adapter: session.harness ?? session.adapterSlug,\n model: session.model,\n authProfile: session.accessProfile?.label ?? session.accessProfile?.profileRef,\n parentSessionId: options.supervisor?.id,\n costUsd: session.costUsd,\n tokensIn: session.tokensIn,\n tokensOut: session.tokensOut,\n source: options.source ?? \"daemon\",\n host: options.host,\n cwd: session.cwd,\n }\n return { prov, authMode: session.auth?.mode }\n}\n\n/** Build the daemon-lane PR footer for one executor session directly. */\nexport function buildSessionPrFooter(\n session: FooterSession,\n options: SessionFooterOptions & { sha?: string } = {},\n): string {\n const { prov, authMode } = sessionFooterProvenance(session, options)\n return buildFooter({ prov, authMode, sha: options.sha, kind: \"PR\" })\n}\n\n/**\n * Append the footer to a PR body exactly once. Idempotent by the marker: a\n * body that already carries a `@agentproto-bot` footer is returned unchanged,\n * so a retried stamp (network reply lost after the edit landed) never stacks\n * a second footer.\n */\nexport function appendFooterOnce(body: string, footer: string): string {\n if (body.includes(MARKER)) return body\n return `${body}${footer}`\n}\n\n/**\n * Recognise a successful `gh pr create` from its argv + stdout and pull the\n * created PR's URL + number out. Returns null for anything that isn't a\n * PR-creating `gh` invocation, or a create whose stdout carried no PR URL.\n *\n * `gh pr create` prints the PR URL on its own line (often the last line,\n * after any advisory notices) — we take the LAST `…/pull/<n>` match so a\n * \"Warning: …/pull/…\" style notice can't shadow the real one.\n */\nexport function parseGhPrCreate(\n command: string,\n args: readonly string[],\n stdout: string,\n): { url: string; number: number } | null {\n if (basename(command) !== \"gh\") return null\n // Skip global flags; the first two non-flag tokens must be `pr` then `create`.\n const positionals = args.filter(a => !a.startsWith(\"-\"))\n if (positionals[0] !== \"pr\" || positionals[1] !== \"create\") return null\n const re = /https?:\\/\\/\\S+?\\/pull\\/(\\d+)/g\n let match: RegExpExecArray | null\n let last: { url: string; number: number } | null = null\n while ((match = re.exec(stdout)) !== null) {\n last = { url: match[0], number: Number(match[1]) }\n }\n return last\n}\n\n/** `session.cwd == cwd || one contains the other` — the executor's cwd is\n * usually the worktree root while the command may run in a subdir (or vice\n * versa), so containment is checked in both directions. */\nfunction cwdRelated(sessionCwd: string, cwd: string): boolean {\n if (sessionCwd === cwd) return true\n const sep = \"/\"\n return cwd.startsWith(sessionCwd + sep) || sessionCwd.startsWith(cwd + sep)\n}\n\n/**\n * Best-effort attribution of a `command_execute` run to the executor agent\n * session that most likely issued it: the newest agent-cli session whose cwd\n * is related to the command's cwd, preferring a still-alive one. Returns\n * undefined when no agent-cli session matches (e.g. a bare shell opened the\n * PR) — the caller then skips stamping rather than emit a misleading \"legacy\n * fallback\" footer.\n */\nexport function pickExecutorSession<T extends FooterSession>(\n sessions: readonly T[],\n cwd: string,\n): T | undefined {\n const candidates = sessions.filter(\n s => s.kind === \"agent-cli\" && typeof s.cwd === \"string\" && cwdRelated(s.cwd, cwd),\n )\n if (candidates.length === 0) return undefined\n const alive = (s: T) => s.status === \"running\" || s.status === \"starting\"\n const byRecency = (a: T, b: T) => (b.startedAt ?? \"\").localeCompare(a.startedAt ?? \"\")\n const live = candidates.filter(alive).sort(byRecency)\n if (live.length > 0) return live[0]\n return [...candidates].sort(byRecency)[0]\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/pr-provenance.ts"],"names":[],"mappings":"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","file":"pr-provenance.mjs","sourcesContent":["/**\n * PR provenance footer — daemon/MCP lane.\n *\n * This is the SAME footer the runner-side agentflow scripts stamp on the\n * CI-PR, review, and local-`gh_open_pr` lanes; `scripts/lib/provenance-\n * footer.mjs` re-exports {@link buildFooter}/{@link MARKER}/{@link fmtTokens}\n * from HERE so there is exactly one format, one marker, one renderer. The\n * only reason this canonical copy lives in `@agentproto/runtime` rather than\n * in `scripts/` is direction of dependency: the daemon is a package and\n * cannot import a loose script, but a script can import a package's built\n * `dist/` (it already does for `computeProvenance`).\n *\n * What the DAEMON lane adds over the runner lanes: the footer is built from\n * a live {@link SessionDescriptor} (the executor agent-cli session that ran\n * `gh pr create`) plus its supervisor/parent session, rather than from a\n * `computeProvenance` join over the worktree. So this file also owns the\n * mapping from a session descriptor to the footer's provenance shape\n * ({@link sessionFooterProvenance}) and the small pure helpers the\n * command_execute stamp path needs ({@link parseGhPrCreate},\n * {@link pickExecutorSession}, {@link appendFooterOnce}).\n *\n * Pure by construction — only `node:path`, no daemon imports, no I/O — so it\n * bundles to a standalone `dist/pr-provenance.mjs` the scripts can import\n * without pulling the rest of the runtime in. Keep it that way. The\n * side-effecting orchestration (spawning `gh`, editing the PR body, recording\n * the opened PR) lives in `pr-provenance-stamp.ts`.\n */\n\nimport { basename } from \"node:path\"\n\n/** Deterministic marker the runner owns — a reliable native-vs-legacy\n * discriminator across every lane (CI-PR, review, local, daemon). */\nexport const MARKER = \"@agentproto-bot\"\n\nexport const fmtTokens = (n: unknown): string | null => {\n if (typeof n !== \"number\" || !Number.isFinite(n)) return null\n return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n)\n}\n\n/** The footer's provenance shape — field names are the renderer's, not the\n * session registry's (see {@link sessionFooterProvenance} for the mapping). */\nexport interface FooterProvenance {\n sessionId?: string\n label?: string\n /** Adapter / harness slug that ran (\"claude-code\", \"codex\", …). */\n adapter?: string\n model?: string\n /** Bound auth-profile IDENTITY — a label or ref, NEVER the credential. */\n authProfile?: string\n sandboxId?: string\n /** Supervisor / parent session that spawned the executor. */\n parentSessionId?: string\n tokensIn?: number\n tokensOut?: number\n costUsd?: number\n /** \"adapter\" | \"local\" | \"daemon\" — drives host/cwd vs run-link rendering. */\n source?: string\n host?: string\n cwd?: string\n /** The session's registered workspace slug (`SessionDescriptor.workspaceSlug`,\n * e.g. \"ts\", \"default\"). Rendered alongside `cwd`'s leaf directory name so\n * the footer identifies WHICH workspace ran the command, not just an\n * arbitrary trailing path segment — a bare `basename(cwd)` reads as\n * meaningless (or actively misleading) when the session's cwd is a\n * workspace root rather than a per-branch worktree. */\n workspaceSlug?: string\n}\n\n/**\n * Render the footer's `cwd` value. A bare `basename(cwd)` collapses to a\n * meaningless (or actively misleading) fragment whenever the session's cwd IS\n * the workspace root rather than a per-branch worktree dir — e.g. a checkout\n * literally named `ts` renders as the opaque `cwd \\`ts\\`` with no indication\n * that's a workspace name, not a worktree/branch leaf. Prefixing the\n * registered workspace slug disambiguates it, and is dropped when it's\n * identical to the leaf (the common per-worktree case) so the common case\n * stays exactly as compact as before.\n */\nfunction cwdLabel(cwd: string, workspaceSlug?: string): string {\n const leaf = basename(cwd)\n if (!workspaceSlug || workspaceSlug === leaf) return workspaceSlug ?? leaf\n return `${workspaceSlug}/${leaf}`\n}\n\nexport interface BuildFooterInput {\n prov?: FooterProvenance\n authMode?: string\n runId?: string\n runUrl?: string\n sha?: string\n kind?: string\n}\n\nexport const buildFooter = ({\n prov,\n authMode,\n runId,\n runUrl,\n sha,\n kind = \"review\",\n}: BuildFooterInput): string => {\n const parts = [`🤖 **${MARKER}** — ${kind}`]\n if (prov?.sessionId) {\n parts.push(`session \\`${prov.sessionId}\\`${prov.label ? ` (\\`${prov.label}\\`)` : \"\"}`)\n }\n // Engine label: \"adapter / authMode\" when an adapter ran; \"legacy fallback\n // (authMode)\" when no agent session exists at all (the API-key fallback path);\n // bare authMode only if a session ran without a resolved adapter slug.\n if (prov?.adapter) parts.push([prov.adapter, authMode].filter(Boolean).join(\" / \"))\n else if (!prov?.sessionId) parts.push(`legacy fallback${authMode ? ` (${authMode})` : \"\"}`)\n else if (authMode) parts.push(authMode)\n // Bound auth profile identity (the \"wallet\") — daemon lane; NEVER the secret.\n if (prov?.authProfile) parts.push(`auth-profile \\`${prov.authProfile}\\``)\n if (prov?.model) parts.push(`model \\`${prov.model}\\``)\n if (prov?.sandboxId) parts.push(`e2b \\`${prov.sandboxId}\\``)\n if (prov?.parentSessionId) parts.push(`supervisor \\`${prov.parentSessionId}\\``)\n const tin = fmtTokens(prov?.tokensIn)\n const tout = fmtTokens(prov?.tokensOut)\n if (tin || tout) parts.push(`${tin ?? \"?\"} in / ${tout ?? \"?\"} out`)\n if (typeof prov?.costUsd === \"number\") {\n parts.push(`$${prov.costUsd.toFixed(4)}${prov.source && prov.source !== \"adapter\" ? ` (${prov.source})` : \"\"}`)\n }\n const showLocalHostCwd = prov?.source === \"local\" || prov?.source === \"daemon\" || !runId\n if (runId && !showLocalHostCwd) parts.push(`run [${runId}](${runUrl})`)\n if (showLocalHostCwd) {\n if (prov?.host) parts.push(`host \\`${prov.host}\\``)\n if (prov?.cwd) parts.push(`cwd \\`${cwdLabel(prov.cwd, prov.workspaceSlug)}\\``)\n }\n if (sha) parts.push(`sha \\`${sha.slice(0, 7)}\\``)\n return `\\n\\n---\\n<sub>${parts.join(\" · \")}</sub>`\n}\n\n/** Structural subset of `SessionDescriptor` the footer needs. The runtime's\n * full descriptor satisfies this; keeping it structural is what lets this\n * module stay free of a `./sessions.js` import (and thus standalone). */\nexport interface FooterSession {\n id: string\n kind?: string\n status?: string\n startedAt?: string\n label?: string\n cwd?: string\n workspaceSlug?: string\n adapterSlug?: string\n harness?: string\n model?: string\n parentSessionId?: string\n costUsd?: number\n tokensIn?: number\n tokensOut?: number\n auth?: { mode?: string }\n accessProfile?: { profileRef: string; label?: string }\n}\n\nexport interface SessionFooterOptions {\n /** The supervisor/parent session descriptor, when resolvable. */\n supervisor?: { id: string } | null\n host?: string\n /** Provenance source tag; defaults to \"daemon\". */\n source?: string\n}\n\n/**\n * Map a live executor session (+ its supervisor) onto the footer's provenance\n * shape and the `authMode` the renderer takes separately. The adapter is the\n * canonical harness slug, falling back to `adapterSlug`. The bound auth\n * profile is the profile's human label (or its ref), never the credential.\n */\nexport function sessionFooterProvenance(\n session: FooterSession,\n options: SessionFooterOptions = {},\n): { prov: FooterProvenance; authMode: string | undefined } {\n const prov: FooterProvenance = {\n sessionId: session.id,\n label: session.label,\n adapter: session.harness ?? session.adapterSlug,\n model: session.model,\n authProfile: session.accessProfile?.label ?? session.accessProfile?.profileRef,\n parentSessionId: options.supervisor?.id,\n costUsd: session.costUsd,\n tokensIn: session.tokensIn,\n tokensOut: session.tokensOut,\n source: options.source ?? \"daemon\",\n host: options.host,\n cwd: session.cwd,\n workspaceSlug: session.workspaceSlug,\n }\n return { prov, authMode: session.auth?.mode }\n}\n\n/** Build the daemon-lane PR footer for one executor session directly. */\nexport function buildSessionPrFooter(\n session: FooterSession,\n options: SessionFooterOptions & { sha?: string } = {},\n): string {\n const { prov, authMode } = sessionFooterProvenance(session, options)\n return buildFooter({ prov, authMode, sha: options.sha, kind: \"PR\" })\n}\n\n/**\n * Append the footer to a PR body exactly once. Idempotent by the marker: a\n * body that already carries a `@agentproto-bot` footer is returned unchanged,\n * so a retried stamp (network reply lost after the edit landed) never stacks\n * a second footer.\n */\nexport function appendFooterOnce(body: string, footer: string): string {\n if (body.includes(MARKER)) return body\n return `${body}${footer}`\n}\n\n/**\n * Recognise a successful `gh pr create` from its argv + stdout and pull the\n * created PR's URL + number out. Returns null for anything that isn't a\n * PR-creating `gh` invocation, or a create whose stdout carried no PR URL.\n *\n * `gh pr create` prints the PR URL on its own line (often the last line,\n * after any advisory notices) — we take the LAST `…/pull/<n>` match so a\n * \"Warning: …/pull/…\" style notice can't shadow the real one.\n */\nexport function parseGhPrCreate(\n command: string,\n args: readonly string[],\n stdout: string,\n): { url: string; number: number } | null {\n if (basename(command) !== \"gh\") return null\n // Skip global flags; the first two non-flag tokens must be `pr` then `create`.\n const positionals = args.filter(a => !a.startsWith(\"-\"))\n if (positionals[0] !== \"pr\" || positionals[1] !== \"create\") return null\n const re = /https?:\\/\\/\\S+?\\/pull\\/(\\d+)/g\n let match: RegExpExecArray | null\n let last: { url: string; number: number } | null = null\n while ((match = re.exec(stdout)) !== null) {\n last = { url: match[0], number: Number(match[1]) }\n }\n return last\n}\n\n/** `session.cwd == cwd || one contains the other` — the executor's cwd is\n * usually the worktree root while the command may run in a subdir (or vice\n * versa), so containment is checked in both directions. */\nfunction cwdRelated(sessionCwd: string, cwd: string): boolean {\n if (sessionCwd === cwd) return true\n const sep = \"/\"\n return cwd.startsWith(sessionCwd + sep) || sessionCwd.startsWith(cwd + sep)\n}\n\n/**\n * Best-effort attribution of a `command_execute` run to the executor agent\n * session that most likely issued it: the newest agent-cli session whose cwd\n * is related to the command's cwd, preferring a still-alive one. Returns\n * undefined when no agent-cli session matches (e.g. a bare shell opened the\n * PR) — the caller then skips stamping rather than emit a misleading \"legacy\n * fallback\" footer.\n */\nexport function pickExecutorSession<T extends FooterSession>(\n sessions: readonly T[],\n cwd: string,\n): T | undefined {\n const candidates = sessions.filter(\n s => s.kind === \"agent-cli\" && typeof s.cwd === \"string\" && cwdRelated(s.cwd, cwd),\n )\n if (candidates.length === 0) return undefined\n const alive = (s: T) => s.status === \"running\" || s.status === \"starting\"\n const byRecency = (a: T, b: T) => (b.startedAt ?? \"\").localeCompare(a.startedAt ?? \"\")\n const live = candidates.filter(alive).sort(byRecency)\n if (live.length > 0) return live[0]\n return [...candidates].sort(byRecency)[0]\n}\n"]}
|
|
@@ -156,6 +156,14 @@ declare const RESUME_ID_REJECTED_RE: RegExp;
|
|
|
156
156
|
* past the kill, …). Eligible only when the file is at-or-after the
|
|
157
157
|
* session's `startedAt` (avoid resuming an unrelated prior session
|
|
158
158
|
* in the same cwd — see ResumeStrategy.fsProbe comment).
|
|
159
|
+
*
|
|
160
|
+
* Fix: when NO `adapterSessionId` was ever captured (session killed
|
|
161
|
+
* before the ACP handshake), run a recency-window probe. When an
|
|
162
|
+
* `adapterSessionId` exists but points to a missing JSONL, the probe
|
|
163
|
+
* tries exact-bind and returns nothing — that's correct, we NEVER
|
|
164
|
+
* silently resume a sibling conversation (safety: prevents cross-
|
|
165
|
+
* session contamination). The adapter-level resume will fail with
|
|
166
|
+
* "not found", triggering the fresh-spawn fallback + digest injection.
|
|
159
167
|
*/
|
|
160
168
|
declare function augmentWithFsResume<T extends FsProbeCandidate>(prev: T): Promise<T>;
|
|
161
169
|
/**
|
|
@@ -314,10 +314,16 @@ Verify cwd="${cwd}" and adapterSessionId="${adapterSessionId}".`
|
|
|
314
314
|
}
|
|
315
315
|
return { meta: { source: "claude-code" }, messages };
|
|
316
316
|
}
|
|
317
|
+
async function importNodeSqlite() {
|
|
318
|
+
return import(
|
|
319
|
+
/* @vite-ignore */
|
|
320
|
+
NODE_SQLITE
|
|
321
|
+
);
|
|
322
|
+
}
|
|
317
323
|
async function openHermesDb(dbPath) {
|
|
318
324
|
let DatabaseSync;
|
|
319
325
|
try {
|
|
320
|
-
const sqlite = await
|
|
326
|
+
const sqlite = await importNodeSqlite();
|
|
321
327
|
DatabaseSync = sqlite.DatabaseSync;
|
|
322
328
|
} catch {
|
|
323
329
|
throw new Error("hermes: node:sqlite unavailable. Requires Node.js \u226522.5.0.");
|
|
@@ -359,7 +365,7 @@ function withRetryOnBusy(fn) {
|
|
|
359
365
|
async function openReadonlySqlite(dbPath, label) {
|
|
360
366
|
let DatabaseSync;
|
|
361
367
|
try {
|
|
362
|
-
const sqlite = await
|
|
368
|
+
const sqlite = await importNodeSqlite();
|
|
363
369
|
DatabaseSync = sqlite.DatabaseSync;
|
|
364
370
|
} catch {
|
|
365
371
|
throw new Error(`${label}: node:sqlite unavailable. Requires Node.js \u226522.5.0.`);
|
|
@@ -1378,7 +1384,7 @@ Only sessions spawned via claude-code or hermes can be exported.`
|
|
|
1378
1384
|
content
|
|
1379
1385
|
};
|
|
1380
1386
|
}
|
|
1381
|
-
var ROLE_ICON, IGNORED_CLAUDE_TYPES, EXPORT_STRATEGIES;
|
|
1387
|
+
var ROLE_ICON, IGNORED_CLAUDE_TYPES, NODE_SQLITE, EXPORT_STRATEGIES;
|
|
1382
1388
|
var init_transcript_export = __esm({
|
|
1383
1389
|
"src/transcript-export.ts"() {
|
|
1384
1390
|
init_tool_presenter();
|
|
@@ -1397,6 +1403,7 @@ var init_transcript_export = __esm({
|
|
|
1397
1403
|
"ai-title",
|
|
1398
1404
|
"last-prompt"
|
|
1399
1405
|
]);
|
|
1406
|
+
NODE_SQLITE = "node:sqlite";
|
|
1400
1407
|
EXPORT_STRATEGIES = {
|
|
1401
1408
|
"claude-code": {
|
|
1402
1409
|
exportSession: exportClaudeCodeSession
|
|
@@ -1692,6 +1699,13 @@ async function augmentWithFsResume(prev) {
|
|
|
1692
1699
|
if (!id) return prev;
|
|
1693
1700
|
return {
|
|
1694
1701
|
...prev,
|
|
1702
|
+
// When `adapterSessionId` was never captured (session killed before
|
|
1703
|
+
// the ACP handshake completed), backfill it from the fsProbe result
|
|
1704
|
+
// so the agent restart path can attempt ACP-level resume too — not
|
|
1705
|
+
// just the PTY-native path. For claude-code (and most adapters) the
|
|
1706
|
+
// on-disk conversation id IS the ACP session id, so the values are
|
|
1707
|
+
// interchangeable. Only backfills; never overwrites an existing id.
|
|
1708
|
+
...!prev.adapterSessionId ? { adapterSessionId: id } : {},
|
|
1695
1709
|
resumeMetadata: {
|
|
1696
1710
|
...prev.resumeMetadata ?? {},
|
|
1697
1711
|
[strategy.storeAs]: id
|