From 1d75f913490d1ff1d9cef24ac148a6af1bcfd97a Mon Sep 17 00:00:00 2001 From: pi Date: Fri, 10 Apr 2026 23:06:52 +0100 Subject: [PATCH] initial commit --- README.md | 24 + index.ts | 353 +++ package-lock.json | 4366 ++++++++++++++++++++++++++++++++++ package.json | 24 + src/commands.ts | 76 + src/config.test.ts | 86 + src/config.ts | 97 + src/distill.test.ts | 30 + src/distill.ts | 47 + src/extension.test.ts | 833 +++++++ src/extract.test.ts | 280 +++ src/extract.ts | 314 +++ src/ledger.test.ts | 132 + src/ledger.ts | 196 ++ src/package-manifest.test.ts | 27 + src/packet.test.ts | 130 + src/packet.ts | 91 + src/persist.test.ts | 67 + src/persist.ts | 142 ++ src/prune.test.ts | 131 + src/prune.ts | 54 + src/runtime.test.ts | 178 ++ src/runtime.ts | 127 + src/summaries.test.ts | 139 ++ src/summaries.ts | 251 ++ 25 files changed, 8195 insertions(+) create mode 100644 README.md create mode 100644 index.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/commands.ts create mode 100644 src/config.test.ts create mode 100644 src/config.ts create mode 100644 src/distill.test.ts create mode 100644 src/distill.ts create mode 100644 src/extension.test.ts create mode 100644 src/extract.test.ts create mode 100644 src/extract.ts create mode 100644 src/ledger.test.ts create mode 100644 src/ledger.ts create mode 100644 src/package-manifest.test.ts create mode 100644 src/packet.test.ts create mode 100644 src/packet.ts create mode 100644 src/persist.test.ts create mode 100644 src/persist.ts create mode 100644 src/prune.test.ts create mode 100644 src/prune.ts create mode 100644 src/runtime.test.ts create mode 100644 src/runtime.ts create mode 100644 src/summaries.test.ts create mode 100644 src/summaries.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2d827d --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# pi-context-manager + +`pi-context-manager` is a Pi extension package for context-pressure management, snapshots, resume packets, and branch-summary compaction behavior. + +## Install + +Use it as a local package root today: + +```bash +pi install /absolute/path/to/context-manager +``` + +After this folder is moved into its own repository, the same package can be installed from git. + +## Resources + +- Extension: `./index.ts` + +## Development + +```bash +npm install +npm test +``` diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..376680d --- /dev/null +++ b/index.ts @@ -0,0 +1,353 @@ +import type { AgentMessage } from "@mariozechner/pi-agent-core"; +import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; +import { adjustPolicyForZone } from "./src/config.ts"; +import { deserializeLatestSnapshot, serializeSnapshot, SNAPSHOT_ENTRY_TYPE, type RuntimeSnapshot } from "./src/persist.ts"; +import { createEmptyLedger } from "./src/ledger.ts"; +import { pruneContextMessages } from "./src/prune.ts"; +import { createContextManagerRuntime } from "./src/runtime.ts"; +import { registerContextCommands } from "./src/commands.ts"; +import { buildBranchSummaryFromEntries, buildCompactionSummaryFromPreparation } from "./src/summaries.ts"; + +type TrackedMessage = Extract; +type BranchEntry = ReturnType[number]; + +function isTextPart(part: unknown): part is { type: "text"; text?: string } { + return typeof part === "object" && part !== null && "type" in part && (part as { type?: unknown }).type === "text"; +} + +function toText(content: unknown): string { + if (typeof content === "string") return content; + if (!Array.isArray(content)) return ""; + + return content + .map((part) => { + if (!isTextPart(part)) return ""; + return typeof part.text === "string" ? part.text : ""; + }) + .join("\n") + .trim(); +} + +function isMessageEntry(entry: BranchEntry): entry is Extract { + return entry.type === "message"; +} + +function isCompactionEntry(entry: BranchEntry): entry is Extract { + return entry.type === "compaction"; +} + +function isBranchSummaryEntry(entry: BranchEntry): entry is Extract { + return entry.type === "branch_summary"; +} + +function isTrackedMessage(message: AgentMessage): message is TrackedMessage { + return message.role === "user" || message.role === "assistant" || message.role === "toolResult"; +} + +function createDefaultSnapshot(): RuntimeSnapshot { + return { + mode: "balanced", + lastZone: "green", + ledger: createEmptyLedger(), + }; +} + +function getMessageContent(message: AgentMessage): string { + return "content" in message ? toText(message.content) : ""; +} + +function getMessageToolName(message: AgentMessage): string | undefined { + return message.role === "toolResult" ? message.toolName : undefined; +} + +function rewriteContextMessage(message: { role: string; content: string; original: AgentMessage; distilled?: boolean }): AgentMessage { + if (!message.distilled || message.role !== "toolResult") { + return message.original; + } + + return { + ...(message.original as Extract), + content: [{ type: "text", text: message.content }], + } as AgentMessage; +} + +function findLatestSnapshotState(branch: BranchEntry[]): { snapshot: RuntimeSnapshot; index: number } | undefined { + for (let index = branch.length - 1; index >= 0; index -= 1) { + const entry = branch[index]!; + if (entry.type !== "custom" || entry.customType !== SNAPSHOT_ENTRY_TYPE) { + continue; + } + + const snapshot = deserializeLatestSnapshot([entry]); + if (snapshot) { + return { snapshot, index }; + } + } + + return undefined; +} + +function findLatestSessionSnapshot(entries: BranchEntry[]): RuntimeSnapshot | undefined { + let latest: RuntimeSnapshot | undefined; + let latestFreshness = -Infinity; + + for (const entry of entries) { + if (entry.type !== "custom" || entry.customType !== SNAPSHOT_ENTRY_TYPE) { + continue; + } + + const snapshot = deserializeLatestSnapshot([entry]); + if (!snapshot) { + continue; + } + + const sessionItems = snapshot.ledger.items.filter((item) => item.scope === "session"); + const freshness = sessionItems.length > 0 ? Math.max(...sessionItems.map((item) => item.timestamp)) : -Infinity; + if (freshness >= latestFreshness) { + latest = snapshot; + latestFreshness = freshness; + } + } + + return latest; +} + +function createSessionFallbackSnapshot(source?: RuntimeSnapshot): RuntimeSnapshot { + return { + mode: source?.mode ?? "balanced", + lastZone: "green", + ledger: { + items: structuredClone((source?.ledger.items ?? []).filter((item) => item.scope === "session")), + rollingSummary: "", + }, + }; +} + +function overlaySessionLayer(base: RuntimeSnapshot, latestSessionSnapshot?: RuntimeSnapshot): RuntimeSnapshot { + const sessionItems = latestSessionSnapshot?.ledger.items.filter((item) => item.scope === "session") ?? []; + if (sessionItems.length === 0) { + return base; + } + + return { + ...base, + ledger: { + ...base.ledger, + items: [ + ...structuredClone(base.ledger.items.filter((item) => item.scope !== "session")), + ...structuredClone(sessionItems), + ], + }, + }; +} + +export default function contextManager(pi: ExtensionAPI) { + const runtime = createContextManagerRuntime({ + mode: "balanced", + contextWindow: 200_000, + }); + let pendingResumeInjection = false; + + const syncContextWindow = (ctx: Pick) => { + runtime.setContextWindow(ctx.model?.contextWindow ?? 200_000); + }; + + const armResumeInjection = () => { + const snapshot = runtime.getSnapshot(); + pendingResumeInjection = Boolean(snapshot.lastCompactionSummary || snapshot.lastBranchSummary) && runtime.buildResumePacket().trim().length > 0; + }; + + const replayBranchEntry = (entry: BranchEntry) => { + if (isMessageEntry(entry) && isTrackedMessage(entry.message)) { + runtime.ingest({ + entryId: entry.id, + role: entry.message.role, + text: toText(entry.message.content), + timestamp: entry.message.timestamp, + isError: entry.message.role === "toolResult" ? entry.message.isError : undefined, + }); + return; + } + + if (isCompactionEntry(entry)) { + runtime.recordCompactionSummary(entry.summary, entry.id, Date.parse(entry.timestamp)); + return; + } + + if (isBranchSummaryEntry(entry)) { + runtime.recordBranchSummary(entry.summary, entry.id, Date.parse(entry.timestamp)); + } + }; + + const rebuildRuntimeFromBranch = ( + ctx: Pick, + fallbackSnapshot: RuntimeSnapshot, + options?: { preferRuntimeMode?: boolean }, + ) => { + syncContextWindow(ctx); + + const branch = ctx.sessionManager.getBranch(); + const latestSessionSnapshot = findLatestSessionSnapshot(ctx.sessionManager.getEntries() as BranchEntry[]); + const restored = findLatestSnapshotState(branch); + const baseSnapshot = restored + ? overlaySessionLayer(restored.snapshot, latestSessionSnapshot) + : createSessionFallbackSnapshot(latestSessionSnapshot ?? fallbackSnapshot); + + runtime.restore({ + ...baseSnapshot, + mode: options?.preferRuntimeMode ? fallbackSnapshot.mode : baseSnapshot.mode, + }); + + const replayEntries = restored ? branch.slice(restored.index + 1) : branch; + for (const entry of replayEntries) { + replayBranchEntry(entry); + } + + const snapshot = runtime.getSnapshot(); + ctx.ui.setStatus("context-manager", `ctx ${snapshot.lastZone}`); + }; + + registerContextCommands(pi, { + getSnapshot: runtime.getSnapshot, + buildPacket: runtime.buildPacket, + buildResumePacket: runtime.buildResumePacket, + setMode: runtime.setMode, + rebuildFromBranch: async (commandCtx) => { + rebuildRuntimeFromBranch(commandCtx, runtime.getSnapshot(), { preferRuntimeMode: true }); + armResumeInjection(); + }, + isResumePending: () => pendingResumeInjection, + }); + + pi.on("session_start", async (_event, ctx) => { + rebuildRuntimeFromBranch(ctx, createDefaultSnapshot()); + armResumeInjection(); + }); + + pi.on("session_tree", async (event, ctx) => { + rebuildRuntimeFromBranch(ctx, createDefaultSnapshot()); + + if ( + event.summaryEntry && + !ctx.sessionManager.getBranch().some((entry) => isBranchSummaryEntry(entry) && entry.id === event.summaryEntry.id) + ) { + runtime.recordBranchSummary(event.summaryEntry.summary, event.summaryEntry.id, Date.parse(event.summaryEntry.timestamp)); + } + + armResumeInjection(); + + if (event.summaryEntry) { + pi.appendEntry(SNAPSHOT_ENTRY_TYPE, serializeSnapshot(runtime.getSnapshot())); + } + }); + + pi.on("tool_result", async (event) => { + runtime.ingest({ + entryId: event.toolCallId, + role: "toolResult", + text: toText(event.content), + timestamp: Date.now(), + }); + }); + + pi.on("turn_end", async (_event, ctx) => { + rebuildRuntimeFromBranch(ctx, runtime.getSnapshot(), { preferRuntimeMode: true }); + + const usage = ctx.getContextUsage(); + if (usage?.tokens !== null && usage?.tokens !== undefined) { + runtime.observeTokens(usage.tokens); + } + + const snapshot = runtime.getSnapshot(); + pi.appendEntry(SNAPSHOT_ENTRY_TYPE, serializeSnapshot(snapshot)); + ctx.ui.setStatus("context-manager", `ctx ${snapshot.lastZone}`); + }); + + pi.on("context", async (event, ctx) => { + syncContextWindow(ctx); + const snapshot = runtime.getSnapshot(); + const policy = adjustPolicyForZone(runtime.getPolicy(), snapshot.lastZone); + const normalized = event.messages.map((message) => ({ + role: message.role, + content: getMessageContent(message), + toolName: getMessageToolName(message), + original: message, + })); + + const pruned = pruneContextMessages(normalized, policy); + const nextMessages = pruned.map((message) => + rewriteContextMessage(message as { role: string; content: string; original: AgentMessage; distilled?: boolean }), + ); + const resumeText = pendingResumeInjection ? runtime.buildResumePacket() : ""; + const packetText = pendingResumeInjection ? "" : runtime.buildPacket().text; + const injectedText = resumeText || packetText; + + if (!injectedText) { + return { messages: nextMessages }; + } + + if (resumeText) { + pendingResumeInjection = false; + } + + return { + messages: [ + { + role: "custom", + customType: resumeText ? "context-manager.resume" : "context-manager.packet", + content: injectedText, + display: false, + timestamp: Date.now(), + } as any, + ...nextMessages, + ], + }; + }); + + pi.on("session_before_compact", async (event, ctx) => { + syncContextWindow(ctx); + + try { + return { + compaction: { + summary: buildCompactionSummaryFromPreparation({ + messagesToSummarize: event.preparation.messagesToSummarize, + turnPrefixMessages: event.preparation.turnPrefixMessages, + previousSummary: event.preparation.previousSummary, + fileOps: event.preparation.fileOps, + customInstructions: event.customInstructions, + }), + firstKeptEntryId: event.preparation.firstKeptEntryId, + tokensBefore: event.preparation.tokensBefore, + details: event.preparation.fileOps, + }, + }; + } catch (error) { + ctx.ui.notify(`context-manager compaction fallback: ${error instanceof Error ? error.message : String(error)}`, "warning"); + return; + } + }); + + pi.on("session_before_tree", async (event, ctx) => { + syncContextWindow(ctx); + if (!event.preparation.userWantsSummary) return; + return { + summary: { + summary: buildBranchSummaryFromEntries({ + branchLabel: "branch handoff", + entriesToSummarize: event.preparation.entriesToSummarize, + customInstructions: event.preparation.customInstructions, + replaceInstructions: event.preparation.replaceInstructions, + commonAncestorId: event.preparation.commonAncestorId, + }), + }, + }; + }); + + pi.on("session_compact", async (event, ctx) => { + runtime.recordCompactionSummary(event.compactionEntry.summary, event.compactionEntry.id, Date.parse(event.compactionEntry.timestamp)); + pi.appendEntry(SNAPSHOT_ENTRY_TYPE, serializeSnapshot(runtime.getSnapshot())); + armResumeInjection(); + ctx.ui.setStatus("context-manager", `ctx ${runtime.getSnapshot().lastZone}`); + }); +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3d64d45 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4366 @@ +{ + "name": "pi-context-manager", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pi-context-manager", + "version": "0.1.0", + "devDependencies": { + "@mariozechner/pi-agent-core": "^0.66.1", + "@mariozechner/pi-coding-agent": "^0.66.1", + "@types/node": "^25.5.2", + "tsx": "^4.21.0", + "typescript": "^6.0.2" + }, + "peerDependencies": { + "@mariozechner/pi-agent-core": "*", + "@mariozechner/pi-coding-agent": "*" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.73.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.73.0.tgz", + "integrity": "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1027.0.tgz", + "integrity": "sha512-Qcda5Z5Vb3LPVt7zNycEiiAo9Blk0JpEPJwz/sUBJby6/0zvTlo+/FIXlwYZ3TJHSgKCYiCaBqAB0WRlWDfLfQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-node": "^3.972.30", + "@aws-sdk/eventstream-handler-node": "^3.972.13", + "@aws-sdk/middleware-eventstream": "^3.972.9", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/middleware-websocket": "^3.972.15", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/token-providers": "3.1027.0", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/eventstream-serde-browser": "^4.2.13", + "@smithy/eventstream-serde-config-resolver": "^4.3.13", + "@smithy/eventstream-serde-node": "^4.2.13", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.27.tgz", + "integrity": "sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/xml-builder": "^3.972.17", + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.25.tgz", + "integrity": "sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.27.tgz", + "integrity": "sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.29.tgz", + "integrity": "sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-login": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.29.tgz", + "integrity": "sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.30.tgz", + "integrity": "sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-ini": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.25.tgz", + "integrity": "sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.29.tgz", + "integrity": "sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/token-providers": "3.1026.0", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.1026.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1026.0.tgz", + "integrity": "sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.29.tgz", + "integrity": "sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.13.tgz", + "integrity": "sha512-2Pi1kD0MDkMAxDHqvpi/hKMs9hXUYbj2GLEjCwy+0jzfLChAsF50SUYnOeTI+RztA+Ic4pnLAdB03f1e8nggxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.9.tgz", + "integrity": "sha512-ypgOvpWxQTCnQyDHGxnTviqqANE7FIIzII7VczJnTPCJcJlu17hMQXnvE47aKSKsawVJAaaRsyOEbHQuLJF9ng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.9.tgz", + "integrity": "sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.9.tgz", + "integrity": "sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.10.tgz", + "integrity": "sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.29.tgz", + "integrity": "sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-retry": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.15.tgz", + "integrity": "sha512-hsZ35FORQsN5hwNdMD6zWmHCphbXkDxO6j+xwCUiuMb0O6gzS/PWgttQNl1OAn7h/uqZAMUG4yOS0wY/yhAieg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-format-url": "^3.972.9", + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/eventstream-serde-browser": "^4.2.13", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.19.tgz", + "integrity": "sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.11.tgz", + "integrity": "sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/config-resolver": "^4.4.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1027.0.tgz", + "integrity": "sha512-mI3Jm14cM5sNKc7aNX3cqJe/rFQ2Zzx7x5W8WUtxj2lVxcH2RGYhqI3hK9nnImY6Ec5MeGXCVPjl/q6Mz5HmSA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.7.tgz", + "integrity": "sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.6.tgz", + "integrity": "sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-endpoints": "^3.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-format-url": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.9.tgz", + "integrity": "sha512-fNJXHrs0ZT7Wx0KGIqKv7zLxlDXt2vqjx9z6oKUQFmpE5o4xxnSryvVHfHpIifYHWKz94hFccIldJ0YSZjlCBw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.9.tgz", + "integrity": "sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.15.tgz", + "integrity": "sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.17.tgz", + "integrity": "sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "fast-xml-parser": "5.5.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.49.0.tgz", + "integrity": "sha512-hO69Zl0H3x+L0KL4stl1pLYgnqnwHoLqtKy6MRlNnW8TAxjqMdOUVafomKd4z1BePkzoxJWbYILny9a2Zk43VQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@mariozechner/clipboard": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.2.tgz", + "integrity": "sha512-IHQpksNjo7EAtGuHFU+tbWDp5LarH3HU/8WiB9O70ZEoBPHOg0/6afwSLK0QyNMMmx4Bpi/zl6+DcBXe95nWYA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@mariozechner/clipboard-darwin-arm64": "0.3.2", + "@mariozechner/clipboard-darwin-universal": "0.3.2", + "@mariozechner/clipboard-darwin-x64": "0.3.2", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", + "@mariozechner/clipboard-linux-x64-musl": "0.3.2", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" + } + }, + "node_modules/@mariozechner/clipboard-darwin-arm64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz", + "integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-universal": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz", + "integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-x64": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz", + "integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz", + "integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz", + "integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz", + "integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-gnu": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz", + "integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-musl": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz", + "integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz", + "integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-x64-msvc": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz", + "integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/jiti": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@mariozechner/jiti/-/jiti-2.6.5.tgz", + "integrity": "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "std-env": "^3.10.0", + "yoctocolors": "^2.1.2" + }, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@mariozechner/pi-agent-core": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.66.1.tgz", + "integrity": "sha512-Nj54A7SuB/EQi8r3Gs+glFOr9wz/a9uxYFf0pCLf2DE7VmzA9O7WSejrvArna17K6auftLSdNyRRe2bIO0qezg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mariozechner/pi-ai": "^0.66.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@mariozechner/pi-ai": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.66.1.tgz", + "integrity": "sha512-7IZHvpsFdKEBkTmjNrdVL7JLUJVIpha6bwTr12cZ5XyDrxij06wP6Ncpnf4HT5BXAzD5w2JnoqTOSbMEIZj3dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "^0.73.0", + "@aws-sdk/client-bedrock-runtime": "^3.983.0", + "@google/genai": "^1.40.0", + "@mistralai/mistralai": "1.14.1", + "@sinclair/typebox": "^0.34.41", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "chalk": "^5.6.2", + "openai": "6.26.0", + "partial-json": "^0.1.7", + "proxy-agent": "^6.5.0", + "undici": "^7.19.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@mariozechner/pi-coding-agent": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.66.1.tgz", + "integrity": "sha512-cNmatT+5HvYzQ78cRhRih00wCeUTH/fFx9ecJh5AbN7axgWU+bwiZYy0cjrTsGVgMGF4xMYlPRn/Nze9JEB+/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mariozechner/jiti": "^2.6.2", + "@mariozechner/pi-agent-core": "^0.66.1", + "@mariozechner/pi-ai": "^0.66.1", + "@mariozechner/pi-tui": "^0.66.1", + "@silvia-odwyer/photon-node": "^0.3.4", + "ajv": "^8.17.1", + "chalk": "^5.5.0", + "cli-highlight": "^2.1.11", + "diff": "^8.0.2", + "extract-zip": "^2.0.1", + "file-type": "^21.1.1", + "glob": "^13.0.1", + "hosted-git-info": "^9.0.2", + "ignore": "^7.0.5", + "marked": "^15.0.12", + "minimatch": "^10.2.3", + "proper-lockfile": "^4.1.2", + "strip-ansi": "^7.1.0", + "undici": "^7.19.1", + "yaml": "^2.8.2" + }, + "bin": { + "pi": "dist/cli.js" + }, + "engines": { + "node": ">=20.6.0" + }, + "optionalDependencies": { + "@mariozechner/clipboard": "^0.3.2" + } + }, + "node_modules/@mariozechner/pi-tui": { + "version": "0.66.1", + "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.66.1.tgz", + "integrity": "sha512-hNFN42ebjwtfGooqoUwM+QaPR1XCyqPuueuP3aLOWS1bZ2nZP/jq8MBuGNrmMw1cgiDcotvOlSNj3BatzEOGsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime-types": "^2.1.4", + "chalk": "^5.5.0", + "get-east-asian-width": "^1.3.0", + "marked": "^15.0.12", + "mime-types": "^3.0.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "koffi": "^2.9.0" + } + }, + "node_modules/@mistralai/mistralai": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.14.1.tgz", + "integrity": "sha512-IiLmmZFCCTReQgPAT33r7KQ1nYo5JPdvGkrkZqA8qQ2qB1GHgs5LoP5K2ICyrjnpw2n8oSxMM/VP+liiKcGNlQ==", + "dev": true, + "dependencies": { + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.24.1" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.14.tgz", + "integrity": "sha512-N55f8mPEccpzKetUagdvmAy8oohf0J5cuj9jLI1TaSceRlq0pJsIZepY3kmAXAhyxqXPV6hDerDQhqQPKWgAoQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.14", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.14.tgz", + "integrity": "sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.13.tgz", + "integrity": "sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.13.tgz", + "integrity": "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.0", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.13.tgz", + "integrity": "sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.13.tgz", + "integrity": "sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.13.tgz", + "integrity": "sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.13.tgz", + "integrity": "sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.16.tgz", + "integrity": "sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.13.tgz", + "integrity": "sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.13.tgz", + "integrity": "sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.13.tgz", + "integrity": "sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.29", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.29.tgz", + "integrity": "sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-middleware": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.0.tgz", + "integrity": "sha512-/NzISn4grj/BRFVua/xnQwF+7fakYZgimpw2dfmlPgcqecBMKxpB9g5mLYRrmBD5OrPoODokw4Vi1hrSR4zRyw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/service-error-classification": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.17.tgz", + "integrity": "sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.13.tgz", + "integrity": "sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.13.tgz", + "integrity": "sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.2.tgz", + "integrity": "sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.13.tgz", + "integrity": "sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.13.tgz", + "integrity": "sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.13.tgz", + "integrity": "sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.13.tgz", + "integrity": "sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.13.tgz", + "integrity": "sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.8.tgz", + "integrity": "sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.13.tgz", + "integrity": "sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.9.tgz", + "integrity": "sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.14", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.0.tgz", + "integrity": "sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.13.tgz", + "integrity": "sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.45", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.45.tgz", + "integrity": "sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.49", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.49.tgz", + "integrity": "sha512-jlN6vHwE8gY5AfiFBavtD3QtCX2f7lM3BKkz7nFKSNfFR5nXLXLg6sqXTJEEyDwtxbztIDBQCfjsGVXlIru2lQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.14", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.4.tgz", + "integrity": "sha512-BKoR/ubPp9KNKFxPpg1J28N1+bgu8NGAtJblBP7yHy8yQPBWhIAv9+l92SlQLpolGm71CVO+btB60gTgzT0wog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.13.tgz", + "integrity": "sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.0.tgz", + "integrity": "sha512-tSOPQNT/4KfbvqeMovWC3g23KSYy8czHd3tlN+tOYVNIDLSfxIsrPJihYi5TpNcoV789KWtgChUVedh2y6dDPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.22", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.22.tgz", + "integrity": "sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/types": "^4.14.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.1.tgz", + "integrity": "sha512-0yaL8JdxTknKDILitVpfYfV2Ob6yb3udX/hK97M7I3jOeznBNxQPtVvTUtnhUkyHlxFWyr5Lvknmgzoc7jf+1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.5.8", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", + "integrity": "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/koffi": { + "version": "2.15.6", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.6.tgz", + "integrity": "sha512-WQBpM5uo74UQ17UpsFN+PUOrQQg4/nYdey4SGVluQun2drYYfePziLLWdSmFb4wSdWlJC1aimXQnjhPCheRKuw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz", + "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", + "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-expression-matcher": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.4.0.tgz", + "integrity": "sha512-s4DQMxIdhj3jLFWd9LxHOplj4p9yQ4ffMGowFf3cpEgrrJjEhN0V5nxw4Ye1EViAGDoL4/1AeO6qHpqYPOzE4Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strnum": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/strtok3": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", + "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9c5a817 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "pi-context-manager", + "version": "0.1.0", + "type": "module", + "keywords": ["pi-package"], + "scripts": { + "test": "tsx --test src/*.test.ts src/**/*.test.ts" + }, + "files": ["index.ts", "src"], + "pi": { + "extensions": ["./index.ts"] + }, + "peerDependencies": { + "@mariozechner/pi-agent-core": "*", + "@mariozechner/pi-coding-agent": "*" + }, + "devDependencies": { + "@mariozechner/pi-agent-core": "^0.66.1", + "@mariozechner/pi-coding-agent": "^0.66.1", + "@types/node": "^25.5.2", + "tsx": "^4.21.0", + "typescript": "^6.0.2" + } +} diff --git a/src/commands.ts b/src/commands.ts new file mode 100644 index 0000000..23b7092 --- /dev/null +++ b/src/commands.ts @@ -0,0 +1,76 @@ +import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; +import type { ContextMode } from "./config.ts"; +import { serializeSnapshot, SNAPSHOT_ENTRY_TYPE, type RuntimeSnapshot } from "./persist.ts"; + +interface CommandRuntime { + getSnapshot(): RuntimeSnapshot; + buildPacket(): { estimatedTokens: number }; + buildResumePacket(): string; + setMode(mode: ContextMode): void; + rebuildFromBranch(ctx: ExtensionCommandContext): Promise; + isResumePending(): boolean; +} + +export function registerContextCommands(pi: ExtensionAPI, runtime: CommandRuntime) { + pi.registerCommand("ctx-status", { + description: "Show context pressure, packet status, and persisted handoff state", + handler: async (_args, ctx) => { + const snapshot = runtime.getSnapshot(); + const packet = runtime.buildPacket(); + const resumePending = runtime.isResumePending(); + const contextTokens = ctx.getContextUsage()?.tokens; + const nextInjectionTokens = resumePending ? Math.ceil(runtime.buildResumePacket().length / 4) : packet.estimatedTokens; + ctx.ui.notify( + [ + `mode=${snapshot.mode}`, + `zone=${snapshot.lastZone}`, + `contextTokens=${contextTokens ?? "unknown"}`, + `packetTokens=${packet.estimatedTokens}`, + `nextInjectionTokens=${nextInjectionTokens}`, + `resumePending=${resumePending ? "yes" : "no"}`, + `compaction=${snapshot.lastCompactionSummary ? "yes" : "no"}`, + `branch=${snapshot.lastBranchSummary ? "yes" : "no"}`, + ].join(" "), + "info", + ); + }, + }); + + pi.registerCommand("ctx-memory", { + description: "Inspect the active context ledger", + handler: async (_args, ctx) => { + const snapshot = runtime.getSnapshot(); + await ctx.ui.editor("Context ledger", JSON.stringify(snapshot.ledger, null, 2)); + }, + }); + + pi.registerCommand("ctx-refresh", { + description: "Rebuild runtime state from the current branch and refresh the working packet", + handler: async (_args, ctx) => { + await runtime.rebuildFromBranch(ctx); + const packet = runtime.buildPacket(); + ctx.ui.notify(`rebuilt runtime from branch (${packet.estimatedTokens} tokens)`, "info"); + }, + }); + + pi.registerCommand("ctx-compact", { + description: "Trigger compaction with optional focus instructions", + handler: async (args, ctx) => { + ctx.compact({ customInstructions: args.trim() || undefined }); + }, + }); + + pi.registerCommand("ctx-mode", { + description: "Switch context mode: conservative | balanced | aggressive", + handler: async (args, ctx) => { + const value = args.trim() as "conservative" | "balanced" | "aggressive"; + if (!["conservative", "balanced", "aggressive"].includes(value)) { + ctx.ui.notify("usage: /ctx-mode conservative|balanced|aggressive", "warning"); + return; + } + runtime.setMode(value); + pi.appendEntry(SNAPSHOT_ENTRY_TYPE, serializeSnapshot(runtime.getSnapshot())); + ctx.ui.notify(`context mode set to ${value}`, "info"); + }, + }); +} diff --git a/src/config.test.ts b/src/config.test.ts new file mode 100644 index 0000000..7d212f1 --- /dev/null +++ b/src/config.test.ts @@ -0,0 +1,86 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { adjustPolicyForZone, resolvePolicy, zoneForTokens } from "./config.ts"; + +test("resolvePolicy returns the balanced policy for a 200k context window", () => { + const policy = resolvePolicy({ mode: "balanced", contextWindow: 200_000 }); + + assert.deepEqual(policy, { + mode: "balanced", + recentUserTurns: 4, + packetTokenCap: 1_200, + bulkyBytes: 4_096, + bulkyLines: 150, + yellowAtTokens: 110_000, + redAtTokens: 140_000, + compactAtTokens: 164_000, + }); +}); + +test("resolvePolicy clamps context windows below 50k before calculating thresholds", () => { + const policy = resolvePolicy({ mode: "balanced", contextWindow: 10_000 }); + + assert.deepEqual(policy, { + mode: "balanced", + recentUserTurns: 4, + packetTokenCap: 1_200, + bulkyBytes: 4_096, + bulkyLines: 150, + yellowAtTokens: 27_500, + redAtTokens: 35_000, + compactAtTokens: 41_000, + }); +}); + +test("aggressive mode compacts earlier than conservative mode", () => { + const aggressive = resolvePolicy({ mode: "aggressive", contextWindow: 200_000 }); + const conservative = resolvePolicy({ mode: "conservative", contextWindow: 200_000 }); + + assert.ok(aggressive.compactAtTokens < conservative.compactAtTokens); +}); + +test("aggressive mode reduces raw-window and packet budgets compared with conservative mode", () => { + const aggressive = resolvePolicy({ mode: "aggressive", contextWindow: 200_000 }); + const conservative = resolvePolicy({ mode: "conservative", contextWindow: 200_000 }); + + assert.ok(aggressive.recentUserTurns < conservative.recentUserTurns); + assert.ok(aggressive.packetTokenCap < conservative.packetTokenCap); + assert.ok(aggressive.bulkyBytes < conservative.bulkyBytes); + assert.ok(aggressive.bulkyLines < conservative.bulkyLines); +}); + + test("adjustPolicyForZone tightens packet and pruning thresholds in yellow, red, and compact zones", () => { + const base = resolvePolicy({ mode: "balanced", contextWindow: 200_000 }); + const yellow = adjustPolicyForZone(base, "yellow"); + const red = adjustPolicyForZone(base, "red"); + const compact = adjustPolicyForZone(base, "compact"); + + assert.ok(yellow.packetTokenCap < base.packetTokenCap); + assert.ok(yellow.bulkyBytes < base.bulkyBytes); + assert.ok(red.packetTokenCap < yellow.packetTokenCap); + assert.ok(red.recentUserTurns <= yellow.recentUserTurns); + assert.ok(red.bulkyBytes < yellow.bulkyBytes); + assert.ok(compact.packetTokenCap < red.packetTokenCap); + assert.ok(compact.recentUserTurns <= red.recentUserTurns); + assert.ok(compact.bulkyLines < red.bulkyLines); +}); + +test("zoneForTokens returns green, yellow, red, and compact for the balanced 200k policy", () => { + const policy = resolvePolicy({ mode: "balanced", contextWindow: 200_000 }); + + assert.equal(zoneForTokens(80_000, policy), "green"); + assert.equal(zoneForTokens(120_000, policy), "yellow"); + assert.equal(zoneForTokens(150_000, policy), "red"); + assert.equal(zoneForTokens(170_000, policy), "compact"); +}); + +test("zoneForTokens uses inclusive balanced 200k thresholds", () => { + const policy = resolvePolicy({ mode: "balanced", contextWindow: 200_000 }); + + assert.equal(zoneForTokens(109_999, policy), "green"); + assert.equal(zoneForTokens(110_000, policy), "yellow"); + assert.equal(zoneForTokens(139_999, policy), "yellow"); + assert.equal(zoneForTokens(140_000, policy), "red"); + assert.equal(zoneForTokens(163_999, policy), "red"); + assert.equal(zoneForTokens(164_000, policy), "compact"); +}); diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..7c6a662 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,97 @@ +export type ContextMode = "conservative" | "balanced" | "aggressive"; +export type ContextZone = "green" | "yellow" | "red" | "compact"; + +export interface Policy { + mode: ContextMode; + recentUserTurns: number; + packetTokenCap: number; + bulkyBytes: number; + bulkyLines: number; + yellowAtTokens: number; + redAtTokens: number; + compactAtTokens: number; +} + +export const MODE_PCTS: Record = { + conservative: { yellow: 0.60, red: 0.76, compact: 0.88 }, + balanced: { yellow: 0.55, red: 0.70, compact: 0.82 }, + aggressive: { yellow: 0.50, red: 0.64, compact: 0.76 }, +}; + +const MODE_SETTINGS: Record> = { + conservative: { + recentUserTurns: 5, + packetTokenCap: 1_400, + bulkyBytes: 6_144, + bulkyLines: 220, + }, + balanced: { + recentUserTurns: 4, + packetTokenCap: 1_200, + bulkyBytes: 4_096, + bulkyLines: 150, + }, + aggressive: { + recentUserTurns: 3, + packetTokenCap: 900, + bulkyBytes: 3_072, + bulkyLines: 100, + }, +}; + +export function resolvePolicy(input: { mode: ContextMode; contextWindow: number }): Policy { + const contextWindow = Math.max(input.contextWindow, 50_000); + const percentages = MODE_PCTS[input.mode]; + const settings = MODE_SETTINGS[input.mode]; + + return { + mode: input.mode, + recentUserTurns: settings.recentUserTurns, + packetTokenCap: settings.packetTokenCap, + bulkyBytes: settings.bulkyBytes, + bulkyLines: settings.bulkyLines, + yellowAtTokens: Math.floor(contextWindow * percentages.yellow), + redAtTokens: Math.floor(contextWindow * percentages.red), + compactAtTokens: Math.floor(contextWindow * percentages.compact), + }; +} + +export function adjustPolicyForZone(policy: Policy, zone: ContextZone): Policy { + if (zone === "green") { + return { ...policy }; + } + + if (zone === "yellow") { + return { + ...policy, + packetTokenCap: Math.max(500, Math.floor(policy.packetTokenCap * 0.9)), + bulkyBytes: Math.max(1_536, Math.floor(policy.bulkyBytes * 0.9)), + bulkyLines: Math.max(80, Math.floor(policy.bulkyLines * 0.9)), + }; + } + + if (zone === "red") { + return { + ...policy, + recentUserTurns: Math.max(2, policy.recentUserTurns - 1), + packetTokenCap: Math.max(400, Math.floor(policy.packetTokenCap * 0.75)), + bulkyBytes: Math.max(1_024, Math.floor(policy.bulkyBytes * 0.75)), + bulkyLines: Math.max(60, Math.floor(policy.bulkyLines * 0.75)), + }; + } + + return { + ...policy, + recentUserTurns: Math.max(1, policy.recentUserTurns - 2), + packetTokenCap: Math.max(300, Math.floor(policy.packetTokenCap * 0.55)), + bulkyBytes: Math.max(768, Math.floor(policy.bulkyBytes * 0.5)), + bulkyLines: Math.max(40, Math.floor(policy.bulkyLines * 0.5)), + }; +} + +export function zoneForTokens(tokens: number, policy: Policy): ContextZone { + if (tokens >= policy.compactAtTokens) return "compact"; + if (tokens >= policy.redAtTokens) return "red"; + if (tokens >= policy.yellowAtTokens) return "yellow"; + return "green"; +} diff --git a/src/distill.test.ts b/src/distill.test.ts new file mode 100644 index 0000000..fab5d8e --- /dev/null +++ b/src/distill.test.ts @@ -0,0 +1,30 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { distillToolResult } from "./distill.ts"; + +const noisy = [ + "Build failed while compiling focus parser", + "Error: missing export createFocusMatcher from ./summary-focus.ts", + "at src/summaries.ts:44:12", + "line filler", + "line filler", + "line filler", +].join("\n"); + +test("distillToolResult prioritizes salient failure lines and truncates noise", () => { + const distilled = distillToolResult({ toolName: "bash", content: noisy }); + + assert.ok(distilled); + assert.match(distilled!, /Build failed while compiling focus parser/); + assert.match(distilled!, /missing export createFocusMatcher/); + assert.ok(distilled!.length < 320); +}); + +test("distillToolResult falls back to the first meaningful non-empty lines", () => { + const distilled = distillToolResult({ + toolName: "read", + content: ["", "src/runtime.ts", "exports createContextManagerRuntime", "", "more noise"].join("\n"), + }); + + assert.equal(distilled, "[distilled read output] src/runtime.ts; exports createContextManagerRuntime"); +}); diff --git a/src/distill.ts b/src/distill.ts new file mode 100644 index 0000000..632efb3 --- /dev/null +++ b/src/distill.ts @@ -0,0 +1,47 @@ +const ERROR_RE = /\b(?:error|failed|failure|missing|undefined|exception)\b/i; +const LOCATION_RE = /\b(?:at\s+.+:\d+(?::\d+)?)\b|(?:[A-Za-z0-9_./-]+\.(?:ts|tsx|js|mjs|json|md):\d+(?::\d+)?)/i; +const MAX_SUMMARY_LENGTH = 320; +const MAX_LINES = 2; + +function unique(lines: string[]): string[] { + return lines.filter((line, index) => lines.indexOf(line) === index); +} + +function pickSalientLines(content: string): string[] { + const lines = content + .split(/\n+/) + .map((line) => line.trim()) + .filter(Boolean); + + if (lines.length === 0) { + return []; + } + + const important = unique(lines.filter((line) => ERROR_RE.test(line))); + const location = unique(lines.filter((line) => LOCATION_RE.test(line))); + const fallback = unique(lines); + + const selected: string[] = []; + for (const line of [...important, ...location, ...fallback]) { + if (selected.includes(line)) { + continue; + } + + selected.push(line); + if (selected.length >= MAX_LINES) { + break; + } + } + + return selected; +} + +export function distillToolResult(input: { toolName?: string; content: string }): string | undefined { + const picked = pickSalientLines(input.content); + if (picked.length === 0) { + return undefined; + } + + const prefix = `[distilled ${input.toolName ?? "tool"} output]`; + return `${prefix} ${picked.join("; ")}`.slice(0, MAX_SUMMARY_LENGTH); +} diff --git a/src/extension.test.ts b/src/extension.test.ts new file mode 100644 index 0000000..3ed6437 --- /dev/null +++ b/src/extension.test.ts @@ -0,0 +1,833 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import contextManagerExtension from "../index.ts"; +import { deserializeLatestSnapshot, SNAPSHOT_ENTRY_TYPE, serializeSnapshot, type RuntimeSnapshot } from "./persist.ts"; + +type EventHandler = (event: any, ctx: any) => Promise | any; +type RegisteredCommand = { description: string; handler: (args: string, ctx: any) => Promise | void }; + +type SessionEntry = + | { + type: "message"; + id: string; + parentId: string | null; + timestamp: string; + message: any; + } + | { + type: "custom"; + id: string; + parentId: string | null; + timestamp: string; + customType: string; + data: unknown; + } + | { + type: "compaction"; + id: string; + parentId: string | null; + timestamp: string; + summary: string; + firstKeptEntryId: string; + tokensBefore: number; + } + | { + type: "branch_summary"; + id: string; + parentId: string | null; + timestamp: string; + fromId: string; + summary: string; + }; + +function createUsage(tokens: number) { + return { + tokens, + contextWindow: 200_000, + percent: tokens / 200_000, + }; +} + +function createUserMessage(content: string, timestamp: number) { + return { + role: "user", + content, + timestamp, + }; +} + +function createAssistantMessage(content: string, timestamp: number) { + return { + role: "assistant", + content: [{ type: "text", text: content }], + api: "openai-responses", + provider: "openai", + model: "gpt-5", + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: "stop", + timestamp, + }; +} + +function createToolResultMessage(content: string, timestamp: number) { + return { + role: "toolResult", + toolCallId: `tool-${timestamp}`, + toolName: "read", + content: [{ type: "text", text: content }], + isError: false, + timestamp, + }; +} + +function createMessageEntry(id: string, parentId: string | null, message: any): SessionEntry { + return { + type: "message", + id, + parentId, + timestamp: new Date(message.timestamp).toISOString(), + message, + }; +} + +function createSnapshotEntry( + id: string, + parentId: string | null, + options: { + text: string; + mode?: RuntimeSnapshot["mode"]; + lastZone?: RuntimeSnapshot["lastZone"]; + lastObservedTokens?: number; + lastCompactionSummary?: string; + lastBranchSummary?: string; + ledgerItems?: RuntimeSnapshot["ledger"]["items"]; + rollingSummary?: string; + }, +): SessionEntry { + const { + text, + mode = "aggressive", + lastZone = "red", + lastObservedTokens = 150_000, + lastCompactionSummary = "existing compaction summary", + lastBranchSummary = "existing branch summary", + ledgerItems, + rollingSummary = "stale ledger", + } = options; + + return { + type: "custom", + id, + parentId, + timestamp: new Date(1).toISOString(), + customType: SNAPSHOT_ENTRY_TYPE, + data: serializeSnapshot({ + mode, + lastZone, + lastObservedTokens, + lastCompactionSummary, + lastBranchSummary, + ledger: { + items: ledgerItems ?? [ + { + id: `goal:session:root-goal:${id}`, + kind: "goal", + subject: "root-goal", + text, + scope: "session", + sourceEntryId: "old-user", + sourceType: "user", + timestamp: 1, + confidence: 1, + freshness: 1, + active: true, + supersedesId: undefined, + }, + ], + rollingSummary, + }, + }), + }; +} + +function createHarness(initialBranch: SessionEntry[], options?: { usageTokens?: number }) { + const commands = new Map(); + const handlers = new Map(); + const appendedEntries: Array<{ customType: string; data: unknown }> = []; + const statuses: Array<{ key: string; value: string }> = []; + let branch = [...initialBranch]; + let entries = [...initialBranch]; + + const ctx = { + model: { contextWindow: 200_000 }, + sessionManager: { + getBranch() { + return branch; + }, + getEntries() { + return entries; + }, + }, + ui: { + setStatus(key: string, value: string) { + statuses.push({ key, value }); + }, + notify() {}, + editor: async () => {}, + }, + getContextUsage() { + return options?.usageTokens === undefined ? undefined : createUsage(options.usageTokens); + }, + compact() {}, + }; + + contextManagerExtension({ + registerCommand(name: string, command: RegisteredCommand) { + commands.set(name, command); + }, + on(name: string, handler: EventHandler) { + handlers.set(name, handler); + }, + appendEntry(customType: string, data: unknown) { + appendedEntries.push({ customType, data }); + const entry = { + type: "custom" as const, + id: `custom-${appendedEntries.length}`, + parentId: branch.at(-1)?.id ?? null, + timestamp: new Date(10_000 + appendedEntries.length).toISOString(), + customType, + data, + }; + branch.push(entry); + entries.push(entry); + }, + } as any); + + return { + commands, + handlers, + appendedEntries, + statuses, + ctx, + setBranch(nextBranch: SessionEntry[]) { + branch = [...nextBranch]; + const byId = new Map(entries.map((entry) => [entry.id, entry])); + for (const entry of nextBranch) { + byId.set(entry.id, entry); + } + entries = [...byId.values()]; + }, + }; +} + +test("the extension registers the expected hooks and commands", () => { + const harness = createHarness([]); + + assert.deepEqual([...harness.commands.keys()].sort(), ["ctx-compact", "ctx-memory", "ctx-mode", "ctx-refresh", "ctx-status"]); + assert.deepEqual( + [...harness.handlers.keys()].sort(), + ["context", "session_before_compact", "session_before_tree", "session_compact", "session_start", "session_tree", "tool_result", "turn_end"], + ); +}); + +test("turn_end persists a rebuilt snapshot that includes branch user and assistant facts", async () => { + const branch: SessionEntry[] = [ + createSnapshotEntry("snapshot-1", null, { text: "Stale snapshot fact" }), + createMessageEntry("user-1", "snapshot-1", createUserMessage("Goal: Fix Task 6\nPrefer keeping the public API stable", 2)), + createMessageEntry( + "assistant-1", + "user-1", + createAssistantMessage("Decision: rebuild from ctx.sessionManager.getBranch()\nNext: add integration tests", 3), + ), + createMessageEntry( + "tool-1", + "assistant-1", + createToolResultMessage("Opened .pi/agent/extensions/context-manager/index.ts", 4), + ), + ]; + + const harness = createHarness(branch, { usageTokens: 120_000 }); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + await harness.handlers.get("turn_end")?.( + { + type: "turn_end", + turnIndex: 1, + message: createAssistantMessage("done", 5), + toolResults: [], + }, + harness.ctx, + ); + + assert.equal(harness.appendedEntries.length, 1); + assert.equal(harness.appendedEntries[0]?.customType, SNAPSHOT_ENTRY_TYPE); + + const snapshot = harness.appendedEntries[0]!.data as any; + const activeTexts = snapshot.ledger.items.filter((item: any) => item.active).map((item: any) => item.text); + + assert.equal(snapshot.mode, "aggressive"); + assert.equal(snapshot.lastCompactionSummary, "existing compaction summary"); + assert.equal(snapshot.lastBranchSummary, "existing branch summary"); + assert.equal(snapshot.lastObservedTokens, 120_000); + assert.equal(snapshot.lastZone, "yellow"); + assert.deepEqual(activeTexts, ["Stale snapshot fact", "Fix Task 6", "Prefer keeping the public API stable", "rebuild from ctx.sessionManager.getBranch()", "add integration tests", ".pi/agent/extensions/context-manager/index.ts"]); + assert.deepEqual(harness.statuses.at(-1), { key: "context-manager", value: "ctx yellow" }); +}); + +test("session_tree rebuilds runtime from snapshot-only branches before injecting the next packet", async () => { + const oldBranch: SessionEntry[] = [createSnapshotEntry("snapshot-old", null, { text: "Old branch goal" })]; + const newBranch: SessionEntry[] = [ + createSnapshotEntry("snapshot-new", null, { + text: "Snapshot-only branch goal", + ledgerItems: [ + { + id: "goal:session:root-goal:snapshot-new", + kind: "goal", + subject: "root-goal", + text: "Snapshot-only branch goal", + scope: "session", + sourceEntryId: "snapshot-new", + sourceType: "user", + timestamp: 11, + confidence: 1, + freshness: 11, + active: true, + supersedesId: undefined, + }, + { + id: "decision:branch:branch-decision:snapshot-new", + kind: "decision", + subject: "branch-decision", + text: "Use the snapshot-backed branch state immediately", + scope: "branch", + sourceEntryId: "snapshot-new", + sourceType: "assistant", + timestamp: 12, + confidence: 0.9, + freshness: 12, + active: true, + supersedesId: undefined, + }, + ], + rollingSummary: "snapshot-only branch state", + }), + ]; + + const harness = createHarness(oldBranch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + harness.setBranch(newBranch); + await harness.handlers.get("session_tree")?.( + { + type: "session_tree", + oldLeafId: "snapshot-old", + newLeafId: "snapshot-new", + }, + harness.ctx, + ); + + const result = await harness.handlers.get("context")?.( + { + type: "context", + messages: [createUserMessage("What should happen next?", 13)], + }, + harness.ctx, + ); + + assert.ok(result); + assert.equal(result.messages[0]?.role, "custom"); + assert.equal(result.messages[0]?.customType, "context-manager.resume"); + assert.match(result.messages[0]?.content, /Snapshot-only branch goal/); + assert.match(result.messages[0]?.content, /Use the snapshot-backed branch state immediately/); + assert.doesNotMatch(result.messages[0]?.content, /Old branch goal/); + assert.deepEqual(harness.statuses.at(-1), { key: "context-manager", value: "ctx red" }); +}); + +test("context keeps a distilled stale tool result visible after pruning bulky output", async () => { + const bulkyFailure = [ + "Build failed while compiling focus parser", + "Error: missing export createFocusMatcher from ./summary-focus.ts", + ...Array.from({ length: 220 }, () => "stack frame"), + ].join("\n"); + + const harness = createHarness([]); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + const result = await harness.handlers.get("context")?.( + { + type: "context", + messages: [ + createUserMessage("turn 1", 1), + createToolResultMessage(bulkyFailure, 2), + createAssistantMessage("observed turn 1", 3), + createUserMessage("turn 2", 4), + createAssistantMessage("observed turn 2", 5), + createUserMessage("turn 3", 6), + createAssistantMessage("observed turn 3", 7), + createUserMessage("turn 4", 8), + createAssistantMessage("observed turn 4", 9), + createUserMessage("turn 5", 10), + ], + }, + harness.ctx, + ); + + const toolResult = result.messages.find((message: any) => message.role === "toolResult"); + assert.ok(toolResult); + assert.match(toolResult.content[0].text, /missing export createFocusMatcher/); + assert.ok(toolResult.content[0].text.length < 320); +}); + +test("session_tree preserves session-scoped facts but drops stale branch handoff metadata on an empty destination branch", async () => { + const sourceBranch: SessionEntry[] = [ + createSnapshotEntry("snapshot-session", null, { + text: "Ship the context manager extension", + mode: "balanced", + lastZone: "yellow", + lastObservedTokens: 120_000, + lastCompactionSummary: "## Key Decisions\n- Keep summaries deterministic.", + lastBranchSummary: "# Handoff for branch\n\n## Key Decisions\n- Do not leak branch-local goals.", + }), + ]; + + const harness = createHarness(sourceBranch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + harness.setBranch([]); + await harness.handlers.get("session_tree")?.( + { + type: "session_tree", + oldLeafId: "snapshot-session", + newLeafId: null, + }, + harness.ctx, + ); + + const result = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue", 30)] }, + harness.ctx, + ); + + assert.equal(result.messages[0]?.customType, "context-manager.packet"); + assert.match(result.messages[0]?.content, /Ship the context manager extension/); + assert.doesNotMatch(result.messages[0]?.content, /Do not leak branch-local goals/); + assert.doesNotMatch(result.messages[0]?.content, /Keep summaries deterministic/); +}); + +test("session_tree overlays newer session-scoped facts onto a destination branch with an older snapshot", async () => { + const newerSessionSnapshot = createSnapshotEntry("snapshot-newer", null, { + text: "Ship the context manager extension", + ledgerItems: [ + { + id: "goal:session:root-goal:snapshot-newer", + kind: "goal", + subject: "root-goal", + text: "Ship the context manager extension", + scope: "session", + sourceEntryId: "snapshot-newer", + sourceType: "user", + timestamp: 1, + confidence: 1, + freshness: 1, + active: true, + supersedesId: undefined, + }, + { + id: "constraint:session:must-session-newer:2", + kind: "constraint", + subject: "must-session-newer", + text: "Prefer concise reports across the whole session.", + scope: "session", + sourceEntryId: "snapshot-newer", + sourceType: "user", + timestamp: 2, + confidence: 0.9, + freshness: 2, + active: true, + supersedesId: undefined, + }, + ], + lastCompactionSummary: "", + lastBranchSummary: "", + }); + const olderBranchSnapshot = createSnapshotEntry("snapshot-older", null, { + text: "Ship the context manager extension", + ledgerItems: [ + { + id: "goal:session:root-goal:snapshot-older", + kind: "goal", + subject: "root-goal", + text: "Ship the context manager extension", + scope: "session", + sourceEntryId: "snapshot-older", + sourceType: "user", + timestamp: 1, + confidence: 1, + freshness: 1, + active: true, + supersedesId: undefined, + }, + ], + lastCompactionSummary: "", + lastBranchSummary: "", + }); + + const harness = createHarness([newerSessionSnapshot]); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + harness.setBranch([olderBranchSnapshot]); + await harness.handlers.get("session_tree")?.( + { + type: "session_tree", + oldLeafId: "snapshot-newer", + newLeafId: "snapshot-older", + }, + harness.ctx, + ); + + const result = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue", 32)] }, + harness.ctx, + ); + + assert.match(result.messages[0]?.content, /Prefer concise reports across the whole session/); +}); + +test("ctx-refresh preserves session memory without leaking old handoff summaries on a snapshot-less branch", async () => { + const sourceBranch: SessionEntry[] = [ + createSnapshotEntry("snapshot-refresh", null, { + text: "Ship the context manager extension", + mode: "balanced", + lastZone: "yellow", + lastObservedTokens: 120_000, + lastCompactionSummary: "## Key Decisions\n- Keep summaries deterministic.", + lastBranchSummary: "# Handoff for branch\n\n## Key Decisions\n- Do not leak branch-local goals.", + }), + ]; + + const harness = createHarness(sourceBranch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + harness.setBranch([]); + await harness.commands.get("ctx-refresh")?.handler("", harness.ctx); + + const result = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue", 31)] }, + harness.ctx, + ); + + assert.equal(result.messages[0]?.customType, "context-manager.packet"); + assert.match(result.messages[0]?.content, /Ship the context manager extension/); + assert.doesNotMatch(result.messages[0]?.content, /Do not leak branch-local goals/); + assert.doesNotMatch(result.messages[0]?.content, /Keep summaries deterministic/); +}); + +test("session_start replays default pi compaction blockers into resume state", async () => { + const branch: SessionEntry[] = [ + createSnapshotEntry("snapshot-default", null, { + text: "Ship the context manager extension", + lastCompactionSummary: undefined, + lastBranchSummary: undefined, + }), + { + type: "compaction", + id: "compaction-default-1", + parentId: "snapshot-default", + timestamp: new Date(40).toISOString(), + summary: [ + "## Progress", + "### Blocked", + "- confirm whether /tree replaceInstructions should override defaults", + ].join("\n"), + firstKeptEntryId: "snapshot-default", + tokensBefore: 123_000, + }, + ]; + + const harness = createHarness(branch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + const result = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue", 41)] }, + harness.ctx, + ); + + assert.match(result.messages[0]?.content, /confirm whether \/tree replaceInstructions should override defaults/); +}); + +test("session_before_compact honors preparation inputs and custom focus", async () => { + const harness = createHarness([]); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + const result = await harness.handlers.get("session_before_compact")?.( + { + type: "session_before_compact", + customInstructions: "Focus on decisions and relevant files.", + preparation: { + messagesToSummarize: [createUserMessage("Decision: keep compaction summaries deterministic", 1)], + turnPrefixMessages: [createToolResultMessage("Opened .pi/agent/extensions/context-manager/index.ts", 2)], + previousSummary: "## Goal\n- Ship the context manager extension", + fileOps: { + readFiles: [".pi/agent/extensions/context-manager/index.ts"], + modifiedFiles: [".pi/agent/extensions/context-manager/src/summaries.ts"], + }, + tokensBefore: 120_000, + firstKeptEntryId: "keep-1", + }, + branchEntries: [], + signal: AbortSignal.timeout(1_000), + }, + harness.ctx, + ); + + assert.equal(result.compaction.firstKeptEntryId, "keep-1"); + assert.equal(result.compaction.tokensBefore, 120_000); + assert.match(result.compaction.summary, /keep compaction summaries deterministic/); + assert.match(result.compaction.summary, /index.ts/); +}); + +test("session_before_tree honors abandoned-branch entries and focus text", async () => { + const harness = createHarness([]); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + const result = await harness.handlers.get("session_before_tree")?.( + { + type: "session_before_tree", + preparation: { + targetId: "target-1", + oldLeafId: "old-1", + commonAncestorId: "root", + userWantsSummary: true, + customInstructions: "Focus on goals and decisions.", + replaceInstructions: false, + entriesToSummarize: [ + createMessageEntry("user-1", null, createUserMessage("Goal: explore tree handoff", 1)), + createMessageEntry("assistant-1", "user-1", createAssistantMessage("Decision: do not leak branch-local goals", 2)), + ], + }, + signal: AbortSignal.timeout(1_000), + }, + harness.ctx, + ); + + assert.ok(result?.summary?.summary); + assert.match(result.summary.summary, /explore tree handoff/); + assert.match(result.summary.summary, /do not leak branch-local goals/); +}); + +test("session_compact persists the latest compaction summary into a fresh snapshot and injects a resume packet once", async () => { + const harness = createHarness([]); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + await harness.handlers.get("session_compact")?.( + { + type: "session_compact", + fromExtension: true, + compactionEntry: { + type: "compaction", + id: "cmp-1", + parentId: "prev", + timestamp: new Date(10).toISOString(), + summary: "## Key Decisions\n- Keep summaries deterministic.\n\n## Open questions and blockers\n- Verify /tree replaceInstructions behavior.", + firstKeptEntryId: "keep-1", + tokensBefore: 140_000, + }, + }, + harness.ctx, + ); + + assert.equal(harness.appendedEntries.at(-1)?.customType, SNAPSHOT_ENTRY_TYPE); + + const context = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue", 11)] }, + harness.ctx, + ); + assert.match(context.messages[0]?.content, /Keep summaries deterministic/); + assert.match(context.messages[0]?.content, /Verify \/tree replaceInstructions behavior/); + + const nextContext = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue again", 12)] }, + harness.ctx, + ); + assert.equal(nextContext.messages[0]?.customType, "context-manager.packet"); + assert.doesNotMatch(nextContext.messages[0]?.content ?? "", /## Latest compaction handoff/); +}); + +test("session_tree replays branch summaries newer than the latest snapshot before the next packet is injected", async () => { + const branch: SessionEntry[] = [ + createSnapshotEntry("snapshot-1", null, { text: "Ship the context manager extension" }), + { + type: "branch_summary", + id: "branch-summary-1", + parentId: "snapshot-1", + timestamp: new Date(20).toISOString(), + fromId: "old-leaf", + summary: "# Handoff for branch\n\n## Key Decisions\n- Do not leak branch-local goals.", + }, + ]; + + const harness = createHarness(branch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + const context = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("what next", 21)] }, + harness.ctx, + ); + assert.match(context.messages[0]?.content, /Do not leak branch-local goals/); +}); + + +test("session_tree records event summaryEntry before persisting the next snapshot", async () => { + const branch: SessionEntry[] = [ + createSnapshotEntry("snapshot-1", null, { + text: "Ship the context manager extension", + lastCompactionSummary: "", + lastBranchSummary: "", + }), + ]; + + const harness = createHarness(branch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + await harness.handlers.get("session_tree")?.( + { + type: "session_tree", + fromExtension: true, + summaryEntry: { + type: "branch_summary", + id: "branch-summary-event", + parentId: "snapshot-1", + timestamp: new Date(20).toISOString(), + fromId: "old-leaf", + summary: "# Handoff for branch\n\n## Key Decisions\n- Preserve the latest branch summary from the event payload.", + }, + }, + harness.ctx, + ); + + const snapshot = harness.appendedEntries.at(-1)?.data as RuntimeSnapshot | undefined; + assert.match(snapshot?.lastBranchSummary ?? "", /Preserve the latest branch summary/); + + const context = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("what changed", 21)] }, + harness.ctx, + ); + assert.match(context.messages[0]?.content, /Preserve the latest branch summary/); +}); + +test("ctx-status reports mode, zone, packet size, and summary-artifact presence", async () => { + const branch = [ + createSnapshotEntry("snapshot-1", null, { + text: "Ship the context manager extension", + mode: "balanced", + lastZone: "yellow", + lastObservedTokens: 120_000, + lastCompactionSummary: "## Key Decisions\n- Keep summaries deterministic.", + lastBranchSummary: "# Handoff for branch\n\n## Key Decisions\n- Do not leak branch-local goals.", + }), + ]; + + const notifications: string[] = []; + const harness = createHarness(branch); + harness.ctx.ui.notify = (message: string) => notifications.push(message); + + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + await harness.commands.get("ctx-status")?.handler("", harness.ctx); + + assert.match(notifications.at(-1) ?? "", /mode=balanced/); + assert.match(notifications.at(-1) ?? "", /zone=yellow/); + assert.match(notifications.at(-1) ?? "", /compaction=yes/); + assert.match(notifications.at(-1) ?? "", /branch=yes/); +}); + +test("ctx-refresh rebuilds runtime from the current branch instead of only re-rendering the packet", async () => { + const harness = createHarness([createSnapshotEntry("snapshot-1", null, { text: "Old goal" })]); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + harness.setBranch([ + createSnapshotEntry("snapshot-2", null, { + text: "New branch goal", + lastBranchSummary: "# Handoff for branch\n\n## Key Decisions\n- Use the new branch immediately.", + }), + ]); + + await harness.commands.get("ctx-refresh")?.handler("", harness.ctx); + const result = await harness.handlers.get("context")?.( + { type: "context", messages: [createUserMessage("continue", 3)] }, + harness.ctx, + ); + + assert.match(result.messages[0]?.content, /New branch goal/); + assert.doesNotMatch(result.messages[0]?.content, /Old goal/); +}); + +test("ctx-mode persists the updated mode immediately without waiting for turn_end", async () => { + const branch: SessionEntry[] = [ + createSnapshotEntry("snapshot-1", null, { + text: "Persist the updated mode", + mode: "balanced", + lastZone: "green", + lastObservedTokens: 90_000, + }), + ]; + + const harness = createHarness(branch); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + assert.equal(deserializeLatestSnapshot(harness.ctx.sessionManager.getBranch())?.mode, "balanced"); + + const modeCommand = harness.commands.get("ctx-mode"); + assert.ok(modeCommand); + await modeCommand.handler("aggressive", harness.ctx); + + assert.equal(harness.appendedEntries.length, 1); + assert.equal(harness.appendedEntries[0]?.customType, SNAPSHOT_ENTRY_TYPE); + assert.equal(deserializeLatestSnapshot(harness.ctx.sessionManager.getBranch())?.mode, "aggressive"); +}); + +test("ctx-mode changes survive turn_end and persist into the next snapshot", async () => { + const branch: SessionEntry[] = [ + createSnapshotEntry("snapshot-1", null, { + text: "Persist the updated mode", + mode: "balanced", + lastZone: "green", + lastObservedTokens: 90_000, + }), + ]; + + const harness = createHarness(branch, { usageTokens: 105_000 }); + await harness.handlers.get("session_start")?.({ type: "session_start" }, harness.ctx); + + const modeCommand = harness.commands.get("ctx-mode"); + assert.ok(modeCommand); + await modeCommand.handler("aggressive", harness.ctx); + + await harness.handlers.get("turn_end")?.( + { + type: "turn_end", + turnIndex: 1, + message: createAssistantMessage("done", 5), + toolResults: [], + }, + harness.ctx, + ); + + assert.equal(harness.appendedEntries.length, 2); + + const immediateSnapshot = harness.appendedEntries[0]!.data as any; + assert.equal(immediateSnapshot.mode, "aggressive"); + assert.equal(immediateSnapshot.lastObservedTokens, 90_000); + assert.equal(immediateSnapshot.lastZone, "green"); + + const snapshot = harness.appendedEntries[1]!.data as any; + assert.equal(snapshot.mode, "aggressive"); + assert.equal(snapshot.lastObservedTokens, 105_000); + assert.equal(snapshot.lastZone, "yellow"); +}); diff --git a/src/extract.test.ts b/src/extract.test.ts new file mode 100644 index 0000000..9c521a7 --- /dev/null +++ b/src/extract.test.ts @@ -0,0 +1,280 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { extractCandidates } from "./extract.ts"; +import { createEmptyLedger, getActiveItems, mergeCandidates } from "./ledger.ts"; + +test("extractCandidates pulls goals, constraints, decisions, next steps, and file references", () => { + const candidates = extractCandidates({ + entryId: "u1", + role: "user", + text: [ + "Goal: Build a context manager extension for pi.", + "We must adapt to the active model context window.", + "Decision: keep the MVP quiet and avoid new LLM-facing tools.", + "Next: inspect .pi/agent/extensions/web-search/index.ts and docs/extensions.md.", + ].join("\n"), + timestamp: 1, + }); + + assert.deepEqual( + candidates.map((candidate) => [ + candidate.kind, + candidate.subject, + candidate.scope, + candidate.sourceEntryId, + candidate.sourceType, + candidate.timestamp, + ]), + [ + ["goal", "root-goal", "session", "u1", "user", 1], + ["constraint", "must-u1-0", "branch", "u1", "user", 1], + ["decision", "decision-u1-0", "branch", "u1", "user", 1], + ["activeTask", "next-step-u1-0", "branch", "u1", "user", 1], + ["relevantFile", ".pi/agent/extensions/web-search/index.ts", "branch", "u1", "user", 1], + ["relevantFile", "docs/extensions.md", "branch", "u1", "user", 1], + ], + ); +}); + +test("extractCandidates promotes only the first durable goal to session scope", () => { + const firstGoal = extractCandidates( + { + entryId: "u-goal-1", + role: "user", + text: "Goal: Ship the context manager extension.", + timestamp: 10, + }, + { hasSessionGoal: false }, + ); + + const branchGoal = extractCandidates( + { + entryId: "u-goal-2", + role: "user", + text: "Goal: prototype a branch-local tree handoff.", + timestamp: 11, + }, + { hasSessionGoal: true }, + ); + + assert.deepEqual( + firstGoal.map((candidate) => [candidate.kind, candidate.subject, candidate.scope, candidate.text]), + [["goal", "root-goal", "session", "Ship the context manager extension."]], + ); + assert.deepEqual( + branchGoal.map((candidate) => [candidate.kind, candidate.subject, candidate.scope, candidate.text]), + [["goal", "goal-u-goal-2-0", "branch", "prototype a branch-local tree handoff."]], + ); +}); + +test("mergeCandidates keeps independently extracted decisions, constraints, and next steps active", () => { + const ledger = mergeCandidates(createEmptyLedger(), [ + ...extractCandidates({ + entryId: "u1", + role: "user", + text: [ + "We must adapt to the active model context window.", + "Decision: keep snapshots tiny.", + "Next: inspect src/extract.ts.", + ].join("\n"), + timestamp: 1, + }), + ...extractCandidates({ + entryId: "u2", + role: "user", + text: [ + "We prefer concise reports across the whole session.", + "Decision: persist snapshots after each turn_end.", + "Task: add regression coverage.", + ].join("\n"), + timestamp: 2, + }), + ]); + + assert.deepEqual( + getActiveItems(ledger, "constraint").map((item) => [item.subject, item.sourceEntryId, item.text]), + [ + ["must-u1-0", "u1", "We must adapt to the active model context window."], + ["must-u2-0", "u2", "We prefer concise reports across the whole session."], + ], + ); + assert.deepEqual( + getActiveItems(ledger, "decision").map((item) => [item.subject, item.sourceEntryId, item.text]), + [ + ["decision-u1-0", "u1", "keep snapshots tiny."], + ["decision-u2-0", "u2", "persist snapshots after each turn_end."], + ], + ); + assert.deepEqual( + getActiveItems(ledger, "activeTask").map((item) => [item.subject, item.sourceEntryId, item.text]), + [ + ["next-step-u1-0", "u1", "inspect src/extract.ts."], + ["next-step-u2-0", "u2", "add regression coverage."], + ], + ); +}); + +test("user constraints default to branch scope unless they explicitly signal durable session scope", () => { + const candidates = extractCandidates({ + entryId: "u3", + role: "user", + text: [ + "We should keep this branch experimental for now.", + "We should keep the MVP branch experimental.", + "We should rename the context window helper in this module.", + "Avoid touching docs/extensions.md.", + "Avoid touching docs/extensions.md across the whole session.", + "Prefer concise reports across the whole session.", + ].join("\n"), + timestamp: 3, + }); + + assert.deepEqual( + candidates + .filter((candidate) => candidate.kind === "constraint") + .map((candidate) => [candidate.text, candidate.scope, candidate.subject]), + [ + ["We should keep this branch experimental for now.", "branch", "must-u3-0"], + ["We should keep the MVP branch experimental.", "branch", "must-u3-1"], + ["We should rename the context window helper in this module.", "branch", "must-u3-2"], + ["Avoid touching docs/extensions.md.", "branch", "must-u3-3"], + ["Avoid touching docs/extensions.md across the whole session.", "session", "must-u3-4"], + ["Prefer concise reports across the whole session.", "session", "must-u3-5"], + ], + ); +}); + +test("extractCandidates treats spelled-out do not as a constraint trigger", () => { + const candidates = extractCandidates({ + entryId: "u4", + role: "user", + text: "Do not add new LLM-facing tools across the whole session.", + timestamp: 4, + }); + + assert.deepEqual( + candidates.map((candidate) => [candidate.kind, candidate.text, candidate.scope, candidate.subject]), + [["constraint", "Do not add new LLM-facing tools across the whole session.", "session", "must-u4-0"]], + ); +}); + +test("extractCandidates keeps compaction goals branch-scoped unless they are explicitly session-wide", () => { + const candidates = extractCandidates( + { + entryId: "cmp-goal-1", + role: "compaction", + text: "## Goal\n- prototype a branch-local tree handoff.", + timestamp: 19, + }, + { hasSessionGoal: false }, + ); + + assert.deepEqual( + candidates.map((candidate) => [candidate.kind, candidate.subject, candidate.scope, candidate.text]), + [["goal", "goal-cmp-goal-1-0", "branch", "prototype a branch-local tree handoff."]], + ); +}); + +test("extractCandidates captures blockers from direct lines, tool errors, and structured summaries", () => { + const direct = extractCandidates( + { + entryId: "a-blocked-1", + role: "assistant", + text: "Blocked: confirm whether /tree summaries should replace instructions.", + timestamp: 20, + }, + { hasSessionGoal: true }, + ); + + const tool = extractCandidates( + { + entryId: "t-blocked-1", + role: "toolResult", + text: "Error: missing export createFocusMatcher\nstack...", + timestamp: 21, + isError: true, + }, + { hasSessionGoal: true }, + ); + + const summary = extractCandidates( + { + entryId: "cmp-1", + role: "compaction", + text: [ + "## Open questions and blockers", + "- Need to confirm whether /tree summaries should replace instructions.", + "", + "## Relevant files", + "- .pi/agent/extensions/context-manager/index.ts", + ].join("\n"), + timestamp: 22, + }, + { hasSessionGoal: true }, + ); + + assert.deepEqual( + direct.map((candidate) => [candidate.kind, candidate.subject, candidate.text]), + [["openQuestion", "open-question-a-blocked-1-0", "confirm whether /tree summaries should replace instructions."]], + ); + assert.equal(tool[0]?.kind, "openQuestion"); + assert.match(tool[0]?.text ?? "", /missing export createFocusMatcher/); + assert.deepEqual( + summary.map((candidate) => [candidate.kind, candidate.text]), + [ + ["openQuestion", "Need to confirm whether /tree summaries should replace instructions."], + ["relevantFile", ".pi/agent/extensions/context-manager/index.ts"], + ], + ); +}); + +test("extractCandidates parses pi fallback progress and blocked summary sections", () => { + const candidates = extractCandidates( + { + entryId: "cmp-default-1", + role: "compaction", + text: [ + "## Constraints and preferences", + "- Keep the public API stable.", + "", + "## Progress", + "### In Progress", + "- Wire runtime hydration.", + "", + "### Blocked", + "- confirm whether /tree replaceInstructions should override defaults", + ].join("\n"), + timestamp: 23, + }, + { hasSessionGoal: true }, + ); + + assert.ok(candidates.some((candidate) => candidate.kind === "constraint" && candidate.text === "Keep the public API stable.")); + assert.ok(candidates.some((candidate) => candidate.kind === "activeTask" && candidate.text === "Wire runtime hydration.")); + assert.ok( + candidates.some( + (candidate) => candidate.kind === "openQuestion" && candidate.text === "confirm whether /tree replaceInstructions should override defaults", + ), + ); +}); + +test("assistant decisions and tool-result file references are extracted as branch facts", () => { + const assistant = extractCandidates({ + entryId: "a1", + role: "assistant", + text: "Decision: persist snapshots after each turn_end.", + timestamp: 2, + }); + + const tool = extractCandidates({ + entryId: "t1", + role: "toolResult", + text: "Updated file: .pi/agent/extensions/context-manager/src/runtime.ts", + timestamp: 3, + }); + + assert.equal(assistant[0]?.kind, "decision"); + assert.equal(assistant[0]?.subject, "decision-a1-0"); + assert.equal(assistant[0]?.scope, "branch"); + assert.equal(tool[0]?.kind, "relevantFile"); +}); diff --git a/src/extract.ts b/src/extract.ts new file mode 100644 index 0000000..4aa53fb --- /dev/null +++ b/src/extract.ts @@ -0,0 +1,314 @@ +import type { MemoryCandidate, MemoryScope, MemorySourceType } from "./ledger.ts"; + +export interface ExtractOptions { + hasSessionGoal?: boolean; +} + +export interface TranscriptSlice { + entryId: string; + role: "user" | "assistant" | "toolResult" | "compaction" | "branchSummary"; + text: string; + timestamp: number; + isError?: boolean; +} + +const FILE_RE = /(?:\.?\/?[A-Za-z0-9_./-]+\.(?:ts|tsx|js|mjs|json|md))/g; +const BRANCH_LOCAL_CONSTRAINT_RE = + /\b(?:this|current)\s+(?:branch|task|change|step|file|module|test|command|implementation|worktree)\b|\b(?:for now|right now|in this branch|on this branch|in this file|in this module|here)\b/i; +const DURABLE_SESSION_CONSTRAINT_RE = + /\b(?:whole|entire|rest of (?:the )?|remaining)\s+(?:session|project|codebase)\b|\bacross (?:the )?(?:whole )?(?:session|project|codebase)\b|\bacross (?:all |every )?branches\b|\b(?:session|project|codebase)[-\s]?wide\b|\bthroughout (?:the )?(?:session|project|codebase)\b/i; +const CONSTRAINT_RE = /\b(?:must|should|don't|do not|avoid|prefer)\b/i; +const GOAL_RE = /^(goal|session goal|overall goal):/i; +const OPEN_QUESTION_RE = /^(?:blocked|blocker|open question|question):/i; +const ERROR_LINE_RE = /\b(?:error|failed|failure|missing|undefined|exception)\b/i; + +type SummarySectionKind = "goal" | "constraint" | "decision" | "activeTask" | "openQuestion" | "relevantFile"; + +function sourceTypeForRole(role: TranscriptSlice["role"]): MemorySourceType { + if (role === "compaction") return "compaction"; + if (role === "branchSummary") return "branchSummary"; + return role; +} + +function pushCandidate( + list: MemoryCandidate[], + candidate: Omit, + slice: TranscriptSlice, +) { + list.push({ + ...candidate, + sourceEntryId: slice.entryId, + sourceType: sourceTypeForRole(slice.role), + timestamp: slice.timestamp, + }); +} + +function createIndexedSubject(prefix: string, slice: TranscriptSlice, index: number): string { + return `${prefix}-${slice.entryId}-${index}`; +} + +function inferConstraintScope(slice: TranscriptSlice, line: string): MemoryScope { + if (slice.role !== "user") { + return "branch"; + } + + if (BRANCH_LOCAL_CONSTRAINT_RE.test(line)) { + return "branch"; + } + + if (DURABLE_SESSION_CONSTRAINT_RE.test(line)) { + return "session"; + } + + if ((line.match(FILE_RE) ?? []).length > 0) { + return "branch"; + } + + return "branch"; +} + +function nextGoalCandidate( + line: string, + slice: TranscriptSlice, + options: ExtractOptions, + index: number, +): Omit { + const text = line.replace(GOAL_RE, "").trim(); + const explicitSessionGoal = /^(session goal|overall goal):/i.test(line); + const canSeedSessionGoal = slice.role === "user"; + const shouldPromoteRootGoal = explicitSessionGoal || (!options.hasSessionGoal && canSeedSessionGoal); + + if (shouldPromoteRootGoal) { + return { + kind: "goal", + subject: "root-goal", + text, + scope: "session", + confidence: 1, + }; + } + + return { + kind: "goal", + subject: createIndexedSubject("goal", slice, index), + text, + scope: "branch", + confidence: 0.9, + }; +} + +function nextOpenQuestionCandidate( + text: string, + slice: TranscriptSlice, + index: number, +): Omit { + return { + kind: "openQuestion", + subject: createIndexedSubject("open-question", slice, index), + text, + scope: "branch", + confidence: slice.role === "toolResult" ? 0.85 : 0.8, + }; +} + +function summarySectionToKind(line: string): SummarySectionKind | undefined { + const heading = line.replace(/^##\s+/i, "").trim().toLowerCase(); + + if (heading === "goal") return "goal"; + if (heading === "constraints" || heading === "constraints & preferences" || heading === "constraints and preferences") { + return "constraint"; + } + if (heading === "decisions" || heading === "key decisions") return "decision"; + if (heading === "active work" || heading === "next steps" || heading === "current task" || heading === "progress") { + return "activeTask"; + } + if (heading === "open questions and blockers" || heading === "open questions / blockers") return "openQuestion"; + if (heading === "relevant files" || heading === "critical context") return "relevantFile"; + + return undefined; +} + +function pushRelevantFiles(list: MemoryCandidate[], slice: TranscriptSlice, line: string) { + const fileMatches = line.match(FILE_RE) ?? []; + for (const match of fileMatches) { + pushCandidate( + list, + { + kind: "relevantFile", + subject: match, + text: match, + scope: "branch", + confidence: 0.7, + }, + slice, + ); + } +} + +export function extractCandidates(slice: TranscriptSlice, options: ExtractOptions = {}): MemoryCandidate[] { + const out: MemoryCandidate[] = []; + const lines = slice.text + .split(/\n+/) + .map((line) => line.trim()) + .filter(Boolean); + + let currentSection: SummarySectionKind | undefined; + let goalIndex = 0; + let decisionIndex = 0; + let nextStepIndex = 0; + let mustIndex = 0; + let openQuestionIndex = 0; + + for (const line of lines) { + if (/^##\s+/i.test(line)) { + currentSection = summarySectionToKind(line); + continue; + } + + if (/^###\s+/i.test(line)) { + const subheading = line.replace(/^###\s+/i, "").trim().toLowerCase(); + if (subheading === "blocked") { + currentSection = "openQuestion"; + } else if (subheading === "in progress" || subheading === "done") { + currentSection = "activeTask"; + } + continue; + } + + const bullet = line.match(/^-\s+(.*)$/)?.[1]?.trim(); + const isGoal = GOAL_RE.test(line); + const isDecision = /^decision:/i.test(line); + const isNextStep = /^(next|task):/i.test(line); + const isOpenQuestion = OPEN_QUESTION_RE.test(line); + + if (isGoal) { + pushCandidate(out, nextGoalCandidate(line, slice, options, goalIndex++), slice); + pushRelevantFiles(out, slice, line); + continue; + } + + if (isOpenQuestion) { + pushCandidate( + out, + nextOpenQuestionCandidate(line.replace(OPEN_QUESTION_RE, "").trim(), slice, openQuestionIndex++), + slice, + ); + pushRelevantFiles(out, slice, line); + continue; + } + + if (isDecision) { + pushCandidate( + out, + { + kind: "decision", + subject: createIndexedSubject("decision", slice, decisionIndex++), + text: line.replace(/^decision:\s*/i, "").trim(), + scope: "branch", + confidence: 0.9, + }, + slice, + ); + pushRelevantFiles(out, slice, line); + continue; + } + + if (isNextStep) { + pushCandidate( + out, + { + kind: "activeTask", + subject: createIndexedSubject("next-step", slice, nextStepIndex++), + text: line.replace(/^(next|task):\s*/i, "").trim(), + scope: "branch", + confidence: 0.8, + }, + slice, + ); + pushRelevantFiles(out, slice, line); + continue; + } + + if (bullet && currentSection === "goal") { + pushCandidate(out, nextGoalCandidate(`Goal: ${bullet}`, slice, options, goalIndex++), slice); + continue; + } + + if (bullet && currentSection === "constraint") { + pushCandidate( + out, + { + kind: "constraint", + subject: createIndexedSubject("must", slice, mustIndex++), + text: bullet, + scope: inferConstraintScope(slice, bullet), + confidence: 0.8, + }, + slice, + ); + continue; + } + + if (bullet && currentSection === "decision") { + pushCandidate( + out, + { + kind: "decision", + subject: createIndexedSubject("decision", slice, decisionIndex++), + text: bullet, + scope: "branch", + confidence: 0.9, + }, + slice, + ); + continue; + } + + if (bullet && currentSection === "activeTask") { + pushCandidate( + out, + { + kind: "activeTask", + subject: createIndexedSubject("next-step", slice, nextStepIndex++), + text: bullet, + scope: "branch", + confidence: 0.8, + }, + slice, + ); + continue; + } + + if (bullet && currentSection === "openQuestion") { + pushCandidate(out, nextOpenQuestionCandidate(bullet, slice, openQuestionIndex++), slice); + continue; + } + + if (bullet && currentSection === "relevantFile") { + pushRelevantFiles(out, slice, bullet); + continue; + } + + if (slice.role === "toolResult" && (slice.isError || ERROR_LINE_RE.test(line))) { + pushCandidate(out, nextOpenQuestionCandidate(line, slice, openQuestionIndex++), slice); + } + + if (CONSTRAINT_RE.test(line)) { + pushCandidate( + out, + { + kind: "constraint", + subject: createIndexedSubject("must", slice, mustIndex++), + text: line, + scope: inferConstraintScope(slice, line), + confidence: 0.8, + }, + slice, + ); + } + + pushRelevantFiles(out, slice, line); + } + + return out; +} diff --git a/src/ledger.test.ts b/src/ledger.test.ts new file mode 100644 index 0000000..d69aca9 --- /dev/null +++ b/src/ledger.test.ts @@ -0,0 +1,132 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createEmptyLedger, getActiveItems, mergeCandidates, type MemoryCandidate } from "./ledger.ts"; + +const base: Omit = { + scope: "branch", + sourceEntryId: "u1", + sourceType: "user", + timestamp: 1, + confidence: 0.9, +}; + +test("mergeCandidates adds new active items to an empty ledger", () => { + const ledger = mergeCandidates(createEmptyLedger(), [ + { ...base, kind: "goal", subject: "root-goal", text: "Build a pi context manager extension" }, + ]); + + assert.equal(getActiveItems(ledger).length, 1); + assert.equal(getActiveItems(ledger)[0]?.text, "Build a pi context manager extension"); +}); + +test("mergeCandidates archives older items when a new item supersedes the same subject", () => { + const first = mergeCandidates(createEmptyLedger(), [ + { ...base, kind: "decision", subject: "persistence", text: "Persist snapshots with appendEntry()", timestamp: 1 }, + ]); + + const second = mergeCandidates(first, [ + { ...base, kind: "decision", subject: "persistence", text: "Persist snapshots after each turn_end", timestamp: 2 }, + ]); + + const active = getActiveItems(second, "decision"); + assert.equal(active.length, 1); + assert.equal(active[0]?.text, "Persist snapshots after each turn_end"); + assert.equal(active[0]?.supersedesId, "decision:branch:persistence:1"); + assert.equal(second.items.find((item) => item.id === "decision:branch:persistence:1")?.active, false); +}); + +test("mergeCandidates keeps the newest item active when same-slot candidates arrive out of order", () => { + const ledger = mergeCandidates(createEmptyLedger(), [ + { ...base, kind: "decision", subject: "persistence", text: "Persist snapshots after each turn_end", timestamp: 2 }, + { ...base, kind: "decision", subject: "persistence", text: "Persist snapshots with appendEntry()", timestamp: 1 }, + ]); + + const active = getActiveItems(ledger, "decision"); + const stale = ledger.items.find((item) => item.text === "Persist snapshots with appendEntry()"); + + assert.equal(active.length, 1); + assert.equal(active[0]?.text, "Persist snapshots after each turn_end"); + assert.equal(stale?.active, false); + assert.equal(stale?.supersedesId, undefined); +}); + +test("mergeCandidates gives same-slot same-timestamp candidates distinct ids", () => { + const ledger = mergeCandidates(createEmptyLedger(), [ + { ...base, kind: "decision", subject: "persistence", text: "Persist snapshots with appendEntry()", timestamp: 1 }, + { ...base, kind: "decision", subject: "persistence", text: "Persist snapshots after each turn_end", timestamp: 1 }, + ]); + + const ids = ledger.items.map((item) => item.id); + const active = getActiveItems(ledger, "decision")[0]; + const archived = ledger.items.find((item) => item.text === "Persist snapshots with appendEntry()"); + + assert.equal(new Set(ids).size, ledger.items.length); + assert.equal(active?.text, "Persist snapshots after each turn_end"); + assert.equal(active?.supersedesId, archived?.id); + assert.notEqual(active?.id, active?.supersedesId); +}); + +test("mergeCandidates keeps same-slot same-timestamp snapshots deterministic regardless of input order", () => { + const appendEntryCandidate = { + ...base, + kind: "decision" as const, + subject: "persistence", + text: "Persist snapshots with appendEntry()", + timestamp: 1, + }; + const turnEndCandidate = { + ...base, + kind: "decision" as const, + subject: "persistence", + text: "Persist snapshots after each turn_end", + timestamp: 1, + }; + + const forward = mergeCandidates(createEmptyLedger(), [appendEntryCandidate, turnEndCandidate]); + const reversed = mergeCandidates(createEmptyLedger(), [turnEndCandidate, appendEntryCandidate]); + + assert.deepEqual(forward, reversed); + assert.deepEqual(forward.items, [ + { + ...turnEndCandidate, + id: "decision:branch:persistence:1", + freshness: 1, + active: true, + supersedesId: "decision:branch:persistence:1:2", + }, + { + ...appendEntryCandidate, + id: "decision:branch:persistence:1:2", + freshness: 1, + active: false, + supersedesId: undefined, + }, + ]); +}); + +test("session-scoped memory can coexist with branch-scoped memory for the same kind", () => { + const ledger = mergeCandidates(createEmptyLedger(), [ + { + kind: "constraint", + subject: "llm-tools", + text: "Do not add new LLM-facing tools in the MVP", + scope: "session", + sourceEntryId: "u1", + sourceType: "user", + timestamp: 1, + confidence: 1, + }, + { + kind: "constraint", + subject: "branch-policy", + text: "Keep branch A experimental", + scope: "branch", + sourceEntryId: "u2", + sourceType: "user", + timestamp: 2, + confidence: 0.8, + }, + ]); + + assert.equal(getActiveItems(ledger, "constraint").length, 2); +}); diff --git a/src/ledger.ts b/src/ledger.ts new file mode 100644 index 0000000..2ab41fe --- /dev/null +++ b/src/ledger.ts @@ -0,0 +1,196 @@ +export type MemoryKind = "goal" | "constraint" | "decision" | "activeTask" | "openQuestion" | "relevantFile"; +export type MemoryScope = "branch" | "session"; +export type MemorySourceType = "user" | "assistant" | "toolResult" | "compaction" | "branchSummary"; + +export interface MemoryCandidate { + kind: MemoryKind; + subject: string; + text: string; + scope: MemoryScope; + sourceEntryId: string; + sourceType: MemorySourceType; + timestamp: number; + confidence: number; +} + +export interface MemoryItem extends MemoryCandidate { + id: string; + freshness: number; + active: boolean; + supersedesId?: string; +} + +export interface LedgerState { + items: MemoryItem[]; + rollingSummary: string; +} + +type MemorySlot = Pick; + +export function createEmptyLedger(): LedgerState { + return { items: [], rollingSummary: "" }; +} + +function createId(candidate: MemoryCandidate): string { + return `${candidate.kind}:${candidate.scope}:${candidate.subject}:${candidate.timestamp}`; +} + +function ensureUniqueId(items: Pick[], baseId: string): string { + let id = baseId; + let suffix = 2; + + while (items.some((item) => item.id === id)) { + id = `${baseId}:${suffix}`; + suffix += 1; + } + + return id; +} + +function sameSlot(left: MemorySlot, right: MemorySlot) { + return left.kind === right.kind && left.scope === right.scope && left.subject === right.subject; +} + +function createSlotKey(slot: MemorySlot): string { + return `${slot.kind}\u0000${slot.scope}\u0000${slot.subject}`; +} + +function compareStrings(left: string, right: string): number { + if (left === right) { + return 0; + } + + return left < right ? -1 : 1; +} + +function compareSameTimestampCandidates( + left: Pick, + right: Pick +): number { + // Exact-timestamp ties should resolve the same way no matter which candidate is processed first. + const textComparison = compareStrings(left.text, right.text); + if (textComparison !== 0) { + return textComparison; + } + + const sourceTypeComparison = compareStrings(left.sourceType, right.sourceType); + if (sourceTypeComparison !== 0) { + return sourceTypeComparison; + } + + const sourceEntryIdComparison = compareStrings(left.sourceEntryId, right.sourceEntryId); + if (sourceEntryIdComparison !== 0) { + return sourceEntryIdComparison; + } + + if (left.confidence !== right.confidence) { + return left.confidence > right.confidence ? -1 : 1; + } + + return 0; +} + +function candidateSupersedesPrevious(candidate: MemoryCandidate, previous?: MemoryItem): boolean { + if (!previous) { + return true; + } + + if (candidate.timestamp !== previous.timestamp) { + return candidate.timestamp > previous.timestamp; + } + + return compareSameTimestampCandidates(candidate, previous) < 0; +} + +function compareSlotItems(left: MemoryItem, right: MemoryItem): number { + if (left.timestamp !== right.timestamp) { + return right.timestamp - left.timestamp; + } + + return compareSameTimestampCandidates(left, right); +} + +function normalizeSlotItems(items: MemoryItem[], slot: MemorySlot): MemoryItem[] { + const slotIndices: number[] = []; + const slotItems: MemoryItem[] = []; + + items.forEach((item, index) => { + if (!sameSlot(item, slot)) { + return; + } + + slotIndices.push(index); + slotItems.push(item); + }); + + if (slotItems.length <= 1) { + return items; + } + + const sortedSlotItems = [...slotItems].sort(compareSlotItems); + const slotIds = new Map(); + const sortedSlotItemsWithIds = sortedSlotItems.map((item) => { + const baseId = createId(item); + const nextSlotIdCount = (slotIds.get(baseId) ?? 0) + 1; + slotIds.set(baseId, nextSlotIdCount); + + return { + item, + id: nextSlotIdCount === 1 ? baseId : `${baseId}:${nextSlotIdCount}`, + }; + }); + + const normalizedSlotItems = sortedSlotItemsWithIds.map(({ item, id }, index) => ({ + ...item, + id, + freshness: index === 0 ? item.timestamp : sortedSlotItemsWithIds[index - 1]!.item.timestamp, + active: index === 0, + supersedesId: sortedSlotItemsWithIds[index + 1]?.id, + })); + + const normalizedItems = [...items]; + slotIndices.forEach((slotIndex, index) => { + normalizedItems[slotIndex] = normalizedSlotItems[index]!; + }); + + return normalizedItems; +} + +export function mergeCandidates(state: LedgerState, candidates: MemoryCandidate[]): LedgerState { + let items = [...state.items]; + const affectedSlots = new Map(); + + for (const candidate of candidates) { + const previousIndex = items.findIndex((item) => item.active && sameSlot(item, candidate)); + const previous = previousIndex === -1 ? undefined : items[previousIndex]; + const candidateIsNewest = candidateSupersedesPrevious(candidate, previous); + + if (previous && candidateIsNewest) { + items[previousIndex] = { ...previous, active: false, freshness: candidate.timestamp }; + } + + items.push({ + ...candidate, + id: ensureUniqueId(items, createId(candidate)), + freshness: candidate.timestamp, + active: candidateIsNewest, + supersedesId: candidateIsNewest ? previous?.id : undefined, + }); + + affectedSlots.set(createSlotKey(candidate), { + kind: candidate.kind, + scope: candidate.scope, + subject: candidate.subject, + }); + } + + for (const slot of affectedSlots.values()) { + items = normalizeSlotItems(items, slot); + } + + return { ...state, items }; +} + +export function getActiveItems(state: LedgerState, kind?: MemoryKind): MemoryItem[] { + return state.items.filter((item) => item.active && (kind ? item.kind === kind : true)); +} diff --git a/src/package-manifest.test.ts b/src/package-manifest.test.ts new file mode 100644 index 0000000..abb3efa --- /dev/null +++ b/src/package-manifest.test.ts @@ -0,0 +1,27 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { existsSync, readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const pkg = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8")); + +test("package.json exposes pi-context-manager as a standalone pi package", () => { + assert.equal(pkg.name, "pi-context-manager"); + assert.equal(pkg.type, "module"); + assert.ok(Array.isArray(pkg.keywords)); + assert.ok(pkg.keywords.includes("pi-package")); + assert.deepEqual(pkg.pi, { + extensions: ["./index.ts"], + }); + + assert.equal(pkg.peerDependencies["@mariozechner/pi-agent-core"], "*"); + assert.equal(pkg.peerDependencies["@mariozechner/pi-coding-agent"], "*"); + assert.deepEqual(pkg.dependencies ?? {}, {}); + assert.equal(pkg.bundledDependencies, undefined); + assert.deepEqual(pkg.files, ["index.ts", "src"]); + + assert.ok(existsSync(resolve(packageRoot, "index.ts"))); + assert.ok(existsSync(resolve(packageRoot, "src/runtime.ts"))); +}); diff --git a/src/packet.test.ts b/src/packet.test.ts new file mode 100644 index 0000000..e50bd5c --- /dev/null +++ b/src/packet.test.ts @@ -0,0 +1,130 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { resolvePolicy } from "./config.ts"; +import { buildContextPacket } from "./packet.ts"; +import { createEmptyLedger, mergeCandidates, type MemoryCandidate } from "./ledger.ts"; + +const baseCandidate: Omit = { + scope: "session", + sourceEntryId: "seed", + sourceType: "user", + timestamp: 1, + confidence: 1, +}; + +function estimateTokens(text: string) { + return Math.ceil(text.length / 4); +} + +function memory(candidate: Pick & Partial>): MemoryCandidate { + return { + ...baseCandidate, + ...candidate, + sourceEntryId: candidate.sourceEntryId ?? candidate.subject, + }; +} + +function buildPolicy(packetTokenCap: number) { + return { + ...resolvePolicy({ mode: "balanced", contextWindow: 200_000 }), + packetTokenCap, + }; +} + +test("buildContextPacket keeps top-ranked facts from a section when the cap is tight", () => { + const expected = [ + "## Active goal", + "- Keep packets compact.", + "", + "## Constraints", + "- Preserve the highest-priority constraint.", + "", + "## Key decisions", + "- Render selected sections in stable order.", + ].join("\n"); + const policy = buildPolicy(estimateTokens(expected)); + const ledger = mergeCandidates(createEmptyLedger(), [ + memory({ kind: "goal", subject: "goal", text: "Keep packets compact." }), + memory({ kind: "constraint", subject: "constraint-a", text: "Preserve the highest-priority constraint.", confidence: 1, timestamp: 3 }), + memory({ + kind: "constraint", + subject: "constraint-b", + text: "Avoid dropping every constraint just because one extra bullet is too large for a tight packet cap.", + confidence: 0.6, + timestamp: 2, + }), + memory({ + kind: "decision", + subject: "decision-a", + text: "Render selected sections in stable order.", + confidence: 0.9, + timestamp: 4, + sourceType: "assistant", + }), + ]); + + const packet = buildContextPacket(ledger, policy); + + assert.equal(packet.text, expected); + assert.equal(packet.estimatedTokens, policy.packetTokenCap); +}); + +test("buildContextPacket uses cross-kind weights when only one lower-priority section can fit", () => { + const expected = [ + "## Active goal", + "- Keep the agent moving.", + "", + "## Current task", + "- Fix packet trimming.", + ].join("\n"); + const policy = buildPolicy(estimateTokens(expected)); + const ledger = mergeCandidates(createEmptyLedger(), [ + memory({ kind: "goal", subject: "goal", text: "Keep the agent moving." }), + memory({ + kind: "decision", + subject: "decision-a", + text: "Keep logs concise.", + confidence: 1, + timestamp: 2, + sourceType: "assistant", + }), + memory({ + kind: "activeTask", + subject: "task-a", + text: "Fix packet trimming.", + confidence: 1, + timestamp: 2, + sourceType: "assistant", + }), + ]); + + const packet = buildContextPacket(ledger, policy); + + assert.equal(packet.text, expected); + assert.equal(packet.estimatedTokens, policy.packetTokenCap); +}); + +test("buildContextPacket keeps a goal ahead of newer low-priority facts at realistic timestamp scales", () => { + const expected = [ + "## Active goal", + "- Keep the agent on track.", + ].join("\n"); + const policy = buildPolicy(estimateTokens(expected)); + const ledger = mergeCandidates(createEmptyLedger(), [ + memory({ kind: "goal", subject: "goal", text: "Keep the agent on track.", timestamp: 1_000_000 }), + memory({ + kind: "relevantFile", + subject: "runtime-file", + text: "src/runtime.ts", + timestamp: 10_000_000, + confidence: 1, + sourceType: "assistant", + scope: "branch", + }), + ]); + + const packet = buildContextPacket(ledger, policy); + + assert.equal(packet.text, expected); + assert.equal(packet.estimatedTokens, policy.packetTokenCap); +}); diff --git a/src/packet.ts b/src/packet.ts new file mode 100644 index 0000000..817ed17 --- /dev/null +++ b/src/packet.ts @@ -0,0 +1,91 @@ +import type { Policy } from "./config.ts"; +import { getActiveItems, type LedgerState, type MemoryItem, type MemoryKind } from "./ledger.ts"; + +const SECTION_ORDER: Array<{ kind: MemoryKind; title: string }> = [ + { kind: "goal", title: "Active goal" }, + { kind: "constraint", title: "Constraints" }, + { kind: "decision", title: "Key decisions" }, + { kind: "activeTask", title: "Current task" }, + { kind: "relevantFile", title: "Relevant files" }, + { kind: "openQuestion", title: "Open questions / blockers" }, +]; + +const WEIGHTS: Record = { + goal: 100, + constraint: 90, + decision: 80, + activeTask: 85, + relevantFile: 60, + openQuestion: 70, +}; + +const SECTION_INDEX = new Map(SECTION_ORDER.map((section, index) => [section.kind, index])); + +function estimateTokens(text: string): number { + return Math.ceil(text.length / 4); +} + +function compareByPriority(left: MemoryItem, right: MemoryItem): number { + const weightDifference = WEIGHTS[right.kind] - WEIGHTS[left.kind]; + if (weightDifference !== 0) { + return weightDifference; + } + + if (left.confidence !== right.confidence) { + return right.confidence - left.confidence; + } + + const sectionDifference = SECTION_INDEX.get(left.kind)! - SECTION_INDEX.get(right.kind)!; + if (sectionDifference !== 0) { + return sectionDifference; + } + + if (left.freshness !== right.freshness) { + return right.freshness - left.freshness; + } + + return left.id.localeCompare(right.id); +} + +function sortByPriority(items: MemoryItem[]) { + return [...items].sort(compareByPriority); +} + +function renderPacket(itemsByKind: Map, selectedIds: Set) { + const lines: string[] = []; + + for (const section of SECTION_ORDER) { + const items = itemsByKind.get(section.kind)?.filter((item) => selectedIds.has(item.id)) ?? []; + if (items.length === 0) continue; + + lines.push(`## ${section.title}`, ...items.map((item) => `- ${item.text}`), ""); + } + + return lines.join("\n").trim(); +} + +export function buildContextPacket(ledger: LedgerState, policy: Policy): { text: string; estimatedTokens: number } { + const itemsByKind = new Map(); + for (const section of SECTION_ORDER) { + itemsByKind.set(section.kind, sortByPriority(getActiveItems(ledger, section.kind))); + } + + const candidates = sortByPriority(getActiveItems(ledger)); + const selectedIds = new Set(); + let text = ""; + + for (const item of candidates) { + const tentativeSelectedIds = new Set(selectedIds); + tentativeSelectedIds.add(item.id); + + const tentative = renderPacket(itemsByKind, tentativeSelectedIds); + if (estimateTokens(tentative) > policy.packetTokenCap) { + continue; + } + + selectedIds.add(item.id); + text = tentative; + } + + return { text, estimatedTokens: estimateTokens(text) }; +} diff --git a/src/persist.test.ts b/src/persist.test.ts new file mode 100644 index 0000000..4475c74 --- /dev/null +++ b/src/persist.test.ts @@ -0,0 +1,67 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { deserializeLatestSnapshot, SNAPSHOT_ENTRY_TYPE, serializeSnapshot } from "./persist.ts"; + +function createSnapshot(lastZone: "green" | "yellow" | "red" | "compact", lastCompactionSummary: string) { + return serializeSnapshot({ + mode: "balanced", + lastZone, + lastCompactionSummary, + lastBranchSummary: undefined, + ledger: { + items: [ + { + id: `goal:session:root:${lastCompactionSummary}`, + kind: "goal", + subject: "root", + text: `Goal ${lastCompactionSummary}`, + scope: "session", + sourceEntryId: "u1", + sourceType: "user", + timestamp: 1, + confidence: 1, + freshness: 1, + active: true, + supersedesId: undefined, + }, + ], + rollingSummary: "summary", + }, + }); +} + +test("deserializeLatestSnapshot restores the newest matching custom entry", () => { + const first = createSnapshot("yellow", "old"); + const second = createSnapshot("red", "new"); + + const restored = deserializeLatestSnapshot([ + { type: "custom", customType: SNAPSHOT_ENTRY_TYPE, data: first }, + { type: "custom", customType: SNAPSHOT_ENTRY_TYPE, data: second }, + ]); + + assert.equal(restored?.lastZone, "red"); + assert.equal(restored?.lastCompactionSummary, "new"); +}); + +test("deserializeLatestSnapshot skips malformed newer entries and clones the accepted snapshot", () => { + const valid = createSnapshot("yellow", "valid"); + + const restored = deserializeLatestSnapshot([ + { type: "custom", customType: SNAPSHOT_ENTRY_TYPE, data: valid }, + { + type: "custom", + customType: SNAPSHOT_ENTRY_TYPE, + data: { + mode: "balanced", + lastZone: "red", + ledger: { items: "not-an-array", rollingSummary: "broken" }, + }, + }, + ]); + + assert.deepEqual(restored, valid); + assert.notStrictEqual(restored, valid); + + restored!.ledger.items[0]!.text = "mutated"; + assert.equal(valid.ledger.items[0]!.text, "Goal valid"); +}); diff --git a/src/persist.ts b/src/persist.ts new file mode 100644 index 0000000..5cac54e --- /dev/null +++ b/src/persist.ts @@ -0,0 +1,142 @@ +import type { ContextMode, ContextZone } from "./config.ts"; +import type { LedgerState, MemoryItem, MemoryKind, MemoryScope, MemorySourceType } from "./ledger.ts"; + +export const SNAPSHOT_ENTRY_TYPE = "context-manager.snapshot"; + +export interface RuntimeSnapshot { + mode: ContextMode; + lastZone: ContextZone; + lastObservedTokens?: number; + lastCompactionSummary?: string; + lastBranchSummary?: string; + ledger: LedgerState; +} + +const CONTEXT_MODES = new Set(["conservative", "balanced", "aggressive"]); +const CONTEXT_ZONES = new Set(["green", "yellow", "red", "compact"]); +const MEMORY_KINDS = new Set(["goal", "constraint", "decision", "activeTask", "openQuestion", "relevantFile"]); +const MEMORY_SCOPES = new Set(["branch", "session"]); +const MEMORY_SOURCE_TYPES = new Set(["user", "assistant", "toolResult", "compaction", "branchSummary"]); + +export function serializeSnapshot(snapshot: RuntimeSnapshot): RuntimeSnapshot { + return structuredClone(snapshot); +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function isFiniteNumber(value: unknown): value is number { + return typeof value === "number" && Number.isFinite(value); +} + +function isOptionalString(value: unknown): value is string | undefined { + return value === undefined || typeof value === "string"; +} + +function parseMemoryItem(value: unknown): MemoryItem | undefined { + if (!isRecord(value)) { + return undefined; + } + + if ( + typeof value.id !== "string" || + !MEMORY_KINDS.has(value.kind as MemoryKind) || + typeof value.subject !== "string" || + typeof value.text !== "string" || + !MEMORY_SCOPES.has(value.scope as MemoryScope) || + typeof value.sourceEntryId !== "string" || + !MEMORY_SOURCE_TYPES.has(value.sourceType as MemorySourceType) || + !isFiniteNumber(value.timestamp) || + !isFiniteNumber(value.confidence) || + !isFiniteNumber(value.freshness) || + typeof value.active !== "boolean" || + !isOptionalString(value.supersedesId) + ) { + return undefined; + } + + return { + id: value.id, + kind: value.kind as MemoryKind, + subject: value.subject, + text: value.text, + scope: value.scope as MemoryScope, + sourceEntryId: value.sourceEntryId, + sourceType: value.sourceType as MemorySourceType, + timestamp: value.timestamp, + confidence: value.confidence, + freshness: value.freshness, + active: value.active, + supersedesId: value.supersedesId, + }; +} + +function parseLedgerState(value: unknown): LedgerState | undefined { + if (!isRecord(value) || !Array.isArray(value.items) || typeof value.rollingSummary !== "string") { + return undefined; + } + + const items: MemoryItem[] = []; + for (const item of value.items) { + const parsed = parseMemoryItem(item); + if (!parsed) { + return undefined; + } + + items.push(parsed); + } + + return { + items, + rollingSummary: value.rollingSummary, + }; +} + +function parseRuntimeSnapshot(value: unknown): RuntimeSnapshot | undefined { + if ( + !isRecord(value) || + !CONTEXT_MODES.has(value.mode as ContextMode) || + !CONTEXT_ZONES.has(value.lastZone as ContextZone) || + !isOptionalString(value.lastCompactionSummary) || + !isOptionalString(value.lastBranchSummary) || + (value.lastObservedTokens !== undefined && !isFiniteNumber(value.lastObservedTokens)) + ) { + return undefined; + } + + const ledger = parseLedgerState(value.ledger); + if (!ledger) { + return undefined; + } + + const snapshot: RuntimeSnapshot = { + mode: value.mode as ContextMode, + lastZone: value.lastZone as ContextZone, + lastCompactionSummary: value.lastCompactionSummary, + lastBranchSummary: value.lastBranchSummary, + ledger, + }; + + if (value.lastObservedTokens !== undefined) { + snapshot.lastObservedTokens = value.lastObservedTokens; + } + + return snapshot; +} + +export function deserializeLatestSnapshot(entries: Array<{ type: string; customType?: string; data?: unknown }>): RuntimeSnapshot | undefined { + for (let index = entries.length - 1; index >= 0; index -= 1) { + const entry = entries[index]!; + if (entry.type !== "custom" || entry.customType !== SNAPSHOT_ENTRY_TYPE) { + continue; + } + + const snapshot = parseRuntimeSnapshot(entry.data); + if (snapshot) { + return snapshot; + } + } + + return undefined; +} diff --git a/src/prune.test.ts b/src/prune.test.ts new file mode 100644 index 0000000..747a415 --- /dev/null +++ b/src/prune.test.ts @@ -0,0 +1,131 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { resolvePolicy } from "./config.ts"; +import { pruneContextMessages } from "./prune.ts"; + +const bulky = "line\n".repeat(300); +const boundaryBulky = "boundary\n".repeat(300); +const thresholdWithTrailingNewline = "threshold\n".repeat(150); + +function buildPolicy(recentUserTurns = 4) { + return { + ...resolvePolicy({ mode: "balanced", contextWindow: 200_000 }), + recentUserTurns, + }; +} + +test("pruneContextMessages replaces old bulky tool results with distilled summaries instead of deleting them", () => { + const policy = buildPolicy(2); + const bulkyFailure = [ + "Build failed while compiling focus parser", + "Error: missing export createFocusMatcher from ./summary-focus.ts", + ...Array.from({ length: 220 }, () => "stack frame"), + ].join("\n"); + const messages = [ + { role: "user", content: "turn 1" }, + { role: "toolResult", toolName: "bash", content: bulkyFailure }, + { role: "assistant", content: "observed turn 1" }, + { role: "user", content: "turn 2" }, + { role: "assistant", content: "observed turn 2" }, + { role: "user", content: "turn 3" }, + ]; + + const pruned = pruneContextMessages(messages, policy); + + const distilled = pruned.find((message) => message.role === "toolResult"); + assert.ok(distilled); + assert.match(distilled!.content, /missing export createFocusMatcher/); + assert.doesNotMatch(distilled!.content, /stack frame\nstack frame\nstack frame/); +}); + +test("aggressive mode distills an older bulky tool result sooner than conservative mode", () => { + const conservative = resolvePolicy({ mode: "conservative", contextWindow: 200_000 }); + const aggressive = resolvePolicy({ mode: "aggressive", contextWindow: 200_000 }); + const messages = [ + { role: "user", content: "turn 1" }, + { role: "toolResult", toolName: "read", content: bulky }, + { role: "assistant", content: "after turn 1" }, + { role: "user", content: "turn 2" }, + { role: "assistant", content: "after turn 2" }, + { role: "user", content: "turn 3" }, + { role: "assistant", content: "after turn 3" }, + { role: "user", content: "turn 4" }, + ]; + + const conservativePruned = pruneContextMessages(messages, conservative); + const aggressivePruned = pruneContextMessages(messages, aggressive); + + assert.equal(conservativePruned[1]?.content, bulky); + assert.notEqual(aggressivePruned[1]?.content, bulky); + assert.match(aggressivePruned[1]?.content ?? "", /^\[distilled read output\]/); +}); + +test("pruneContextMessages keeps recent bulky tool results inside the recent-turn window", () => { + const policy = buildPolicy(2); + const messages = [ + { role: "user", content: "turn 1" }, + { role: "assistant", content: "observed turn 1" }, + { role: "user", content: "turn 2" }, + { role: "toolResult", toolName: "read", content: bulky }, + { role: "assistant", content: "observed turn 2" }, + { role: "user", content: "turn 3" }, + ]; + + const pruned = pruneContextMessages(messages, policy); + + assert.deepEqual(pruned, messages); +}); + +test("pruneContextMessages keeps old non-bulky tool results outside the recent-turn window", () => { + const policy = buildPolicy(2); + const messages = [ + { role: "user", content: "turn 1" }, + { role: "toolResult", toolName: "read", content: "short output" }, + { role: "assistant", content: "observed turn 1" }, + { role: "user", content: "turn 2" }, + { role: "assistant", content: "observed turn 2" }, + { role: "user", content: "turn 3" }, + ]; + + const pruned = pruneContextMessages(messages, policy); + + assert.deepEqual(pruned, messages); +}); + +test("pruneContextMessages keeps exactly-150-line tool results with a trailing newline", () => { + const policy = buildPolicy(2); + const messages = [ + { role: "user", content: "turn 1" }, + { role: "toolResult", toolName: "read", content: thresholdWithTrailingNewline }, + { role: "assistant", content: "after threshold output" }, + { role: "user", content: "turn 2" }, + { role: "assistant", content: "after turn 2" }, + { role: "user", content: "turn 3" }, + ]; + + const pruned = pruneContextMessages(messages, policy); + + assert.deepEqual(pruned, messages); +}); + +test("pruneContextMessages honors the recent-user-turn boundary", () => { + const policy = buildPolicy(2); + const messages = [ + { role: "user", content: "turn 1" }, + { role: "toolResult", toolName: "read", content: bulky }, + { role: "assistant", content: "after turn 1" }, + { role: "user", content: "turn 2" }, + { role: "toolResult", toolName: "read", content: boundaryBulky }, + { role: "assistant", content: "after turn 2" }, + { role: "user", content: "turn 3" }, + ]; + + const pruned = pruneContextMessages(messages, policy); + + assert.equal(pruned[1]?.role, "toolResult"); + assert.match(pruned[1]?.content ?? "", /^\[distilled read output\]/); + assert.deepEqual( + pruned.map((message) => message.content), + ["turn 1", pruned[1]!.content, "after turn 1", "turn 2", boundaryBulky, "after turn 2", "turn 3"] + ); +}); diff --git a/src/prune.ts b/src/prune.ts new file mode 100644 index 0000000..768243e --- /dev/null +++ b/src/prune.ts @@ -0,0 +1,54 @@ +import type { Policy } from "./config.ts"; +import { distillToolResult } from "./distill.ts"; + +export interface ContextMessage { + role: string; + content: string; + toolName?: string; + original?: unknown; + distilled?: boolean; +} + +function isBulky(content: string, policy: Policy) { + const bytes = Buffer.byteLength(content, "utf8"); + const parts = content.split("\n"); + const lines = content.endsWith("\n") ? parts.length - 1 : parts.length; + return bytes > policy.bulkyBytes || lines > policy.bulkyLines; +} + +export function pruneContextMessages(messages: ContextMessage[], policy: Policy): ContextMessage[] { + let seenUserTurns = 0; + const keep = new Set(); + + for (let index = messages.length - 1; index >= 0; index -= 1) { + const message = messages[index]!; + keep.add(index); + if (message.role === "user") { + seenUserTurns += 1; + if (seenUserTurns >= policy.recentUserTurns) { + break; + } + } + } + + const next: ContextMessage[] = []; + for (const [index, message] of messages.entries()) { + if (keep.has(index) || message.role !== "toolResult" || !isBulky(message.content, policy)) { + next.push(message); + continue; + } + + const distilled = distillToolResult({ toolName: message.toolName, content: message.content }); + if (!distilled) { + continue; + } + + next.push({ + ...message, + content: distilled, + distilled: true, + }); + } + + return next; +} diff --git a/src/runtime.test.ts b/src/runtime.test.ts new file mode 100644 index 0000000..7fe3a52 --- /dev/null +++ b/src/runtime.test.ts @@ -0,0 +1,178 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { SNAPSHOT_ENTRY_TYPE, deserializeLatestSnapshot } from "./persist.ts"; +import { createContextManagerRuntime } from "./runtime.ts"; + +test("runtime ingests transcript slices and updates pressure state", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + + runtime.ingest({ entryId: "u1", role: "user", text: "Goal: Build a pi context manager. Next: wire hooks.", timestamp: 1 }); + runtime.observeTokens(150_000); + + const packet = runtime.buildPacket(); + assert.match(packet.text, /Build a pi context manager/); + assert.equal(runtime.getSnapshot().lastZone, "red"); +}); + +test("runtime keeps the session root goal while allowing later branch-local goals", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + + runtime.ingest({ entryId: "u-root-goal", role: "user", text: "Goal: Ship the context manager extension.", timestamp: 10 }); + runtime.ingest({ entryId: "u-branch-goal", role: "user", text: "Goal: prototype a branch-local tree handoff.", timestamp: 11 }); + + const packet = runtime.buildPacket(); + assert.match(packet.text, /Ship the context manager extension/); + assert.match(packet.text, /prototype a branch-local tree handoff/); + assert.equal( + runtime + .getSnapshot() + .ledger.items.filter((item) => item.active && item.kind === "goal" && item.scope === "session").length, + 1, + ); +}); + +test("recordCompactionSummary and recordBranchSummary update snapshot state and resume output", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + + runtime.ingest({ entryId: "u-artifact-1", role: "user", text: "Goal: Ship the context manager extension.", timestamp: 20 }); + runtime.recordCompactionSummary( + "## Key Decisions\n- Keep summaries deterministic.\n\n## Open questions and blockers\n- Verify /tree replaceInstructions behavior.", + ); + runtime.recordBranchSummary("# Handoff for branch\n\n## Key Decisions\n- Do not leak branch-local goals."); + + const snapshot = runtime.getSnapshot(); + assert.match(snapshot.lastCompactionSummary ?? "", /Keep summaries deterministic/); + assert.match(snapshot.lastBranchSummary ?? "", /Do not leak branch-local goals/); + assert.match(runtime.buildResumePacket(), /Verify \/tree replaceInstructions behavior/); + assert.match(runtime.buildResumePacket(), /Do not leak branch-local goals/); +}); + +test("buildPacket tightens the live packet after pressure reaches the compact zone", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + + runtime.restore({ + mode: "balanced", + lastZone: "green", + ledger: { + rollingSummary: "", + items: [ + { id: "goal:session:root-goal:1", kind: "goal", subject: "root-goal", text: "Ship the context manager extension with deterministic handoffs and predictable branch-boundary behavior.", scope: "session", sourceEntryId: "u1", sourceType: "user", timestamp: 1, confidence: 1, freshness: 1, active: true }, + { id: "constraint:session:must-1:1", kind: "constraint", subject: "must-1", text: "Keep the public API stable while hardening branch-boundary state carryover, fallback summary replay, and resume injection behavior.", scope: "session", sourceEntryId: "u1", sourceType: "user", timestamp: 2, confidence: 0.9, freshness: 2, active: true }, + { id: "decision:branch:decision-1:1", kind: "decision", subject: "decision-1", text: "Persist summary artifacts, replay them after the latest snapshot, and surface them through the next hidden resume packet before normal packet injection resumes.", scope: "branch", sourceEntryId: "a1", sourceType: "assistant", timestamp: 3, confidence: 0.9, freshness: 3, active: true }, + { id: "activeTask:branch:task-1:1", kind: "activeTask", subject: "task-1", text: "Verify mode-dependent pruning, packet tightening under pressure, and snapshot-less branch rehydration without stale handoff leakage.", scope: "branch", sourceEntryId: "a2", sourceType: "assistant", timestamp: 4, confidence: 0.8, freshness: 4, active: true }, + { id: "openQuestion:branch:question-1:1", kind: "openQuestion", subject: "question-1", text: "Confirm whether default pi fallback summaries preserve blockers and active work end to end when custom compaction falls back.", scope: "branch", sourceEntryId: "a3", sourceType: "assistant", timestamp: 5, confidence: 0.8, freshness: 5, active: true }, + { id: "relevantFile:branch:file-1:1", kind: "relevantFile", subject: "file-1", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-1.ts", scope: "branch", sourceEntryId: "t1", sourceType: "toolResult", timestamp: 6, confidence: 0.7, freshness: 6, active: true }, + { id: "relevantFile:branch:file-2:1", kind: "relevantFile", subject: "file-2", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-2.ts", scope: "branch", sourceEntryId: "t2", sourceType: "toolResult", timestamp: 7, confidence: 0.7, freshness: 7, active: true }, + { id: "relevantFile:branch:file-3:1", kind: "relevantFile", subject: "file-3", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-3.ts", scope: "branch", sourceEntryId: "t3", sourceType: "toolResult", timestamp: 8, confidence: 0.7, freshness: 8, active: true }, + { id: "relevantFile:branch:file-4:1", kind: "relevantFile", subject: "file-4", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-4.ts", scope: "branch", sourceEntryId: "t4", sourceType: "toolResult", timestamp: 9, confidence: 0.7, freshness: 9, active: true }, + { id: "relevantFile:branch:file-5:1", kind: "relevantFile", subject: "file-5", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-5.ts", scope: "branch", sourceEntryId: "t5", sourceType: "toolResult", timestamp: 10, confidence: 0.7, freshness: 10, active: true }, + { id: "relevantFile:branch:file-6:1", kind: "relevantFile", subject: "file-6", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-6.ts", scope: "branch", sourceEntryId: "t6", sourceType: "toolResult", timestamp: 11, confidence: 0.7, freshness: 11, active: true }, + { id: "relevantFile:branch:file-7:1", kind: "relevantFile", subject: "file-7", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-7.ts", scope: "branch", sourceEntryId: "t7", sourceType: "toolResult", timestamp: 12, confidence: 0.7, freshness: 12, active: true }, + { id: "relevantFile:branch:file-8:1", kind: "relevantFile", subject: "file-8", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-8.ts", scope: "branch", sourceEntryId: "t8", sourceType: "toolResult", timestamp: 13, confidence: 0.7, freshness: 13, active: true }, + { id: "relevantFile:branch:file-9:1", kind: "relevantFile", subject: "file-9", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-9.ts", scope: "branch", sourceEntryId: "t9", sourceType: "toolResult", timestamp: 14, confidence: 0.7, freshness: 14, active: true }, + { id: "relevantFile:branch:file-10:1", kind: "relevantFile", subject: "file-10", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-10.ts", scope: "branch", sourceEntryId: "t10", sourceType: "toolResult", timestamp: 15, confidence: 0.7, freshness: 15, active: true }, + { id: "relevantFile:branch:file-11:1", kind: "relevantFile", subject: "file-11", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-11.ts", scope: "branch", sourceEntryId: "t11", sourceType: "toolResult", timestamp: 16, confidence: 0.7, freshness: 16, active: true }, + { id: "relevantFile:branch:file-12:1", kind: "relevantFile", subject: "file-12", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-12.ts", scope: "branch", sourceEntryId: "t12", sourceType: "toolResult", timestamp: 17, confidence: 0.7, freshness: 17, active: true }, + { id: "relevantFile:branch:file-13:1", kind: "relevantFile", subject: "file-13", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-13.ts", scope: "branch", sourceEntryId: "t13", sourceType: "toolResult", timestamp: 18, confidence: 0.7, freshness: 18, active: true }, + { id: "relevantFile:branch:file-14:1", kind: "relevantFile", subject: "file-14", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-14.ts", scope: "branch", sourceEntryId: "t14", sourceType: "toolResult", timestamp: 19, confidence: 0.7, freshness: 19, active: true }, + { id: "relevantFile:branch:file-15:1", kind: "relevantFile", subject: "file-15", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-15.ts", scope: "branch", sourceEntryId: "t15", sourceType: "toolResult", timestamp: 20, confidence: 0.7, freshness: 20, active: true }, + { id: "relevantFile:branch:file-16:1", kind: "relevantFile", subject: "file-16", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-16.ts", scope: "branch", sourceEntryId: "t16", sourceType: "toolResult", timestamp: 21, confidence: 0.7, freshness: 21, active: true }, + { id: "relevantFile:branch:file-17:1", kind: "relevantFile", subject: "file-17", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-17.ts", scope: "branch", sourceEntryId: "t17", sourceType: "toolResult", timestamp: 22, confidence: 0.7, freshness: 22, active: true }, + { id: "relevantFile:branch:file-18:1", kind: "relevantFile", subject: "file-18", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-18.ts", scope: "branch", sourceEntryId: "t18", sourceType: "toolResult", timestamp: 23, confidence: 0.7, freshness: 23, active: true }, + { id: "relevantFile:branch:file-19:1", kind: "relevantFile", subject: "file-19", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-19.ts", scope: "branch", sourceEntryId: "t19", sourceType: "toolResult", timestamp: 24, confidence: 0.7, freshness: 24, active: true }, + { id: "relevantFile:branch:file-20:1", kind: "relevantFile", subject: "file-20", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-20.ts", scope: "branch", sourceEntryId: "t20", sourceType: "toolResult", timestamp: 25, confidence: 0.7, freshness: 25, active: true }, + { id: "relevantFile:branch:file-21:1", kind: "relevantFile", subject: "file-21", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-21.ts", scope: "branch", sourceEntryId: "t21", sourceType: "toolResult", timestamp: 26, confidence: 0.7, freshness: 26, active: true }, + { id: "relevantFile:branch:file-22:1", kind: "relevantFile", subject: "file-22", text: "src/context-manager/very/long/path/for/runtime/index-and-hook-wiring-reference-file-22.ts", scope: "branch", sourceEntryId: "t22", sourceType: "toolResult", timestamp: 27, confidence: 0.7, freshness: 27, active: true }, + ], + }, + }); + + const before = runtime.buildPacket(); + runtime.observeTokens(170_000); + const after = runtime.buildPacket(); + + assert.ok(after.estimatedTokens < before.estimatedTokens); +}); + +test("runtime recomputes lastZone when setContextWindow and setMode change policy", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + + runtime.observeTokens(150_000); + assert.equal(runtime.getSnapshot().lastZone, "red"); + + runtime.setContextWindow(300_000); + assert.equal(runtime.getSnapshot().lastZone, "green"); + + runtime.setMode("aggressive"); + assert.equal(runtime.getSnapshot().lastZone, "yellow"); +}); + +test("restore recomputes lastZone against the receiving runtime policy", () => { + const source = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + source.observeTokens(150_000); + + const target = createContextManagerRuntime({ mode: "balanced", contextWindow: 500_000 }); + target.restore(source.getSnapshot()); + + assert.equal(target.getSnapshot().lastZone, "green"); +}); + +test("restore resets legacy lastZone when the snapshot lacks lastObservedTokens", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 500_000 }); + + runtime.restore({ + mode: "balanced", + lastZone: "red", + ledger: { items: [], rollingSummary: "" }, + }); + + const restored = runtime.getSnapshot(); + assert.equal(restored.lastZone, "green"); + assert.equal(restored.lastObservedTokens, undefined); +}); + +test("legacy snapshot deserialization plus restore clears stale lastZone", () => { + const snapshot = deserializeLatestSnapshot([ + { + type: "custom", + customType: SNAPSHOT_ENTRY_TYPE, + data: { + mode: "balanced", + lastZone: "red", + ledger: { items: [], rollingSummary: "" }, + }, + }, + ]); + + assert.ok(snapshot); + + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 500_000 }); + runtime.restore(snapshot); + + assert.equal(runtime.getSnapshot().lastZone, "green"); +}); + +test("getPolicy returns a clone and restore detaches from external snapshot objects", () => { + const runtime = createContextManagerRuntime({ mode: "balanced", contextWindow: 200_000 }); + + const policy = runtime.getPolicy(); + policy.packetTokenCap = 1; + policy.redAtTokens = 1; + + const currentPolicy = runtime.getPolicy(); + assert.equal(currentPolicy.packetTokenCap, 1_200); + assert.equal(currentPolicy.redAtTokens, 140_000); + + const snapshot = runtime.getSnapshot(); + snapshot.mode = "aggressive"; + snapshot.ledger.rollingSummary = "before restore"; + + runtime.restore(snapshot); + + snapshot.mode = "conservative"; + snapshot.ledger.rollingSummary = "mutated after restore"; + + const restored = runtime.getSnapshot(); + assert.equal(restored.mode, "aggressive"); + assert.equal(restored.ledger.rollingSummary, "before restore"); +}); diff --git a/src/runtime.ts b/src/runtime.ts new file mode 100644 index 0000000..9a4ef50 --- /dev/null +++ b/src/runtime.ts @@ -0,0 +1,127 @@ +import { adjustPolicyForZone, resolvePolicy, zoneForTokens, type ContextMode, type Policy } from "./config.ts"; +import { extractCandidates, type TranscriptSlice } from "./extract.ts"; +import { createEmptyLedger, getActiveItems, mergeCandidates } from "./ledger.ts"; +import { buildContextPacket } from "./packet.ts"; +import { buildBranchSummary, buildCompactionSummary, buildResumePacket as renderResumePacket } from "./summaries.ts"; +import type { RuntimeSnapshot } from "./persist.ts"; + +function syncSnapshotZone(snapshot: RuntimeSnapshot, policy: Policy): RuntimeSnapshot { + if (snapshot.lastObservedTokens === undefined) { + return snapshot.lastZone === "green" + ? snapshot + : { + ...snapshot, + lastZone: "green", + }; + } + + return { + ...snapshot, + lastZone: zoneForTokens(snapshot.lastObservedTokens, policy), + }; +} + +export function createContextManagerRuntime(input: { mode?: ContextMode; contextWindow: number }) { + let contextWindow = input.contextWindow; + let policy = resolvePolicy({ mode: input.mode ?? "balanced", contextWindow }); + let snapshot: RuntimeSnapshot = { + mode: policy.mode, + lastZone: "green", + ledger: createEmptyLedger(), + }; + + function applyPolicy(nextPolicy: Policy) { + policy = nextPolicy; + snapshot = syncSnapshotZone(snapshot, policy); + } + + function hasSessionGoal() { + return getActiveItems(snapshot.ledger, "goal").some((item) => item.scope === "session" && item.subject === "root-goal"); + } + + function ingest(slice: TranscriptSlice) { + snapshot = { + ...snapshot, + ledger: mergeCandidates(snapshot.ledger, extractCandidates(slice, { hasSessionGoal: hasSessionGoal() })), + }; + } + + function observeTokens(tokens: number) { + snapshot = { + ...snapshot, + lastObservedTokens: tokens, + lastZone: zoneForTokens(tokens, policy), + }; + } + + function buildPacket() { + return buildContextPacket(snapshot.ledger, adjustPolicyForZone(policy, snapshot.lastZone)); + } + + function mergeArtifact(role: "compaction" | "branchSummary", text: string, entryId: string, timestamp: number) { + snapshot = { + ...snapshot, + lastCompactionSummary: role === "compaction" ? text : snapshot.lastCompactionSummary, + lastBranchSummary: role === "branchSummary" ? text : snapshot.lastBranchSummary, + ledger: mergeCandidates(snapshot.ledger, extractCandidates({ entryId, role, text, timestamp }, { hasSessionGoal: hasSessionGoal() })), + }; + } + + function recordCompactionSummary(text: string, entryId = `compaction-${Date.now()}`, timestamp = Date.now()) { + mergeArtifact("compaction", text, entryId, timestamp); + } + + function recordBranchSummary(text: string, entryId = `branch-${Date.now()}`, timestamp = Date.now()) { + mergeArtifact("branchSummary", text, entryId, timestamp); + } + + function buildResumePacket() { + const lines: string[] = []; + + if (snapshot.lastCompactionSummary) { + lines.push("## Latest compaction handoff", snapshot.lastCompactionSummary, ""); + } + + if (snapshot.lastBranchSummary) { + lines.push("## Latest branch handoff", snapshot.lastBranchSummary, ""); + } + + const livePacket = renderResumePacket(snapshot.ledger); + if (livePacket) { + lines.push(livePacket); + } + + return lines.join("\n").trim(); + } + + function setContextWindow(nextContextWindow: number) { + contextWindow = Math.max(nextContextWindow, 50_000); + applyPolicy(resolvePolicy({ mode: snapshot.mode, contextWindow })); + } + + function setMode(mode: ContextMode) { + snapshot = { ...snapshot, mode }; + applyPolicy(resolvePolicy({ mode, contextWindow })); + } + + function restore(next: RuntimeSnapshot) { + snapshot = structuredClone(next); + applyPolicy(resolvePolicy({ mode: snapshot.mode, contextWindow })); + } + + return { + ingest, + observeTokens, + buildPacket, + buildCompactionSummary: () => buildCompactionSummary(snapshot.ledger), + buildBranchSummary: (label: string) => buildBranchSummary(snapshot.ledger, label), + buildResumePacket, + recordCompactionSummary, + recordBranchSummary, + setContextWindow, + setMode, + getPolicy: () => structuredClone(policy), + getSnapshot: () => structuredClone(snapshot), + restore, + }; +} diff --git a/src/summaries.test.ts b/src/summaries.test.ts new file mode 100644 index 0000000..ee76a5f --- /dev/null +++ b/src/summaries.test.ts @@ -0,0 +1,139 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createEmptyLedger, mergeCandidates } from "./ledger.ts"; +import { + buildBranchSummary, + buildBranchSummaryFromEntries, + buildCompactionSummary, + buildCompactionSummaryFromPreparation, + buildResumePacket, +} from "./summaries.ts"; + +const ledger = mergeCandidates(createEmptyLedger(), [ + { kind: "goal", subject: "root-goal", text: "Build a pi context manager", scope: "session", sourceEntryId: "u1", sourceType: "user", timestamp: 1, confidence: 1 }, + { kind: "constraint", subject: "must-u1-0", text: "Must adapt to the active model context window.", scope: "session", sourceEntryId: "u1", sourceType: "user", timestamp: 1, confidence: 0.9 }, + { kind: "decision", subject: "decision-a1-0", text: "Keep the MVP quiet.", scope: "branch", sourceEntryId: "a1", sourceType: "assistant", timestamp: 2, confidence: 0.9 }, + { kind: "activeTask", subject: "next-step-a2-0", text: "Wire hooks into pi.", scope: "branch", sourceEntryId: "a2", sourceType: "assistant", timestamp: 3, confidence: 0.8 }, + { kind: "relevantFile", subject: "runtime-ts", text: "src/runtime.ts", scope: "branch", sourceEntryId: "a3", sourceType: "assistant", timestamp: 4, confidence: 0.7 }, +]); + +test("buildCompactionSummary renders the exact section order and content", () => { + const summary = buildCompactionSummary(ledger); + + assert.equal( + summary, + [ + "## Goal", + "- Build a pi context manager", + "", + "## Constraints", + "- Must adapt to the active model context window.", + "", + "## Decisions", + "- Keep the MVP quiet.", + "", + "## Active work", + "- Wire hooks into pi.", + "", + "## Relevant files", + "- src/runtime.ts", + "", + "## Next steps", + "- Wire hooks into pi.", + ].join("\n") + ); +}); + +test("buildBranchSummary renders the handoff header and sections in order", () => { + const summary = buildBranchSummary(ledger, "experimental branch"); + + assert.equal( + summary, + [ + "# Handoff for experimental branch", + "", + "## Goal", + "- Build a pi context manager", + "", + "## Decisions", + "- Keep the MVP quiet.", + "", + "## Active work", + "- Wire hooks into pi.", + "", + "## Relevant files", + "- src/runtime.ts", + ].join("\n") + ); +}); + +test("buildCompactionSummaryFromPreparation uses preparation messages, previous summary, file ops, and focus text", () => { + const previousSummary = [ + "## Goal", + "- Ship the context manager extension", + "", + "## Key Decisions", + "- Keep the public API stable.", + ].join("\n"); + + const summary = buildCompactionSummaryFromPreparation({ + messagesToSummarize: [ + { role: "user", content: "Decision: keep compaction summaries deterministic", timestamp: 1 }, + { role: "assistant", content: [{ type: "text", text: "Blocked: verify /tree replaceInstructions behavior" }], timestamp: 2 }, + ], + turnPrefixMessages: [ + { role: "toolResult", toolName: "read", content: [{ type: "text", text: "Opened .pi/agent/extensions/context-manager/index.ts" }], isError: false, timestamp: 3 }, + ], + previousSummary, + fileOps: { + readFiles: [".pi/agent/extensions/context-manager/index.ts"], + modifiedFiles: [".pi/agent/extensions/context-manager/src/summaries.ts"], + }, + customInstructions: "Focus on decisions, blockers, and relevant files.", + }); + + assert.match(summary, /## Key Decisions/); + assert.match(summary, /keep compaction summaries deterministic/); + assert.match(summary, /## Open questions and blockers/); + assert.match(summary, /verify \/tree replaceInstructions behavior/); + assert.match(summary, /[\s\S]*index.ts[\s\S]*<\/read-files>/); + assert.match(summary, /[\s\S]*src\/summaries.ts[\s\S]*<\/modified-files>/); +}); + +test("buildBranchSummaryFromEntries uses only the abandoned branch entries and custom focus", () => { + const summary = buildBranchSummaryFromEntries({ + branchLabel: "abandoned branch", + entriesToSummarize: [ + { type: "message", id: "user-1", parentId: null, timestamp: new Date(1).toISOString(), message: { role: "user", content: "Goal: explore tree handoff" } }, + { type: "message", id: "assistant-1", parentId: "user-1", timestamp: new Date(2).toISOString(), message: { role: "assistant", content: [{ type: "text", text: "Decision: do not leak branch-local goals" }] } }, + ], + customInstructions: "Focus on goals and decisions.", + replaceInstructions: false, + commonAncestorId: "root", + }); + + assert.match(summary, /# Handoff for abandoned branch/); + assert.match(summary, /explore tree handoff/); + assert.match(summary, /do not leak branch-local goals/); +}); + +test("buildResumePacket renders restart guidance in the expected order", () => { + const summary = buildResumePacket(ledger); + + assert.equal( + summary, + [ + "## Goal", + "- Build a pi context manager", + "", + "## Current task", + "- Wire hooks into pi.", + "", + "## Constraints", + "- Must adapt to the active model context window.", + "", + "## Key decisions", + "- Keep the MVP quiet.", + ].join("\n") + ); +}); diff --git a/src/summaries.ts b/src/summaries.ts new file mode 100644 index 0000000..b4a722b --- /dev/null +++ b/src/summaries.ts @@ -0,0 +1,251 @@ +import { createEmptyLedger, getActiveItems, mergeCandidates, type LedgerState } from "./ledger.ts"; +import { extractCandidates, type TranscriptSlice } from "./extract.ts"; + +function lines(title: string, items: string[]) { + if (items.length === 0) return [`## ${title}`, "- none", ""]; + return [`## ${title}`, ...items.map((item) => `- ${item}`), ""]; +} + +function isTextPart(part: unknown): part is { type: "text"; text?: string } { + return typeof part === "object" && part !== null && "type" in part && (part as { type?: unknown }).type === "text"; +} + +function toText(content: unknown): string { + if (typeof content === "string") { + return content; + } + + if (!Array.isArray(content)) { + return ""; + } + + return content + .map((part) => { + if (!isTextPart(part)) return ""; + return typeof part.text === "string" ? part.text : ""; + }) + .join("\n") + .trim(); +} + +type FocusKind = "goal" | "constraint" | "decision" | "activeTask" | "openQuestion" | "relevantFile"; +type SummarySection = { kind: FocusKind; title: string; items: string[] }; + +function hasSessionGoal(ledger: LedgerState) { + return getActiveItems(ledger, "goal").some((item) => item.scope === "session" && item.subject === "root-goal"); +} + +function buildLedgerFromSlices(slices: TranscriptSlice[], previousSummary?: string) { + let ledger = createEmptyLedger(); + + if (previousSummary) { + ledger = mergeCandidates( + ledger, + extractCandidates( + { + entryId: "previous-summary", + role: "compaction", + text: previousSummary, + timestamp: 0, + }, + { hasSessionGoal: false }, + ), + ); + } + + for (const slice of slices) { + ledger = mergeCandidates(ledger, extractCandidates(slice, { hasSessionGoal: hasSessionGoal(ledger) })); + } + + return ledger; +} + +function parseFocus(customInstructions?: string): Set { + const text = (customInstructions ?? "").toLowerCase(); + const focus = new Set(); + + if (/\bgoal/.test(text)) focus.add("goal"); + if (/\bconstraint|preference/.test(text)) focus.add("constraint"); + if (/\bdecision/.test(text)) focus.add("decision"); + if (/\btask|next step|progress/.test(text)) focus.add("activeTask"); + if (/\bblocker|blocked|open question/.test(text)) focus.add("openQuestion"); + if (/\bfile|read-files|modified-files/.test(text)) focus.add("relevantFile"); + + return focus; +} + +function buildStructuredSections(ledger: LedgerState): SummarySection[] { + return [ + { kind: "goal", title: "Goal", items: getActiveItems(ledger, "goal").map((item) => item.text) }, + { + kind: "constraint", + title: "Constraints & Preferences", + items: getActiveItems(ledger, "constraint").map((item) => item.text), + }, + { + kind: "activeTask", + title: "Progress", + items: getActiveItems(ledger, "activeTask").map((item) => item.text), + }, + { + kind: "decision", + title: "Key Decisions", + items: getActiveItems(ledger, "decision").map((item) => item.text), + }, + { + kind: "openQuestion", + title: "Open questions and blockers", + items: getActiveItems(ledger, "openQuestion").map((item) => item.text), + }, + { + kind: "relevantFile", + title: "Critical Context", + items: getActiveItems(ledger, "relevantFile").map((item) => item.text), + }, + { + kind: "activeTask", + title: "Next Steps", + items: getActiveItems(ledger, "activeTask").map((item) => item.text), + }, + ]; +} + +function sortSectionsForFocus(sections: SummarySection[], focus: Set): SummarySection[] { + if (focus.size === 0) { + return sections; + } + + return [ + ...sections.filter((section) => focus.has(section.kind)), + ...sections.filter((section) => !focus.has(section.kind)), + ]; +} + +function unique(values: string[]) { + return [...new Set(values)]; +} + +function fileTag(name: "read-files" | "modified-files", values: string[]) { + if (values.length === 0) { + return [] as string[]; + } + + return [`<${name}>`, ...values, ``, ""]; +} + +function renderStructuredSummary( + ledger: LedgerState, + options?: { + header?: string; + focus?: Set; + readFiles?: string[]; + modifiedFiles?: string[]; + }, +) { + const sections = sortSectionsForFocus(buildStructuredSections(ledger), options?.focus ?? new Set()); + return [ + ...(options?.header ? [options.header, ""] : []), + ...sections.flatMap((section) => lines(section.title, section.items)), + ...fileTag("read-files", unique(options?.readFiles ?? [])), + ...fileTag("modified-files", unique(options?.modifiedFiles ?? [])), + ].join("\n").trim(); +} + +function messageToSlice(message: any, entryId: string, timestampFallback: number): TranscriptSlice | undefined { + if (!message || typeof message !== "object") { + return undefined; + } + + if (message.role !== "user" && message.role !== "assistant" && message.role !== "toolResult") { + return undefined; + } + + return { + entryId, + role: message.role, + text: toText(message.content), + timestamp: typeof message.timestamp === "number" ? message.timestamp : timestampFallback, + isError: message.role === "toolResult" ? message.isError : undefined, + }; +} + +export function buildCompactionSummary(ledger: LedgerState): string { + return [ + ...lines("Goal", getActiveItems(ledger, "goal").map((item) => item.text)), + ...lines("Constraints", getActiveItems(ledger, "constraint").map((item) => item.text)), + ...lines("Decisions", getActiveItems(ledger, "decision").map((item) => item.text)), + ...lines("Active work", getActiveItems(ledger, "activeTask").map((item) => item.text)), + ...lines("Relevant files", getActiveItems(ledger, "relevantFile").map((item) => item.text)), + ...lines("Next steps", getActiveItems(ledger, "activeTask").map((item) => item.text)), + ].join("\n").trim(); +} + +export function buildBranchSummary(ledger: LedgerState, branchLabel: string): string { + return [ + `# Handoff for ${branchLabel}`, + "", + ...lines("Goal", getActiveItems(ledger, "goal").map((item) => item.text)), + ...lines("Decisions", getActiveItems(ledger, "decision").map((item) => item.text)), + ...lines("Active work", getActiveItems(ledger, "activeTask").map((item) => item.text)), + ...lines("Relevant files", getActiveItems(ledger, "relevantFile").map((item) => item.text)), + ].join("\n").trim(); +} + +export function buildResumePacket(ledger: LedgerState): string { + return [ + ...lines("Goal", getActiveItems(ledger, "goal").map((item) => item.text)), + ...lines("Current task", getActiveItems(ledger, "activeTask").map((item) => item.text)), + ...lines("Constraints", getActiveItems(ledger, "constraint").map((item) => item.text)), + ...lines("Key decisions", getActiveItems(ledger, "decision").map((item) => item.text)), + ].join("\n").trim(); +} + +export function buildCompactionSummaryFromPreparation(input: { + messagesToSummarize: any[]; + turnPrefixMessages: any[]; + previousSummary?: string; + fileOps?: { readFiles?: string[]; modifiedFiles?: string[] }; + customInstructions?: string; +}): string { + const slices = [...input.messagesToSummarize, ...input.turnPrefixMessages] + .map((message, index) => messageToSlice(message, `compaction-${index}`, index)) + .filter((slice): slice is TranscriptSlice => Boolean(slice)); + + const ledger = buildLedgerFromSlices(slices, input.previousSummary); + return renderStructuredSummary(ledger, { + focus: parseFocus(input.customInstructions), + readFiles: input.fileOps?.readFiles, + modifiedFiles: input.fileOps?.modifiedFiles, + }); +} + +export function buildBranchSummaryFromEntries(input: { + branchLabel: string; + entriesToSummarize: Array<{ type: string; id: string; timestamp: string; message?: any; summary?: string }>; + customInstructions?: string; + replaceInstructions?: boolean; + commonAncestorId?: string | null; +}): string { + const slices = input.entriesToSummarize.flatMap((entry) => { + if (entry.type === "message") { + const slice = messageToSlice(entry.message, entry.id, Date.parse(entry.timestamp)); + return slice ? [slice] : []; + } + + if (entry.type === "compaction" && typeof entry.summary === "string") { + return [{ entryId: entry.id, role: "compaction", text: entry.summary, timestamp: Date.parse(entry.timestamp) } satisfies TranscriptSlice]; + } + + if (entry.type === "branch_summary" && typeof entry.summary === "string") { + return [{ entryId: entry.id, role: "branchSummary", text: entry.summary, timestamp: Date.parse(entry.timestamp) } satisfies TranscriptSlice]; + } + + return []; + }); + + const ledger = buildLedgerFromSlices(slices); + return renderStructuredSummary(ledger, { + header: `# Handoff for ${input.branchLabel}`, + focus: parseFocus(input.customInstructions), + }); +}