(() => {
if (!billing) return [];
- return billing.stages.map((stage) => mapStageRow(stage, stage.runtime_secs));
+ return billing.stages.map((stage) => mapStageRow(stage, stage.timing.wall_time_ms));
}, [billing]);
// The model breakdown is server-derived and stable across ticks too.
@@ -206,16 +206,16 @@ export default function RunBilling({ params }: { params: { id: string } }) {
if (!hasInFlight) return completedRows;
return billing.stages.map((stage, idx) =>
isInFlight(stage)
- ? mapStageRow(stage, liveRuntimeSecs(stage, now))
+ ? mapStageRow(stage, liveWallTimeMs(stage, now))
: completedRows[idx],
);
}, [billing, completedRows, hasInFlight, now]);
// While ticking, sum the displayed row runtimes so the footer updates in
// lock-step. Otherwise trust the server's authoritative total.
- const totalRuntimeSecs = hasInFlight
- ? rows.reduce((sum, row) => sum + row.runtimeSecs, 0)
- : (billing?.totals.runtime_secs ?? 0);
+ const totalWallTimeMs = hasInFlight
+ ? rows.reduce((sum, row) => sum + row.wallTimeMs, 0)
+ : (billing?.totals.timing.wall_time_ms ?? 0);
const hasLlmStages = (billing?.by_model.length ?? 0) > 0;
const totalInput = hasLlmStages ? (billing?.totals.input_tokens ?? null) : null;
@@ -276,7 +276,7 @@ export default function RunBilling({ params }: { params: { id: string } }) {
/>
|
- {formatDurationSecs(row.runtimeSecs)}
+ {formatDurationMs(row.wallTimeMs)}
|
{formatUsdMicrosOrDash(row.totalUsdMicros)}
@@ -297,7 +297,7 @@ export default function RunBilling({ params }: { params: { id: string } }) {
/>
|
- {formatDurationSecs(totalRuntimeSecs)}
+ {formatDurationMs(totalWallTimeMs)}
|
{formatUsdMicrosOrDash(totalUsdMicros)}
diff --git a/apps/fabro-web/app/routes/run-detail.test.ts b/apps/fabro-web/app/routes/run-detail.test.ts
index 6a6f72d8f..84a012e8a 100644
--- a/apps/fabro-web/app/routes/run-detail.test.ts
+++ b/apps/fabro-web/app/routes/run-detail.test.ts
@@ -104,9 +104,8 @@ function makeRunSummary(
started_at: null,
last_event_at: null,
completed_at: null,
- duration_ms: null,
- elapsed_secs: null,
},
+ timing: null,
billing: null,
diff: diffSummary,
pull_request: pullRequest,
@@ -548,4 +547,4 @@ describe("RunDetail full-height child routes", () => {
);
expect(outletWrappers).toHaveLength(1);
});
-});
+});
\ No newline at end of file
diff --git a/bun.lock b/bun.lock
index dd8729a4f..48c749309 100644
--- a/bun.lock
+++ b/bun.lock
@@ -77,6 +77,7 @@
"axios": "^1.7.0",
},
"devDependencies": {
+ "@openapitools/openapi-generator-cli": "2.20.2",
"typescript": "^5.9.2",
},
},
@@ -146,6 +147,8 @@
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
+ "@borewit/text-codec": ["@borewit/text-codec@0.2.2", "", {}, "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ=="],
+
"@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="],
"@clack/core": ["@clack/core@1.2.0", "", { "dependencies": { "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg=="],
@@ -294,6 +297,8 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+ "@lukeed/csprng": ["@lukeed/csprng@1.1.0", "", {}, "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA=="],
+
"@mediabunny/aac-encoder": ["@mediabunny/aac-encoder@1.39.2", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-KD6KADVzAnW7tqhRFGBOX4uaiHbd0Yxvg0lfthj3wJLAEEgEBAvi43w+ZXWeEn54X/jpabrLe4bW/eYFFvlbUA=="],
"@mediabunny/flac-encoder": ["@mediabunny/flac-encoder@1.39.2", "", { "peerDependencies": { "mediabunny": "^1.0.0" } }, "sha512-VwBr3AzZTPEEPvt4aladZiXwOf3W293eq213zDupGQi/taS8WWNqDd3eBdf8FfvlbXATfbRiycXDKyQ0HlOZaQ=="],
@@ -314,6 +319,18 @@
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="],
+ "@nestjs/axios": ["@nestjs/axios@4.0.0", "", { "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "axios": "^1.3.1", "rxjs": "^7.0.0" } }, "sha512-1cB+Jyltu/uUPNQrpUimRHEQHrnQrpLzVj6dU3dgn6iDDDdahr10TgHFGTmw5VuJ9GzKZsCLDL78VSwJAs/9JQ=="],
+
+ "@nestjs/common": ["@nestjs/common@11.1.1", "", { "dependencies": { "file-type": "20.5.0", "iterare": "1.2.1", "load-esm": "1.0.2", "tslib": "2.8.1", "uid": "2.0.2" }, "peerDependencies": { "class-transformer": ">=0.4.1", "class-validator": ">=0.13.2", "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "optionalPeers": ["class-transformer", "class-validator"] }, "sha512-crzp+1qeZ5EGL0nFTPy9NrVMAaUWewV5AwtQyv6SQ9yQPXwRl9W9hm1pt0nAtUu5QbYMbSuo7lYcF81EjM+nCA=="],
+
+ "@nestjs/core": ["@nestjs/core@11.1.1", "", { "dependencies": { "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", "path-to-regexp": "8.2.0", "tslib": "2.8.1", "uid": "2.0.2" }, "peerDependencies": { "@nestjs/common": "^11.0.0", "@nestjs/microservices": "^11.0.0", "@nestjs/platform-express": "^11.0.0", "@nestjs/websockets": "^11.0.0", "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "optionalPeers": ["@nestjs/microservices", "@nestjs/platform-express", "@nestjs/websockets"] }, "sha512-UFoUAgLKFT+RwHTANJdr0dF7p0qS9QjkaUPjg8aafnjM/qxxxrUVDB49nVvyMlk+Hr1+vvcNaOHbWWQBxoZcHA=="],
+
+ "@nuxt/opencollective": ["@nuxt/opencollective@0.4.1", "", { "dependencies": { "consola": "^3.2.3" }, "bin": { "opencollective": "bin/opencollective.js" } }, "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ=="],
+
+ "@nuxtjs/opencollective": ["@nuxtjs/opencollective@0.3.2", "", { "dependencies": { "chalk": "^4.1.0", "consola": "^2.15.0", "node-fetch": "^2.6.1" }, "bin": { "opencollective": "bin/opencollective.js" } }, "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA=="],
+
+ "@openapitools/openapi-generator-cli": ["@openapitools/openapi-generator-cli@2.20.2", "", { "dependencies": { "@nestjs/axios": "4.0.0", "@nestjs/common": "11.1.1", "@nestjs/core": "11.1.1", "@nuxtjs/opencollective": "0.3.2", "axios": "1.9.0", "chalk": "4.1.2", "commander": "8.3.0", "compare-versions": "4.1.4", "concurrently": "6.5.1", "console.table": "0.10.0", "fs-extra": "11.3.0", "glob": "9.3.5", "inquirer": "8.2.6", "lodash": "4.17.21", "proxy-agent": "6.5.0", "reflect-metadata": "0.2.2", "rxjs": "7.8.2", "tslib": "2.8.1" }, "bin": { "openapi-generator-cli": "main.js" } }, "sha512-dNFwQcQu6+rmEWSJj4KUx468+p6Co7nfpVgi5QEfVhzKj7wBytz9GEhCN2qmVgtg3ZX8H6nxbXI8cjh7hAxAqg=="],
+
"@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
"@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="],
@@ -668,6 +685,12 @@
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.19", "", {}, "sha512-/BMP7kNhzKOd7wnDeB8NrIRNLwkf5AhCYCvtfZV2GXWbBieFm/el0n6LOAXlTi6ZwHICSNnQcIxRCWHrLzDY+g=="],
+ "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="],
+
+ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
+
+ "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
+
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
@@ -760,12 +783,20 @@
"acorn-import-phases": ["acorn-import-phases@1.0.4", "", { "peerDependencies": { "acorn": "^8.14.0" } }, "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ=="],
+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
+
"ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
"ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="],
"ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="],
+ "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
+
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
@@ -792,14 +823,26 @@
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
+ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
+
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
+ "basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="],
+
"big.js": ["big.js@5.2.2", "", {}, "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="],
+ "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
+
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
+ "brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="],
+
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
+ "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
+
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
@@ -810,6 +853,8 @@
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
+ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
@@ -818,6 +863,8 @@
"character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
+ "chardet": ["chardet@0.7.0", "", {}, "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="],
+
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="],
@@ -828,16 +875,38 @@
"classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="],
+ "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
+
+ "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
+
+ "cli-width": ["cli-width@3.0.0", "", {}, "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw=="],
+
+ "cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="],
+
+ "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="],
+
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
- "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
+ "commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
"common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="],
+ "compare-versions": ["compare-versions@4.1.4", "", {}, "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw=="],
+
+ "concurrently": ["concurrently@6.5.1", "", { "dependencies": { "chalk": "^4.1.0", "date-fns": "^2.16.1", "lodash": "^4.17.21", "rxjs": "^6.6.3", "spawn-command": "^0.0.2-1", "supports-color": "^8.1.0", "tree-kill": "^1.2.2", "yargs": "^16.2.0" }, "bin": { "concurrently": "bin/concurrently.js" } }, "sha512-FlSwNpGjWQfRwPLXvJ/OgysbBxPkWpiVjy1042b0U7on7S7qwwMIILRj7WTN1mTgqa582bG6NFuScOoh6Zgdag=="],
+
+ "consola": ["consola@2.15.3", "", {}, "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw=="],
+
+ "console.table": ["console.table@0.10.0", "", { "dependencies": { "easy-table": "1.1.0" } }, "sha512-dPyZofqggxuvSf7WXvNjuRfnsOk1YazkVP8FdxH4tcH2c37wc79/Yl6Bhr7Lsu00KMgy2ql/qCMuNu8xctZM8g=="],
+
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
@@ -862,14 +931,22 @@
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
+ "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
+
+ "date-fns": ["date-fns@2.30.0", "", { "dependencies": { "@babel/runtime": "^7.21.0" } }, "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw=="],
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
+ "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
+
"define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
"defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="],
+ "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
+
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
@@ -902,8 +979,12 @@
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
+ "easy-table": ["easy-table@1.1.0", "", { "optionalDependencies": { "wcwidth": ">=1.0.1" } }, "sha512-oq33hWOSSnl2Hoh00tZWaIPi1ievrD9aFG82/IgjlycAnW9hHx5PkJiXpxPsgEE+H7BsbVQXFVFST8TEXS6/pA=="],
+
"electron-to-chromium": ["electron-to-chromium@1.5.302", "", {}, "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg=="],
+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
"emojis-list": ["emojis-list@3.0.0", "", {}, "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
@@ -928,7 +1009,9 @@
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
- "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+ "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+
+ "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="],
"eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="],
@@ -942,6 +1025,8 @@
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
+ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
+
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
@@ -950,6 +1035,8 @@
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
+ "external-editor": ["external-editor@3.1.0", "", { "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew=="],
+
"extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
"fabro-remotion": ["fabro-remotion@workspace:apps/remotion"],
@@ -960,6 +1047,8 @@
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
+ "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="],
+
"fast-string-truncated-width": ["fast-string-truncated-width@1.2.1", "", {}, "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow=="],
"fast-string-width": ["fast-string-width@1.1.0", "", { "dependencies": { "fast-string-truncated-width": "^1.2.0" } }, "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ=="],
@@ -972,6 +1061,12 @@
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+ "fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="],
+
+ "figures": ["figures@3.2.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg=="],
+
+ "file-type": ["file-type@20.5.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg=="],
+
"flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="],
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
@@ -982,14 +1077,20 @@
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
+ "fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
+
"fs-monkey": ["fs-monkey@1.0.3", "", {}, "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q=="],
+ "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
+
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
@@ -998,8 +1099,12 @@
"get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
+ "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
+
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
+ "glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
+
"glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
@@ -1048,12 +1153,26 @@
"http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
+ "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
+
+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
+
"human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
+ "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
+
"icss-utils": ["icss-utils@5.1.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA=="],
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
+ "inquirer": ["inquirer@8.2.6", "", { "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", "cli-cursor": "^3.1.0", "cli-width": "^3.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", "lodash": "^4.17.21", "mute-stream": "0.0.8", "ora": "^5.4.1", "run-async": "^2.4.0", "rxjs": "^7.5.5", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6", "wrap-ansi": "^6.0.1" } }, "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg=="],
+
+ "ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="],
+
"iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
"is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
@@ -1066,20 +1185,28 @@
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
+
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
+ "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="],
+
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
+ "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
+
"is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+ "iterare": ["iterare@1.2.1", "", {}, "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q=="],
+
"jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
@@ -1096,6 +1223,8 @@
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
+ "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
+
"kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
@@ -1122,15 +1251,21 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
+ "load-esm": ["load-esm@1.0.2", "", {}, "sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw=="],
+
"loader-runner": ["loader-runner@4.3.1", "", {}, "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q=="],
"loader-utils": ["loader-utils@2.0.4", "", { "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", "json5": "^2.1.2" } }, "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw=="],
+ "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
+
"lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="],
+ "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
+
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
- "lru-cache": ["lru-cache@11.3.5", "", {}, "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw=="],
+ "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
"lru_map": ["lru_map@0.4.1", "", {}, "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg=="],
@@ -1250,24 +1385,34 @@
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
+ "minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="],
+
"minimist": ["minimist@1.2.6", "", {}, "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="],
+ "minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
+
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+ "mute-stream": ["mute-stream@0.0.8", "", {}, "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="],
+
"nanoid": ["nanoid@5.1.11", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg=="],
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
+ "netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="],
+
"nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="],
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
+ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
+
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
"node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="],
@@ -1296,12 +1441,20 @@
"open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
+ "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
+
+ "os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="],
+
"p-limit": ["p-limit@7.3.0", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw=="],
"p-queue": ["p-queue@9.1.2", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^7.0.0" } }, "sha512-ktsDOALzTYTWWF1PbkNVg2rOt+HaOaMWJMUnt7T3qf5tvZ1L8dBW3tObzprBcXNMKkwj+yFSLqHso0x+UFcJXw=="],
"p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="],
+ "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "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" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="],
+
+ "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="],
+
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
"parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
@@ -1312,6 +1465,10 @@
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
+ "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
+
+ "path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
+
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
"piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="],
@@ -1346,6 +1503,8 @@
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
+ "proxy-agent": ["proxy-agent@6.5.0", "", { "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" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="],
+
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
@@ -1378,10 +1537,14 @@
"react-textarea-autosize": ["react-textarea-autosize@8.5.9", "", { "dependencies": { "@babel/runtime": "^7.20.13", "use-composed-ref": "^1.3.0", "use-latest": "^1.2.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A=="],
+ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
+
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
+ "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="],
+
"regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
"regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="],
@@ -1408,8 +1571,12 @@
"remotion": ["remotion@4.0.437", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-mQHiYZwt3HoMngJeTGyytVFdobf/mgsPTiQSUfPP43kA7bEpn4OdaF4hWoMfJhjfTC1ZdkTIRX/s3OKto0aWzg=="],
+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
+ "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
+
"retext": ["retext@9.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "retext-latin": "^4.0.0", "retext-stringify": "^4.0.0", "unified": "^11.0.0" } }, "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA=="],
"retext-latin": ["retext-latin@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "parse-latin": "^7.0.0", "unified": "^11.0.0" } }, "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA=="],
@@ -1420,8 +1587,16 @@
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
+ "run-async": ["run-async@2.4.1", "", {}, "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ=="],
+
+ "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
+
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
"safe-content-frame": ["safe-content-frame@0.0.19", "", {}, "sha512-+R0IHHjvghT5O8bc8itf9AoS9MvzhUcD0p+hNINLgyEuFQJug3wt3ZuhLFZFG3bUzHi8UfQED4p6J3/Ft9oCtg=="],
+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
+
"sax": ["sax@1.5.0", "", {}, "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
@@ -1446,8 +1621,14 @@
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
+ "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
+
"smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="],
+ "socks": ["socks@2.8.9", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw=="],
+
+ "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
+
"source-map": ["source-map@0.7.3", "", {}, "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
@@ -1456,19 +1637,29 @@
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
+ "spawn-command": ["spawn-command@0.0.2", "", {}, "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ=="],
+
"stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
"strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
+ "strtok3": ["strtok3@10.3.5", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA=="],
+
"style-loader": ["style-loader@4.0.0", "", { "peerDependencies": { "webpack": "^5.27.0" } }, "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA=="],
"style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="],
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
- "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
+ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="],
@@ -1484,6 +1675,8 @@
"terser-webpack-plugin": ["terser-webpack-plugin@5.4.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g=="],
+ "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
+
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
@@ -1494,7 +1687,13 @@
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
- "tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="],
+ "tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="],
+
+ "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
+
+ "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
+
+ "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
@@ -1504,10 +1703,16 @@
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+ "type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
+ "uid": ["uid@2.0.2", "", { "dependencies": { "@lukeed/csprng": "^1.0.0" } }, "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g=="],
+
+ "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
+
"ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="],
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
@@ -1536,6 +1741,8 @@
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
+ "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
+
"unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
@@ -1570,28 +1777,36 @@
"watchpack": ["watchpack@2.5.1", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg=="],
+ "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
+
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
- "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="],
+ "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"webpack": ["webpack@5.105.0", "", { "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.19.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.16", "watchpack": "^2.5.1", "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw=="],
"webpack-sources": ["webpack-sources@3.3.4", "", {}, "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q=="],
- "whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="],
+ "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="],
+ "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
+
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
"xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
+
"yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
+ "yargs": ["yargs@16.2.0", "", { "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" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="],
+
"yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
@@ -1620,6 +1835,10 @@
"@babel/traverse/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
+ "@nuxt/opencollective/consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
+
+ "@openapitools/openapi-generator-cli/axios": ["axios@1.9.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg=="],
+
"@parcel/watcher/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"@radix-ui/react-accordion/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
@@ -1816,24 +2035,44 @@
"astro/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
+ "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+ "concurrently/rxjs": ["rxjs@6.6.7", "", { "dependencies": { "tslib": "^1.9.0" } }, "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ=="],
+
+ "concurrently/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
+
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
+ "degenerator/ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
+
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+ "escodegen/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
+
+ "escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
"esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"extract-zip/get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
"is-inside-container/is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
+ "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
+
"magicast/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
+ "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+
"open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
"open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
+ "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+
+ "path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
+
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"postcss-modules-local-by-default/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
@@ -1852,14 +2091,20 @@
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+ "svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
+
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"terser-webpack-plugin/schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="],
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
+ "unstorage/lru-cache": ["lru-cache@11.3.5", "", {}, "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw=="],
+
"webpack/schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="],
+ "yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
+
"@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="],
"@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="],
@@ -1986,6 +2231,8 @@
"@remotion/bundler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ=="],
+ "@remotion/renderer/source-map/whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="],
+
"@remotion/studio-server/semver/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
"@remotion/studio/semver/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
@@ -2030,6 +2277,8 @@
"astro/shiki/@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="],
+ "concurrently/rxjs/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
+
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
"terser-webpack-plugin/schema-utils/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
@@ -2040,6 +2289,10 @@
"webpack/schema-utils/ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="],
+ "@remotion/renderer/source-map/whatwg-url/tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="],
+
+ "@remotion/renderer/source-map/whatwg-url/webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="],
+
"@tailwindcss/vite/@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
"@tailwindcss/vite/@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml
index 0c3f767c3..417acf422 100644
--- a/docs/public/api-reference/fabro-api.yaml
+++ b/docs/public/api-reference/fabro-api.yaml
@@ -7583,11 +7583,13 @@ components:
type: ["string", "null"]
format: date-time
description: Wall-clock time the latest attempt of this stage started, if known.
- duration_ms:
- type: ["integer", "null"]
- format: uint64
- minimum: 0
- description: Wall-clock duration of the stage's latest terminal attempt, if known.
+ timing:
+ oneOf:
+ - $ref: "#/components/schemas/StageTiming"
+ - type: "null"
+ description: |
+ Per-attempt timing breakdown for the latest terminal attempt:
+ wall time plus the active inference/tool breakdown.
usage:
$ref: "#/components/schemas/BilledTokenCounts"
model:
@@ -7743,17 +7745,15 @@ components:
required:
- stage_id
- stage_label
- - duration_ms
+ - timing
- retries
properties:
stage_id:
type: string
stage_label:
type: string
- duration_ms:
- type: integer
- format: uint64
- minimum: 0
+ timing:
+ $ref: "#/components/schemas/StageTiming"
billing_usd_micros:
type: ["integer", "null"]
format: int64
@@ -7768,7 +7768,7 @@ components:
required:
- timestamp
- status
- - duration_ms
+ - timing
- stages
- total_retries
- diff
@@ -7778,10 +7778,8 @@ components:
format: date-time
status:
$ref: "#/components/schemas/StageOutcome"
- duration_ms:
- type: integer
- format: uint64
- minimum: 0
+ timing:
+ $ref: "#/components/schemas/RunTiming"
failure:
oneOf:
- $ref: "#/components/schemas/RunFailure"
@@ -7908,6 +7906,7 @@ components:
- models
- source_directory
- timestamps
+ - timing
- billing
- diff
- pull_request
@@ -7964,6 +7963,13 @@ components:
type: ["string", "null"]
timestamps:
$ref: "#/components/schemas/RunTimestamps"
+ timing:
+ oneOf:
+ - $ref: "#/components/schemas/RunTiming"
+ - type: "null"
+ description: |
+ Run-level timing rollup. Wall time is the run's clock duration;
+ active timing sums work across stage visits.
billing:
oneOf:
- $ref: "#/components/schemas/RunBillingSummary"
@@ -8068,11 +8074,6 @@ components:
completed_at:
type: ["string", "null"]
format: date-time
- duration_ms:
- type: ["integer", "null"]
- format: int64
- elapsed_secs:
- type: ["number", "null"]
RunBillingSummary:
type: object
@@ -8212,10 +8213,12 @@ components:
example: unit-tests
status:
$ref: "#/components/schemas/CheckRunStatus"
- duration_secs:
- type: number
- description: Duration of the check run in seconds.
- example: 154.0
+ wall_time_ms:
+ type: integer
+ format: uint64
+ minimum: 0
+ description: Wall-clock duration of the check run in milliseconds.
+ example: 154000
# ── Reusable Sub-Schemas ───────────────────────────────────────────
@@ -8611,20 +8614,75 @@ components:
format: uri
example: https://github.com/fabro-sh/fabro/pull/123
- RunTimings:
- description: Timing information for a run.
+ StageTiming:
+ description: |
+ Timing breakdown for one stage visit. Fields are all milliseconds.
+ `wall_time_ms` is elapsed clock time; `inference_time_ms` is Fabro-
+ observed LLM request/stream elapsed time; `tool_time_ms` is tool or
+ command execution elapsed time; `active_time_ms` equals
+ `inference_time_ms + tool_time_ms`.
type: object
required:
- - elapsed_secs
+ - wall_time_ms
+ - active_time_ms
properties:
- elapsed_secs:
- type: number
- description: Wall-clock time elapsed in seconds.
- example: 420.0
- elapsed_warning:
- type: boolean
- description: Whether the elapsed time exceeds the expected threshold.
- example: false
+ wall_time_ms:
+ type: integer
+ format: uint64
+ minimum: 0
+ example: 1500
+ inference_time_ms:
+ type: integer
+ format: uint64
+ minimum: 0
+ default: 0
+ example: 900
+ tool_time_ms:
+ type: integer
+ format: uint64
+ minimum: 0
+ default: 0
+ example: 200
+ active_time_ms:
+ type: integer
+ format: uint64
+ minimum: 0
+ description: Equals `inference_time_ms + tool_time_ms`.
+ example: 1100
+
+ RunTiming:
+ description: |
+ Timing rollup for an entire run. Active fields sum work across stage
+ visits, so `active_time_ms` can exceed `wall_time_ms` when parallel
+ branches run concurrently.
+ type: object
+ required:
+ - wall_time_ms
+ - active_time_ms
+ properties:
+ wall_time_ms:
+ type: integer
+ format: uint64
+ minimum: 0
+ example: 420000
+ inference_time_ms:
+ type: integer
+ format: uint64
+ minimum: 0
+ default: 0
+ example: 120000
+ tool_time_ms:
+ type: integer
+ format: uint64
+ minimum: 0
+ default: 0
+ example: 60000
+ active_time_ms:
+ type: integer
+ format: uint64
+ minimum: 0
+ description: Equals `inference_time_ms + tool_time_ms`.
+ example: 180000
SandboxProvider:
description: Sandbox execution provider.
@@ -8704,7 +8762,7 @@ components:
- reasoning_tokens
- cache_read_tokens
- cache_write_tokens
- - runtime_secs
+ - timing
properties:
runs:
type: integer
@@ -8739,10 +8797,12 @@ components:
format: int64
description: Total billed USD amount in micros.
example: 20340000
- runtime_secs:
- type: number
- description: Total runtime in seconds.
- example: 3501.0
+ timing:
+ $ref: "#/components/schemas/RunTiming"
+ description: |
+ Aggregate timing rollup across every completed run. Active timing
+ sums work across stage visits, so `active_time_ms` can exceed
+ `wall_time_ms`.
BillingStageRef:
description: Reference to a workflow node in a billing stage row.
@@ -8864,10 +8924,12 @@ components:
$ref: "#/components/schemas/StageHandler"
status:
$ref: "#/components/schemas/StageState"
- duration_secs:
- type: number
- description: Time spent in this stage, in seconds.
- example: 154.0
+ wall_time_ms:
+ type: integer
+ format: uint64
+ minimum: 0
+ description: Wall-clock time the latest attempt spent in this stage, in milliseconds.
+ example: 154000
node_id:
type: string
description: Node id in the workflow graph; multiple stages with different visits share the same node_id.
@@ -9220,13 +9282,13 @@ components:
# ── Billing Schemas ──────────────────────────────────────────────────
RunBillingStage:
- description: Token counts and billed totals for one workflow node within a run. Rows are grouped by node; billing and runtime sum every visit of that node.
+ description: Token counts and billed totals for one workflow node within a run. Rows are grouped by node; billing and timing sum every visit of that node.
type: object
required:
- stage
- model
- billing
- - runtime_secs
+ - timing
properties:
stage:
$ref: "#/components/schemas/BillingStageRef"
@@ -9237,10 +9299,11 @@ components:
- type: "null"
billing:
$ref: "#/components/schemas/BilledTokenCounts"
- runtime_secs:
- type: number
- description: Wall-clock runtime in seconds, summed across every visit of this node.
- example: 154.0
+ timing:
+ $ref: "#/components/schemas/StageTiming"
+ description: |
+ Per-node timing summed across every visit. `wall_time_ms` is the
+ sum of visit wall times; the active breakdown sums work timing.
started_at:
type: ["string", "null"]
format: date-time
@@ -9256,7 +9319,7 @@ components:
description: Aggregate billing totals across all stages of a run.
type: object
required:
- - runtime_secs
+ - timing
- input_tokens
- output_tokens
- total_tokens
@@ -9264,10 +9327,11 @@ components:
- cache_read_tokens
- cache_write_tokens
properties:
- runtime_secs:
- type: number
- description: Total wall-clock runtime in seconds.
- example: 389.0
+ timing:
+ $ref: "#/components/schemas/RunTiming"
+ description: |
+ Run-level timing rollup. `wall_time_ms` is summed across stage
+ visits; active timing sums work across visits.
input_tokens:
type: integer
description: Total input tokens consumed.
@@ -11350,4 +11414,4 @@ components:
login:
type: string
description: User's login identifier (e.g. GitHub username).
- example: octocat
+ example: octocat
\ No newline at end of file
diff --git a/lib/crates/fabro-api/build.rs b/lib/crates/fabro-api/build.rs
index eb779ff40..9a5324342 100644
--- a/lib/crates/fabro-api/build.rs
+++ b/lib/crates/fabro-api/build.rs
@@ -364,6 +364,8 @@ fn main() {
("BillingModelRef", "fabro_model::ModelRef", &[]),
("BillingSpeed", "fabro_model::Speed", &[]),
("ExecOutputTail", "fabro_types::ExecOutputTail", &[]),
+ ("StageTiming", "fabro_types::StageTiming", &[]),
+ ("RunTiming", "fabro_types::RunTiming", &[]),
("ProviderId", "fabro_model::ProviderId", &[]),
("Model", "fabro_model::Model", &[]),
("Provider", "fabro_model::Provider", &[]),
diff --git a/lib/crates/fabro-api/tests/run_billing_stage_round_trip.rs b/lib/crates/fabro-api/tests/run_billing_stage_round_trip.rs
index 8acfd67af..be8d85747 100644
--- a/lib/crates/fabro-api/tests/run_billing_stage_round_trip.rs
+++ b/lib/crates/fabro-api/tests/run_billing_stage_round_trip.rs
@@ -37,7 +37,7 @@ fn run_billing_stage_model_accepts_required_null() {
"cache_read_tokens": 0,
"cache_write_tokens": 0
},
- "runtime_secs": 0.0
+ "timing": {"wall_time_ms": 0, "inference_time_ms": 0, "tool_time_ms": 0, "active_time_ms": 0}
});
let stage: RunBillingStage =
@@ -69,7 +69,7 @@ fn run_billing_stage_round_trips_terminal_row_with_started_at_and_state() {
"cache_read_tokens": 0,
"cache_write_tokens": 0
},
- "runtime_secs": 5.5,
+ "timing": {"wall_time_ms": 5500, "inference_time_ms": 0, "tool_time_ms": 0, "active_time_ms": 0},
"started_at": "2026-04-29T12:34:56Z",
"state": "succeeded"
});
@@ -122,7 +122,7 @@ fn run_billing_stage_round_trips_in_flight_row() {
"cache_read_tokens": 0,
"cache_write_tokens": 0
},
- "runtime_secs": 1.25,
+ "timing": {"wall_time_ms": 1250, "inference_time_ms": 0, "tool_time_ms": 0, "active_time_ms": 0},
"started_at": "2026-04-29T12:34:56Z",
"state": "running"
});
diff --git a/lib/crates/fabro-api/tests/run_failure_round_trip.rs b/lib/crates/fabro-api/tests/run_failure_round_trip.rs
index caeabcfb7..b3bd27baa 100644
--- a/lib/crates/fabro-api/tests/run_failure_round_trip.rs
+++ b/lib/crates/fabro-api/tests/run_failure_round_trip.rs
@@ -70,7 +70,7 @@ fn conclusion_json_uses_failure_object() {
status: StageOutcome::Failed {
retry_requested: false,
},
- duration_ms: 42,
+ timing: fabro_types::RunTiming::wall_only(42),
failure: Some(RunFailure {
reason: FailureReason::WorkflowError,
detail: FailureDetail::new("boom", FailureCategory::Deterministic),
@@ -84,7 +84,12 @@ fn conclusion_json_uses_failure_object() {
json!({
"timestamp": "2026-05-13T12:00:00Z",
"status": "failed",
- "duration_ms": 42,
+ "timing": {
+ "wall_time_ms": 42,
+ "inference_time_ms": 0,
+ "tool_time_ms": 0,
+ "active_time_ms": 0
+ },
"failure": {
"reason": "workflow_error",
"detail": {
diff --git a/lib/crates/fabro-api/tests/run_summary_round_trip.rs b/lib/crates/fabro-api/tests/run_summary_round_trip.rs
index f5f283372..3b7887e72 100644
--- a/lib/crates/fabro-api/tests/run_summary_round_trip.rs
+++ b/lib/crates/fabro-api/tests/run_summary_round_trip.rs
@@ -6,7 +6,7 @@ use fabro_api::types::{RepositoryRef as ApiRepositoryRef, Run as ApiRun};
use fabro_types::status::{RunStatus, SuccessReason};
use fabro_types::{
DiffSummary, PullRequestLink, RepositoryProvider, RepositoryRef, Run, RunBillingSummary, RunId,
- RunLifecycle, RunLinks, RunOrigin, RunTimestamps, WorkflowRef,
+ RunLifecycle, RunLinks, RunOrigin, RunTimestamps, RunTiming, WorkflowRef,
};
use serde_json::json;
@@ -62,9 +62,8 @@ fn run_summary_json_matches_openapi_shape() {
started_at: Some(created_at),
last_event_at: Some(last_event_at),
completed_at: None,
- duration_ms: Some(42_000),
- elapsed_secs: Some(42.0),
},
+ timing: Some(RunTiming::new(42_000, 12_000, 30_000)),
billing: Some(RunBillingSummary {
total_usd_micros: Some(123),
}),
@@ -128,9 +127,13 @@ fn run_summary_json_matches_openapi_shape() {
"created_at": "2026-04-20T12:00:00Z",
"started_at": "2026-04-20T12:00:00Z",
"last_event_at": "2026-04-20T12:00:42Z",
- "completed_at": null,
- "duration_ms": 42000,
- "elapsed_secs": 42.0
+ "completed_at": null
+ },
+ "timing": {
+ "wall_time_ms": 42000,
+ "inference_time_ms": 12000,
+ "tool_time_ms": 30000,
+ "active_time_ms": 42000
},
"billing": {
"total_usd_micros": 123
@@ -220,8 +223,7 @@ fn run_summary_deserializes_when_optional_fields_are_absent() {
assert_eq!(summary.timestamps.last_event_at, None);
assert_eq!(summary.lifecycle.status, RunStatus::Running);
assert_eq!(summary.lifecycle.pending_control, None);
- assert_eq!(summary.timestamps.duration_ms, None);
- assert_eq!(summary.timestamps.elapsed_secs, None);
+ assert_eq!(summary.timing.map(|t| t.wall_time_ms), None);
assert_eq!(summary.billing, None);
assert_eq!(summary.superseded_by, None);
assert_eq!(summary.diff, None);
diff --git a/lib/crates/fabro-api/tests/stage_projection_round_trip.rs b/lib/crates/fabro-api/tests/stage_projection_round_trip.rs
index 7d740316c..762bb9120 100644
--- a/lib/crates/fabro-api/tests/stage_projection_round_trip.rs
+++ b/lib/crates/fabro-api/tests/stage_projection_round_trip.rs
@@ -29,7 +29,12 @@ fn stage_projection_round_trips_representative_json() {
"output": "ok",
"termination": "exited",
"started_at": "2026-04-29T12:34:00Z",
- "duration_ms": 56000,
+ "timing": {
+ "wall_time_ms": 56000,
+ "inference_time_ms": 0,
+ "tool_time_ms": 0,
+ "active_time_ms": 0
+ },
"usage": {
"input_tokens": 0,
"output_tokens": 0,
diff --git a/lib/crates/fabro-cli/src/commands/run/events.rs b/lib/crates/fabro-cli/src/commands/run/events.rs
index 061ecdb08..40cb62a52 100644
--- a/lib/crates/fabro-cli/src/commands/run/events.rs
+++ b/lib/crates/fabro-cli/src/commands/run/events.rs
@@ -372,7 +372,7 @@ fn format_event_pretty_value(envelope: &serde_json::Value, styles: &Styles) -> O
}
}
"run.completed" => {
- let duration = format_duration_ms(prop_field(envelope, "duration_ms"));
+ let duration = format_duration_ms(timing_wall_field(envelope));
let status_str = match prop_str_field(envelope, "status") {
Some(status) if !status.is_empty() => status,
_ => "succeeded",
@@ -526,7 +526,7 @@ fn format_event_pretty_value(envelope: &serde_json::Value, styles: &Styles) -> O
}
"stage.completed" => {
let label = str_field(envelope, "node_label").unwrap_or("?");
- let duration = format_duration_ms(prop_field(envelope, "duration_ms"));
+ let duration = format_duration_ms(timing_wall_field(envelope));
let billing = prop_field(envelope, "billing").or_else(|| prop_field(envelope, "usage"));
let cost = format_cost(
billing
@@ -809,6 +809,13 @@ fn prop_str_field<'a>(value: &'a serde_json::Value, key: &str) -> Option<&'a str
prop_field(value, key)?.as_str()
}
+/// Read `properties.timing.wall_time_ms` from a stage/run terminal event
+/// envelope. Returns `None` when timing is absent, which falls back to a
+/// blank display via `format_duration_ms`.
+fn timing_wall_field(envelope: &serde_json::Value) -> Option<&serde_json::Value> {
+ prop_field(envelope, "timing")?.get("wall_time_ms")
+}
+
fn failure_message(failure: &serde_json::Value) -> Option<&serde_json::Value> {
failure
.get("detail")
@@ -1027,7 +1034,7 @@ mod tests {
#[test]
fn pretty_stage_completed() {
let styles = no_color_styles();
- let line = r#"{"ts":"2026-01-01T14:23:15Z","event":"stage.completed","node_label":"plan","properties":{"duration_ms":8000,"status":"succeeded","usage":{"cost":0.12,"input_tokens":10000,"output_tokens":5200}}}"#;
+ let line = r#"{"ts":"2026-01-01T14:23:15Z","event":"stage.completed","node_label":"plan","properties":{"timing":{"wall_time_ms":8000,"inference_time_ms":0,"tool_time_ms":0,"active_time_ms":0},"status":"succeeded","usage":{"cost":0.12,"input_tokens":10000,"output_tokens":5200}}}"#;
let result = format_event_pretty(line, &styles).unwrap();
assert!(result.contains("plan"), "got: {result}");
assert!(result.contains("$0.12"), "got: {result}");
@@ -1106,7 +1113,7 @@ mod tests {
#[test]
fn pretty_workflow_run_completed() {
let styles = no_color_styles();
- let line = r#"{"ts":"2026-01-01T14:23:32Z","run_id":"abc123","event":"run.completed","properties":{"duration_ms":25000,"status":"succeeded","total_usd_micros":570000,"billing":{"input_tokens":5000,"output_tokens":2000,"total_tokens":7000,"cache_read_tokens":3000,"cache_write_tokens":500,"reasoning_tokens":800}}}"#;
+ let line = r#"{"ts":"2026-01-01T14:23:32Z","run_id":"abc123","event":"run.completed","properties":{"timing":{"wall_time_ms":25000,"inference_time_ms":0,"tool_time_ms":0,"active_time_ms":0},"status":"succeeded","total_usd_micros":570000,"billing":{"input_tokens":5000,"output_tokens":2000,"total_tokens":7000,"cache_read_tokens":3000,"cache_write_tokens":500,"reasoning_tokens":800}}}"#;
let result = format_event_pretty(line, &styles).unwrap();
assert!(result.contains("SUCCEEDED"), "got: {result}");
assert!(result.contains("25s"), "got: {result}");
@@ -1120,7 +1127,7 @@ mod tests {
#[test]
fn pretty_workflow_run_completed_backward_compat() {
let styles = no_color_styles();
- let line = r#"{"ts":"2026-01-01T14:23:32Z","run_id":"abc123","event":"run.completed","properties":{"duration_ms":25000,"total_cost":0.57}}"#;
+ let line = r#"{"ts":"2026-01-01T14:23:32Z","run_id":"abc123","event":"run.completed","properties":{"timing":{"wall_time_ms":25000,"inference_time_ms":0,"tool_time_ms":0,"active_time_ms":0},"total_cost":0.57}}"#;
let result = format_event_pretty(line, &styles).unwrap();
assert!(result.contains("SUCCEEDED"), "got: {result}");
assert!(result.contains("25s"), "got: {result}");
@@ -1131,7 +1138,7 @@ mod tests {
#[test]
fn pretty_workflow_run_completed_fail_status() {
let styles = no_color_styles();
- let line = r#"{"ts":"2026-01-01T14:23:32Z","event":"run.completed","properties":{"duration_ms":25000,"status":"failed"}}"#;
+ let line = r#"{"ts":"2026-01-01T14:23:32Z","event":"run.completed","properties":{"timing":{"wall_time_ms":25000,"inference_time_ms":0,"tool_time_ms":0,"active_time_ms":0},"status":"failed"}}"#;
let result = format_event_pretty(line, &styles).unwrap();
assert!(result.contains("FAIL"), "got: {result}");
}
diff --git a/lib/crates/fabro-cli/src/commands/run/output.rs b/lib/crates/fabro-cli/src/commands/run/output.rs
index 33574c330..65e60d85c 100644
--- a/lib/crates/fabro-cli/src/commands/run/output.rs
+++ b/lib/crates/fabro-cli/src/commands/run/output.rs
@@ -205,7 +205,7 @@ pub(crate) fn print_run_conclusion(
fabro_util::printerr!(
printer,
"Duration: {}",
- HumanDuration(Duration::from_millis(conclusion.duration_ms))
+ HumanDuration(Duration::from_millis(conclusion.timing.wall_time_ms))
);
if let Some(billing) = conclusion.billing.as_ref() {
diff --git a/lib/crates/fabro-cli/src/commands/run/run_progress/event.rs b/lib/crates/fabro-cli/src/commands/run/run_progress/event.rs
index fcb072d49..2b9d47123 100644
--- a/lib/crates/fabro-cli/src/commands/run/run_progress/event.rs
+++ b/lib/crates/fabro-cli/src/commands/run/run_progress/event.rs
@@ -132,11 +132,11 @@ pub(super) enum ProgressEvent {
script: Option,
},
StageCompleted {
- node_id: String,
- name: String,
- duration_ms: u64,
- status: String,
- usage: Option,
+ node_id: String,
+ name: String,
+ timing: fabro_types::StageTiming,
+ status: String,
+ usage: Option,
},
StageFailed {
node_id: String,
@@ -350,7 +350,7 @@ pub(super) fn from_run_event(stored: &RunEvent) -> Option {
EventBody::StageCompleted(props) => Some(ProgressEvent::StageCompleted {
node_id,
name: node_label,
- duration_ms: props.duration_ms,
+ timing: props.timing,
status: props.status.to_string(),
usage: props
.billing
@@ -550,7 +550,7 @@ mod tests {
node_id: "plan".into(),
name: "Plan".into(),
index: 0,
- duration_ms: 5000,
+ timing: fabro_types::StageTiming::wall_only(5000),
status: "succeeded".into(),
preferred_label: None,
suggested_next_ids: Vec::new(),
@@ -576,9 +576,9 @@ mod tests {
ProgressEvent::StageCompleted {
node_id,
name,
- duration_ms,
+ timing,
..
- } if node_id == "plan" && name == "Plan" && duration_ms == 5000
+ } if node_id == "plan" && name == "Plan" && timing.wall_time_ms == 5000
));
}
diff --git a/lib/crates/fabro-cli/src/commands/run/run_progress/mod.rs b/lib/crates/fabro-cli/src/commands/run/run_progress/mod.rs
index 4fea33577..ee2f9677f 100644
--- a/lib/crates/fabro-cli/src/commands/run/run_progress/mod.rs
+++ b/lib/crates/fabro-cli/src/commands/run/run_progress/mod.rs
@@ -255,7 +255,7 @@ impl ProgressUI {
ProgressEvent::StageCompleted {
node_id,
name,
- duration_ms,
+ timing,
status,
usage,
} => {
@@ -263,7 +263,7 @@ impl ProgressUI {
renderer,
&node_id,
&name,
- duration_ms,
+ timing.wall_time_ms,
&status,
usage.as_ref(),
);
@@ -585,7 +585,7 @@ mod tests {
node_id: node_id.into(),
name: name.into(),
index: 0,
- duration_ms: 5000,
+ timing: fabro_types::StageTiming::wall_only(5000),
status: "succeeded".into(),
preferred_label: None,
suggested_next_ids: Vec::new(),
diff --git a/lib/crates/fabro-cli/src/commands/run/runner.rs b/lib/crates/fabro-cli/src/commands/run/runner.rs
index 775731075..7cccfe674 100644
--- a/lib/crates/fabro-cli/src/commands/run/runner.rs
+++ b/lib/crates/fabro-cli/src/commands/run/runner.rs
@@ -850,7 +850,7 @@ mod tests {
);
assert_eq!(
worker_title_phase_for_event(&EventBody::RunCompleted(RunCompletedProps {
- duration_ms: 10,
+ timing: fabro_types::RunTiming::wall_only(10),
artifact_count: 0,
status: "succeeded".to_string(),
reason: SuccessReason::Completed,
@@ -868,7 +868,7 @@ mod tests {
reason: FailureReason::Cancelled,
detail: FailureDetail::new("cancelled", FailureCategory::Canceled),
},
- duration_ms: 10,
+ timing: fabro_types::RunTiming::wall_only(10),
final_git_commit_sha: None,
final_patch: None,
diff_summary: None,
@@ -882,7 +882,7 @@ mod tests {
reason: FailureReason::Terminated,
detail: FailureDetail::new("boom", FailureCategory::Deterministic),
},
- duration_ms: 10,
+ timing: fabro_types::RunTiming::wall_only(10),
final_git_commit_sha: None,
final_patch: None,
diff_summary: None,
diff --git a/lib/crates/fabro-cli/src/commands/run/wait.rs b/lib/crates/fabro-cli/src/commands/run/wait.rs
index 94c94b34e..6ef61c4ee 100644
--- a/lib/crates/fabro-cli/src/commands/run/wait.rs
+++ b/lib/crates/fabro-cli/src/commands/run/wait.rs
@@ -82,7 +82,8 @@ fn build_json_output(
"status": run_status_kind(status),
});
if let Some(c) = conclusion {
- value["duration_ms"] = c.duration_ms.into();
+ value["timing"] =
+ serde_json::to_value(c.timing).unwrap_or_else(|_| serde_json::Value::Null);
if let Some(total_usd_micros) = c
.billing
.as_ref()
@@ -112,7 +113,7 @@ fn print_human_output(
let details = match conclusion {
Some(c) => {
- let duration = format_duration_ms(c.duration_ms);
+ let duration = format_duration_ms(c.timing.wall_time_ms);
let cost = c
.billing
.as_ref()
@@ -152,7 +153,7 @@ mod tests {
let conclusion = Conclusion {
timestamp: chrono::Utc::now(),
status: StageOutcome::Succeeded,
- duration_ms: 12345,
+ timing: fabro_types::RunTiming::wall_only(12345),
failure: None,
final_git_commit_sha: None,
stages: vec![],
@@ -177,7 +178,7 @@ mod tests {
);
assert_eq!(json["run_id"], run_id.to_string());
assert_eq!(json["status"], "succeeded");
- assert_eq!(json["duration_ms"], 12345);
+ assert_eq!(json["timing"]["wall_time_ms"], 12345);
assert_eq!(json["total_usd_micros"], 420_000);
}
@@ -193,7 +194,7 @@ mod tests {
);
assert_eq!(json["run_id"], run_id.to_string());
assert_eq!(json["status"], "failed");
- assert!(json.get("duration_ms").is_none());
+ assert!(json.get("timing").is_none());
assert!(json.get("total_usd_micros").is_none());
}
@@ -211,7 +212,7 @@ mod tests {
status: StageOutcome::Failed {
retry_requested: false,
},
- duration_ms: 500,
+ timing: fabro_types::RunTiming::wall_only(500),
failure: Some(RunFailure {
reason: FailureReason::WorkflowError,
detail: FailureDetail::new("error", FailureCategory::Deterministic),
@@ -230,7 +231,7 @@ mod tests {
Some(&conclusion),
);
assert!(json.get("total_usd_micros").is_none());
- assert_eq!(json["duration_ms"], 500);
+ assert_eq!(json["timing"]["wall_time_ms"], 500);
}
#[test]
@@ -240,7 +241,7 @@ mod tests {
let conclusion = Conclusion {
timestamp: chrono::Utc::now(),
status: StageOutcome::Succeeded,
- duration_ms: 8000,
+ timing: fabro_types::RunTiming::wall_only(8000),
failure: None,
final_git_commit_sha: None,
stages: vec![],
diff --git a/lib/crates/fabro-cli/src/commands/runs/list.rs b/lib/crates/fabro-cli/src/commands/runs/list.rs
index 93ccab216..e545efd67 100644
--- a/lib/crates/fabro-cli/src/commands/runs/list.rs
+++ b/lib/crates/fabro-cli/src/commands/runs/list.rs
@@ -54,7 +54,7 @@ pub(crate) async fn list_command(
"status": run.status(),
"start_time": run.start_time(),
"labels": run.labels(),
- "duration_ms": run.duration_ms(),
+ "wall_time_ms": run.wall_time_ms(),
"total_usd_micros": run.total_usd_micros(),
"source_directory": run.source_directory(),
"repo_origin_url": run.repo_origin_url(),
@@ -107,7 +107,7 @@ pub(crate) async fn list_command(
let rows: Vec> = display_runs
.iter()
.map(|run| {
- let duration_display = match run.duration_ms() {
+ let duration_display = match run.wall_time_ms() {
Some(ms) => format_duration_ms(ms),
None => match run.start_time_dt() {
Some(start) => {
diff --git a/lib/crates/fabro-cli/src/server_runs.rs b/lib/crates/fabro-cli/src/server_runs.rs
index 2a4b20543..adb2f9566 100644
--- a/lib/crates/fabro-cli/src/server_runs.rs
+++ b/lib/crates/fabro-cli/src/server_runs.rs
@@ -77,8 +77,8 @@ impl ServerRunInfo {
&self.run.labels
}
- pub(crate) fn duration_ms(&self) -> Option {
- self.run.timestamps.duration_ms
+ pub(crate) fn wall_time_ms(&self) -> Option {
+ self.run.timing.as_ref().map(|t| t.wall_time_ms)
}
pub(crate) fn total_usd_micros(&self) -> Option {
diff --git a/lib/crates/fabro-cli/tests/it/cmd/attach.rs b/lib/crates/fabro-cli/tests/it/cmd/attach.rs
index 0995ae15d..285793b9d 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/attach.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/attach.rs
@@ -1147,13 +1147,18 @@ fn attach_json_errors_without_prompting_for_human_input() {
"internal.run_id": "[ULID]",
"internal.thread_id": null
},
- "duration_ms": "[DURATION_MS]",
"index": 0,
"max_attempts": 1,
"node_visits": {
"start": 1
},
- "status": "succeeded"
+ "status": "succeeded",
+ "timing": {
+ "active_time_ms": 0,
+ "inference_time_ms": 0,
+ "tool_time_ms": 0,
+ "wall_time_ms": 0
+ }
},
"run_id": "[ULID]",
"stage_id": "start@1",
diff --git a/lib/crates/fabro-cli/tests/it/cmd/inspect.rs b/lib/crates/fabro-cli/tests/it/cmd/inspect.rs
index 8d212c4b1..f060a2979 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/inspect.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/inspect.rs
@@ -356,7 +356,7 @@ fn inspect_completed_run_shows_run_start_conclusion_checkpoint() {
},
"conclusion": {
"status": "succeeded",
- "duration_ms": "[DURATION_MS]",
+ "timing": "[TIMING]",
"stage_count": null
},
"checkpoint": {
@@ -428,7 +428,7 @@ fn inspect_completed_run_reads_store_without_disk_metadata_files() {
},
"conclusion": {
"status": "succeeded",
- "duration_ms": "[DURATION_MS]",
+ "timing": "[TIMING]",
"stage_count": null
},
"checkpoint": {
@@ -487,7 +487,7 @@ fn inspect_git_backed_run_exposes_checkpoint_and_sandbox_state() {
},
"conclusion": {
"status": "succeeded",
- "duration_ms": "[DURATION_MS]",
+ "timing": "[TIMING]",
"final_git_commit_sha": "[SHA]",
"stage_count": null
},
diff --git a/lib/crates/fabro-cli/tests/it/cmd/run.rs b/lib/crates/fabro-cli/tests/it/cmd/run.rs
index c86788e4b..464c7cb95 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/run.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/run.rs
@@ -57,7 +57,7 @@ fn remote_run_state_response(run_id: &str) -> serde_json::Value {
state["conclusion"] = serde_json::json!({
"timestamp": "2026-04-05T12:00:01Z",
"status": "succeeded",
- "duration_ms": 12,
+ "timing": {"wall_time_ms": 12, "inference_time_ms": 0, "tool_time_ms": 0, "active_time_ms": 0},
"stages": [],
"billing": null,
"total_retries": 0,
@@ -74,7 +74,7 @@ fn run_completed_event(run_id: &str) -> serde_json::Value {
"run_id": run_id,
"ts": "2026-04-05T12:00:01Z",
"properties": {
- "duration_ms": 12,
+ "timing": {"wall_time_ms": 12, "inference_time_ms": 0, "tool_time_ms": 0, "active_time_ms": 0},
"artifact_count": 0,
"status": "succeeded",
"reason": "completed"
diff --git a/lib/crates/fabro-cli/tests/it/cmd/support.rs b/lib/crates/fabro-cli/tests/it/cmd/support.rs
index 660d5534b..e2ab6ef22 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/support.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/support.rs
@@ -174,10 +174,9 @@ pub(crate) fn remote_run_summary_json(
"created_at": timestamp,
"started_at": timestamp,
"last_event_at": null,
- "completed_at": null,
- "duration_ms": null,
- "elapsed_secs": null
+ "completed_at": null
},
+ "timing": null,
"billing": null,
"diff": null,
"pull_request": null,
@@ -1108,7 +1107,7 @@ async fn append_seeded_simple_completion_events(
None,
"run.completed",
serde_json::json!({
- "duration_ms": 123,
+ "timing": {"wall_time_ms": 123, "inference_time_ms": 0, "tool_time_ms": 0, "active_time_ms": 0},
"artifact_count": 0,
"status": "succeeded",
"reason": "completed",
@@ -1274,7 +1273,7 @@ async fn append_seeded_git_completion_events(
None,
"run.completed",
serde_json::json!({
- "duration_ms": 456,
+ "timing": {"wall_time_ms": 456, "inference_time_ms": 0, "tool_time_ms": 0, "active_time_ms": 0},
"artifact_count": 0,
"status": "succeeded",
"reason": "completed",
@@ -1335,7 +1334,7 @@ async fn append_seeded_git_noop_events(
None,
"run.completed",
serde_json::json!({
- "duration_ms": 123,
+ "timing": {"wall_time_ms": 123, "inference_time_ms": 0, "tool_time_ms": 0, "active_time_ms": 0},
"artifact_count": 0,
"status": "succeeded",
"reason": "completed",
@@ -1395,7 +1394,7 @@ async fn append_seeded_artifact_run_events(
None,
"run.completed",
serde_json::json!({
- "duration_ms": 123,
+ "timing": {"wall_time_ms": 123, "inference_time_ms": 0, "tool_time_ms": 0, "active_time_ms": 0},
"artifact_count": 6,
"status": "succeeded",
"reason": "completed",
@@ -1544,7 +1543,7 @@ fn test_labels(context: &TestContext) -> Vec {
fn stage_completed_properties(index: usize, response: Option<&str>) -> serde_json::Value {
serde_json::json!({
"index": index,
- "duration_ms": 1,
+ "timing": {"wall_time_ms": 1, "inference_time_ms": 0, "tool_time_ms": 0, "active_time_ms": 0},
"status": "succeeded",
"preferred_label": null,
"suggested_next_ids": [],
@@ -1741,7 +1740,7 @@ pub(crate) fn compact_inspect(output: &Output) -> Value {
"conclusion": conclusion.as_object().map(|_| {
serde_json::json!({
"status": conclusion["status"],
- "duration_ms": "[DURATION_MS]",
+ "timing": "[TIMING]",
"stage_count": conclusion["stages"].as_array().map(|stages| stages.len()),
})
}),
@@ -1802,7 +1801,7 @@ pub(crate) fn compact_git_inspect(output: &Output) -> Value {
"conclusion": conclusion.as_object().map(|_| {
serde_json::json!({
"status": conclusion["status"],
- "duration_ms": "[DURATION_MS]",
+ "timing": "[TIMING]",
"final_git_commit_sha": "[SHA]",
"stage_count": conclusion["stages"].as_array().map(|stages| stages.len()),
})
diff --git a/lib/crates/fabro-cli/tests/it/cmd/wait.rs b/lib/crates/fabro-cli/tests/it/cmd/wait.rs
index dffa0b799..792b78284 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/wait.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/wait.rs
@@ -96,8 +96,20 @@ fn wait_completed_run_json_outputs_status_and_duration() {
let run = setup_seeded_completed_dry_run(&context);
let mut filters = context.filters();
filters.push((
- r#""duration_ms":\s*\d+"#.to_string(),
- r#""duration_ms": [DURATION_MS]"#.to_string(),
+ r#""wall_time_ms":\s*\d+"#.to_string(),
+ r#""wall_time_ms": [WALL_TIME_MS]"#.to_string(),
+ ));
+ filters.push((
+ r#""inference_time_ms":\s*\d+"#.to_string(),
+ r#""inference_time_ms": [INFERENCE_TIME_MS]"#.to_string(),
+ ));
+ filters.push((
+ r#""tool_time_ms":\s*\d+"#.to_string(),
+ r#""tool_time_ms": [TOOL_TIME_MS]"#.to_string(),
+ ));
+ filters.push((
+ r#""active_time_ms":\s*\d+"#.to_string(),
+ r#""active_time_ms": [ACTIVE_TIME_MS]"#.to_string(),
));
let mut cmd = context.command();
cmd.args(["wait", "--json", &run.run_id]);
@@ -109,7 +121,12 @@ fn wait_completed_run_json_outputs_status_and_duration() {
{
"run_id": "[ULID]",
"status": "succeeded",
- "duration_ms": [DURATION_MS]
+ "timing": {
+ "wall_time_ms": [WALL_TIME_MS],
+ "inference_time_ms": [INFERENCE_TIME_MS],
+ "tool_time_ms": [TOOL_TIME_MS],
+ "active_time_ms": [ACTIVE_TIME_MS]
+ }
}
----- stderr -----
"###);
diff --git a/lib/crates/fabro-cli/tests/it/scenario/smoke.rs b/lib/crates/fabro-cli/tests/it/scenario/smoke.rs
index 0edc31c18..08e56acb8 100644
--- a/lib/crates/fabro-cli/tests/it/scenario/smoke.rs
+++ b/lib/crates/fabro-cli/tests/it/scenario/smoke.rs
@@ -21,7 +21,7 @@ fn run_sse_body(run_id: &str) -> String {
"run_id": run_id,
"ts": "2026-04-05T12:00:01Z",
"properties": {
- "duration_ms": 12,
+ "timing": {"wall_time_ms": 12, "inference_time_ms": 0, "tool_time_ms": 0, "active_time_ms": 0},
"artifact_count": 0,
"status": "succeeded",
"reason": "completed"
diff --git a/lib/crates/fabro-cli/tests/it/workflow/full_stack.rs b/lib/crates/fabro-cli/tests/it/workflow/full_stack.rs
index 3ba6c9caa..e70dea83a 100644
--- a/lib/crates/fabro-cli/tests/it/workflow/full_stack.rs
+++ b/lib/crates/fabro-cli/tests/it/workflow/full_stack.rs
@@ -37,8 +37,8 @@ fn scenario_full_stack(sandbox: &str) {
"conclusion: {conclusion}"
);
assert!(
- conclusion["duration_ms"].as_u64().unwrap_or(0) > 0,
- "duration_ms should be > 0"
+ conclusion["timing"]["wall_time_ms"].as_u64().unwrap_or(0) > 0,
+ "timing.wall_time_ms should be > 0"
);
// RunSpec should have key fields
diff --git a/lib/crates/fabro-core/src/executor.rs b/lib/crates/fabro-core/src/executor.rs
index 99fa5bb52..19cf142a1 100644
--- a/lib/crates/fabro-core/src/executor.rs
+++ b/lib/crates/fabro-core/src/executor.rs
@@ -14,9 +14,37 @@ use crate::lifecycle::{
AttemptContext, AttemptResultContext, EdgeContext, EdgeDecision, NodeDecision, NoopLifecycle,
RunLifecycle,
};
-use crate::outcome::{NodeResult, NodeResultExt, Outcome};
+use crate::outcome::{NodeResult, NodeResultExt, Outcome, OutcomeMeta};
use crate::state::ExecutionState;
+/// Build a [`NodeResult`] from an attempt outcome, pulling the inference and
+/// tool breakdown from `outcome.timing` when handlers populated it. The wall
+/// time comes from the executor's stopwatch since that is the source of
+/// authoritative per-attempt clock time.
+fn node_result_from_outcome(
+ outcome: Outcome,
+ wall_time: std::time::Duration,
+ attempts: u32,
+ max_attempts: u32,
+) -> NodeResult {
+ let inference_time = outcome
+ .timing
+ .map(|t| std::time::Duration::from_millis(t.inference_time_ms))
+ .unwrap_or_default();
+ let tool_time = outcome
+ .timing
+ .map(|t| std::time::Duration::from_millis(t.tool_time_ms))
+ .unwrap_or_default();
+ NodeResult::new(
+ outcome,
+ wall_time,
+ inference_time,
+ tool_time,
+ attempts,
+ max_attempts,
+ )
+}
+
#[derive(Default)]
pub struct ExecutorOptions {
pub cancel_token: Option,
@@ -269,9 +297,9 @@ impl Executor {
graph: &G,
) -> Result> {
let policy = self.handler.retry_policy(node, graph);
- let start = Instant::now();
for attempt in 1..=policy.max_attempts {
+ let attempt_start = Instant::now();
let attempt_ctx = AttemptContext {
node,
attempt,
@@ -288,8 +316,12 @@ impl Executor {
match self.handler.execute(node, &state.context, graph).await {
Ok(outcome) if outcome.status.retry_requested() && can_retry => {
let delay = policy.backoff.delay_for_attempt(attempt);
- let result =
- NodeResult::new(outcome, start.elapsed(), attempt, policy.max_attempts);
+ let result = node_result_from_outcome(
+ outcome,
+ attempt_start.elapsed(),
+ attempt,
+ policy.max_attempts,
+ );
let ctx = AttemptResultContext {
node,
result: &result,
@@ -302,9 +334,9 @@ impl Executor {
}
Ok(outcome) if outcome.status.retry_requested() => {
let final_outcome = self.handler.on_retries_exhausted(node, outcome);
- let result = NodeResult::new(
+ let result = node_result_from_outcome(
final_outcome,
- start.elapsed(),
+ attempt_start.elapsed(),
attempt,
policy.max_attempts,
);
@@ -319,8 +351,12 @@ impl Executor {
return Ok(result);
}
Ok(outcome) => {
- let result =
- NodeResult::new(outcome, start.elapsed(), attempt, policy.max_attempts);
+ let result = node_result_from_outcome(
+ outcome,
+ attempt_start.elapsed(),
+ attempt,
+ policy.max_attempts,
+ );
let ctx = AttemptResultContext {
node,
result: &result,
@@ -333,8 +369,12 @@ impl Executor {
}
Err(e) if can_retry && e.is_retryable() => {
let delay = policy.backoff.delay_for_attempt(attempt);
- let fail_result =
- NodeResult::from_error(&e, start.elapsed(), attempt, policy.max_attempts);
+ let fail_result = NodeResult::from_error(
+ &e,
+ attempt_start.elapsed(),
+ attempt,
+ policy.max_attempts,
+ );
let ctx = AttemptResultContext {
node,
result: &fail_result,
@@ -348,8 +388,12 @@ impl Executor {
Err(e @ Error::Handler { .. }) => {
// Convert handler failures to fail outcomes so routing continues.
let outcome = e.to_fail_outcome();
- let result =
- NodeResult::new(outcome, start.elapsed(), attempt, policy.max_attempts);
+ let result = node_result_from_outcome(
+ outcome,
+ attempt_start.elapsed(),
+ attempt,
+ policy.max_attempts,
+ );
let ctx = AttemptResultContext {
node,
result: &result,
@@ -1289,6 +1333,80 @@ mod tests {
assert_eq!(log, vec![(1, true), (2, false)]);
}
+ #[tokio::test]
+ async fn executor_retry_attempt_wall_time_excludes_prior_attempts_and_backoff() {
+ let wall_times = Arc::new(Mutex::new(Vec::::new()));
+
+ struct WallTimeTracker(Arc>>);
+ #[async_trait]
+ impl RunLifecycle for WallTimeTracker {
+ async fn after_attempt(
+ &self,
+ ctx: &AttemptResultContext<'_, TestGraph>,
+ _s: &ExecutionState,
+ ) -> Result<()> {
+ self.0.lock().unwrap().push(ctx.result.wall_time);
+ Ok(())
+ }
+ }
+
+ struct SlowRetryThenSuccess(AtomicU32);
+ #[async_trait]
+ impl NodeHandler for SlowRetryThenSuccess {
+ async fn execute(
+ &self,
+ _n: &TestNode,
+ _c: &Context,
+ _g: &TestGraph,
+ ) -> Result {
+ sleep(Duration::from_millis(5)).await;
+ let call = self.0.fetch_add(1, Ordering::Relaxed);
+ if call == 0 {
+ Ok(Outcome {
+ status: StageOutcome::Failed {
+ retry_requested: true,
+ },
+ ..Outcome::default()
+ })
+ } else {
+ Ok(Outcome::success())
+ }
+ }
+
+ fn retry_policy(&self, _n: &TestNode, _g: &TestGraph) -> RetryPolicy {
+ RetryPolicy {
+ max_attempts: 2,
+ backoff: BackoffPolicy {
+ initial_delay: Duration::from_millis(500),
+ factor: 1.0,
+ max_delay: Duration::from_millis(500),
+ jitter: false,
+ },
+ }
+ }
+ }
+
+ let g = linear_graph(&["start", "end"]);
+ let state = ExecutionState::new(&g).unwrap();
+ let executor =
+ ExecutorBuilder::new(Arc::new(SlowRetryThenSuccess(AtomicU32::new(0)))
+ as Arc>)
+ .lifecycle(Box::new(WallTimeTracker(Arc::clone(&wall_times))))
+ .build();
+
+ executor.run(&g, state).await.unwrap();
+
+ let wall_times = wall_times.lock().unwrap().clone();
+ assert_eq!(wall_times.len(), 2);
+ for wall_time in wall_times {
+ assert!(wall_time >= Duration::from_millis(5));
+ assert!(
+ wall_time < Duration::from_millis(300),
+ "attempt wall time should not include retry backoff or prior attempts: {wall_time:?}"
+ );
+ }
+ }
+
#[tokio::test]
async fn executor_retry_lifecycle_before_attempt_skip_stops_retry() {
let call_count = Arc::new(std::sync::atomic::AtomicU32::new(0));
diff --git a/lib/crates/fabro-core/src/lifecycle.rs b/lib/crates/fabro-core/src/lifecycle.rs
index f122c426c..6fc61c087 100644
--- a/lib/crates/fabro-core/src/lifecycle.rs
+++ b/lib/crates/fabro-core/src/lifecycle.rs
@@ -570,7 +570,14 @@ mod tests {
let g = linear_graph(&["start", "end"]);
let state = ExecutionState::new(&g).unwrap();
let node = g.get_node("start").unwrap();
- let result = NodeResult::new(Outcome::success(), Duration::ZERO, 1, 1);
+ let result = NodeResult::new(
+ Outcome::success(),
+ Duration::ZERO,
+ Duration::ZERO,
+ Duration::ZERO,
+ 1,
+ 1,
+ );
let ctx = AttemptResultContext {
node: &node,
result: &result,
@@ -667,7 +674,14 @@ mod tests {
let g = linear_graph(&["start", "end"]);
let state = ExecutionState::new(&g).unwrap();
let node = g.get_node("start").unwrap();
- let mut result = NodeResult::new(Outcome::success(), Duration::ZERO, 1, 1);
+ let mut result = NodeResult::new(
+ Outcome::success(),
+ Duration::ZERO,
+ Duration::ZERO,
+ Duration::ZERO,
+ 1,
+ 1,
+ );
lc.after_node(&node, &mut result, &state).await.unwrap();
let calls = log.lock().unwrap().clone();
assert_eq!(calls, vec!["a:after_node", "b:after_node"]);
@@ -683,7 +697,14 @@ mod tests {
let g = linear_graph(&["start", "end"]);
let state = ExecutionState::new(&g).unwrap();
let node = g.get_node("start").unwrap();
- let result = NodeResult::new(Outcome::success(), Duration::ZERO, 1, 1);
+ let result = NodeResult::new(
+ Outcome::success(),
+ Duration::ZERO,
+ Duration::ZERO,
+ Duration::ZERO,
+ 1,
+ 1,
+ );
lc.after_record(&node, &result, &state).await.unwrap();
let calls = log.lock().unwrap().clone();
assert_eq!(calls, vec!["a:after_record", "b:after_record"]);
diff --git a/lib/crates/fabro-core/src/outcome.rs b/lib/crates/fabro-core/src/outcome.rs
index 53d590fbf..794efae79 100644
--- a/lib/crates/fabro-core/src/outcome.rs
+++ b/lib/crates/fabro-core/src/outcome.rs
@@ -7,14 +7,16 @@ pub use fabro_types::outcome::{
use crate::error::Error;
pub trait NodeResultExt {
- fn from_error(error: &Error, duration: Duration, attempts: u32, max_attempts: u32) -> Self;
+ fn from_error(error: &Error, wall_time: Duration, attempts: u32, max_attempts: u32) -> Self;
}
impl NodeResultExt for NodeResult {
- fn from_error(error: &Error, duration: Duration, attempts: u32, max_attempts: u32) -> Self {
+ fn from_error(error: &Error, wall_time: Duration, attempts: u32, max_attempts: u32) -> Self {
Self {
outcome: error.to_fail_outcome(),
- duration,
+ wall_time,
+ inference_time: Duration::ZERO,
+ tool_time: Duration::ZERO,
attempts,
max_attempts,
}
diff --git a/lib/crates/fabro-core/src/state.rs b/lib/crates/fabro-core/src/state.rs
index af37e89dd..ca3b8e56b 100644
--- a/lib/crates/fabro-core/src/state.rs
+++ b/lib/crates/fabro-core/src/state.rs
@@ -111,7 +111,14 @@ mod tests {
fn run_state_record_updates_all_fields() {
let g = linear_graph(&["start", "end"]);
let mut state = ExecutionState::<()>::new(&g).unwrap();
- let result = NodeResult::new(Outcome::success(), Duration::from_millis(50), 2, 3);
+ let result = NodeResult::new(
+ Outcome::success(),
+ Duration::from_millis(50),
+ Duration::ZERO,
+ Duration::ZERO,
+ 2,
+ 3,
+ );
state.record("start", &result);
assert_eq!(state.completed_nodes, vec!["start"]);
@@ -126,7 +133,14 @@ mod tests {
let mut state = ExecutionState::<()>::new(&g).unwrap();
let mut outcome = Outcome::success();
outcome.context_updates.insert("key".into(), json!("value"));
- let result = NodeResult::new(outcome, Duration::ZERO, 1, 1);
+ let result = NodeResult::new(
+ outcome,
+ Duration::ZERO,
+ Duration::ZERO,
+ Duration::ZERO,
+ 1,
+ 1,
+ );
state.record("start", &result);
assert_eq!(state.context.get("key"), Some(json!("value")));
}
@@ -155,7 +169,14 @@ mod tests {
state.increment_visits("work");
state.record(
"start",
- &NodeResult::new(Outcome::success(), Duration::ZERO, 1, 1),
+ &NodeResult::new(
+ Outcome::success(),
+ Duration::ZERO,
+ Duration::ZERO,
+ Duration::ZERO,
+ 1,
+ 1,
+ ),
);
state.advance("work");
diff --git a/lib/crates/fabro-dump/src/lib.rs b/lib/crates/fabro-dump/src/lib.rs
index 2a023f2f2..8345b84a4 100644
--- a/lib/crates/fabro-dump/src/lib.rs
+++ b/lib/crates/fabro-dump/src/lib.rs
@@ -548,7 +548,7 @@ mod tests {
.single()
.unwrap(),
status: StageOutcome::Succeeded,
- duration_ms: 5,
+ timing: fabro_types::RunTiming::wall_only(5),
failure: None,
final_git_commit_sha: Some("abc123".to_string()),
stages: Vec::new(),
diff --git a/lib/crates/fabro-server/src/demo/mod.rs b/lib/crates/fabro-server/src/demo/mod.rs
index f42fffff2..d3f2463db 100644
--- a/lib/crates/fabro-server/src/demo/mod.rs
+++ b/lib/crates/fabro-server/src/demo/mod.rs
@@ -1106,7 +1106,8 @@ mod runs {
let run_id = RunId::with_timestamp(created_at, sequence);
let source_directory = Some(format!("/demo/{repo_name}"));
let repo_origin_url = Some(format!("https://github.com/demo/{repo_name}.git"));
- let duration_ms = elapsed_secs.and_then(duration_ms_from_secs);
+ let wall_time_ms = elapsed_secs.and_then(duration_ms_from_secs);
+ let timing = wall_time_ms.map(fabro_types::RunTiming::wall_only);
Run {
id: run_id,
parent_id: None,
@@ -1145,9 +1146,8 @@ mod runs {
started_at: Some(created_at),
last_event_at: Some(created_at),
completed_at: Some(created_at),
- duration_ms,
- elapsed_secs,
},
+ timing,
billing: total_usd_micros.map(|total_usd_micros| RunBillingSummary {
total_usd_micros: Some(total_usd_micros),
}),
@@ -1350,7 +1350,7 @@ mod runs {
&StageId::new("detect-drift", 1),
"Detect Drift",
StageState::Succeeded,
- Some(72.0),
+ Some(72_000),
None,
StageHandler::Command,
),
@@ -1358,7 +1358,7 @@ mod runs {
&StageId::new("propose-changes", 1),
"Propose Changes",
StageState::Succeeded,
- Some(154.0),
+ Some(154_000),
None,
StageHandler::Agent,
),
@@ -1366,7 +1366,7 @@ mod runs {
&StageId::new("review-changes", 1),
"Review Changes",
StageState::Succeeded,
- Some(45.0),
+ Some(45_000),
None,
StageHandler::Agent,
),
@@ -1374,7 +1374,7 @@ mod runs {
&StageId::new("apply-changes", 1),
"Apply Changes",
StageState::Succeeded,
- Some(118.0),
+ Some(118_000),
None,
StageHandler::Command,
),
@@ -1512,15 +1512,15 @@ mod runs {
RunBilling {
stages: vec![
RunBillingStage {
- stage: BillingStageRef {
+ stage: BillingStageRef {
id: "detect-drift".into(),
name: "Detect Drift".into(),
},
- model: Some(billing_model(
+ model: Some(billing_model(
fabro_model::ProviderId::anthropic(),
"claude-opus-4-6",
)),
- billing: BilledTokenCounts {
+ billing: BilledTokenCounts {
cache_read_tokens: 0,
cache_write_tokens: 0,
input_tokens: 12480,
@@ -1529,20 +1529,20 @@ mod runs {
total_tokens: 15690,
total_usd_micros: Some(480_000),
},
- runtime_secs: 72.0,
- started_at: None,
- state: Some(StageState::Succeeded),
+ timing: fabro_types::StageTiming::wall_only(72_000),
+ started_at: None,
+ state: Some(StageState::Succeeded),
},
RunBillingStage {
- stage: BillingStageRef {
+ stage: BillingStageRef {
id: "propose-changes".into(),
name: "Propose Changes".into(),
},
- model: Some(billing_model(
+ model: Some(billing_model(
fabro_model::ProviderId::gemini(),
"gemini-3.1-pro-preview",
)),
- billing: BilledTokenCounts {
+ billing: BilledTokenCounts {
cache_read_tokens: 0,
cache_write_tokens: 0,
input_tokens: 28640,
@@ -1551,20 +1551,20 @@ mod runs {
total_tokens: 37390,
total_usd_micros: Some(720_000),
},
- runtime_secs: 154.0,
- started_at: None,
- state: Some(StageState::Succeeded),
+ timing: fabro_types::StageTiming::wall_only(154_000),
+ started_at: None,
+ state: Some(StageState::Succeeded),
},
RunBillingStage {
- stage: BillingStageRef {
+ stage: BillingStageRef {
id: "review-changes".into(),
name: "Review Changes".into(),
},
- model: Some(billing_model(
+ model: Some(billing_model(
fabro_model::ProviderId::openai(),
"gpt-5.3-codex",
)),
- billing: BilledTokenCounts {
+ billing: BilledTokenCounts {
cache_read_tokens: 0,
cache_write_tokens: 0,
input_tokens: 9120,
@@ -1573,20 +1573,20 @@ mod runs {
total_tokens: 11760,
total_usd_micros: Some(190_000),
},
- runtime_secs: 45.0,
- started_at: None,
- state: Some(StageState::Succeeded),
+ timing: fabro_types::StageTiming::wall_only(45_000),
+ started_at: None,
+ state: Some(StageState::Succeeded),
},
RunBillingStage {
- stage: BillingStageRef {
+ stage: BillingStageRef {
id: "apply-changes".into(),
name: "Apply Changes".into(),
},
- model: Some(billing_model(
+ model: Some(billing_model(
fabro_model::ProviderId::anthropic(),
"claude-opus-4-6",
)),
- billing: BilledTokenCounts {
+ billing: BilledTokenCounts {
cache_read_tokens: 0,
cache_write_tokens: 0,
input_tokens: 21300,
@@ -1595,15 +1595,15 @@ mod runs {
total_tokens: 27780,
total_usd_micros: Some(870_000),
},
- runtime_secs: 118.0,
- started_at: None,
- state: Some(StageState::Running),
+ timing: fabro_types::StageTiming::wall_only(118_000),
+ started_at: None,
+ state: Some(StageState::Running),
},
],
totals: RunBillingTotals {
cache_read_tokens: 0,
cache_write_tokens: 0,
- runtime_secs: 389.0,
+ timing: fabro_types::RunTiming::wall_only(389_000),
input_tokens: 71540,
output_tokens: 21080,
reasoning_tokens: 0,
@@ -1987,7 +1987,7 @@ mod billing {
input_tokens: 643_860,
output_tokens: 189_720,
reasoning_tokens: 0,
- runtime_secs: 3_501.0,
+ timing: fabro_types::RunTiming::wall_only(3_501_000),
total_tokens: 833_580,
total_usd_micros: Some(20_340_000),
},
diff --git a/lib/crates/fabro-server/src/run_files.rs b/lib/crates/fabro-server/src/run_files.rs
index d93c02d69..835b386c4 100644
--- a/lib/crates/fabro-server/src/run_files.rs
+++ b/lib/crates/fabro-server/src/run_files.rs
@@ -2390,7 +2390,7 @@ index 1111111..2222222 160000
projection.conclusion = Some(fabro_types::Conclusion {
timestamp: chrono::Utc::now(),
status: fabro_types::StageOutcome::Succeeded,
- duration_ms: 1,
+ timing: fabro_types::RunTiming::wall_only(1),
failure: None,
final_git_commit_sha: None,
stages: Vec::new(),
diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs
index 7f2fb6a3a..d016c9755 100644
--- a/lib/crates/fabro-server/src/server.rs
+++ b/lib/crates/fabro-server/src/server.rs
@@ -252,9 +252,9 @@ struct ModelBillingTotals {
/// In-memory aggregate billing counters, reset on server restart.
#[derive(Default)]
struct BillingAccumulator {
- total_runs: i64,
- total_runtime_secs: f64,
- by_model: HashMap,
+ total_runs: i64,
+ total_timing: fabro_types::RunTiming,
+ by_model: HashMap,
}
pub(crate) type RegistryFactoryOverride =
@@ -703,7 +703,7 @@ fn accumulate_billing_rollup(
rollup: &fabro_workflow::ProjectionBillingRollup,
) {
accumulator.total_runs += 1;
- accumulator.total_runtime_secs += rollup.runtime_ms as f64 / 1000.0;
+ accumulator.total_timing = accumulator.total_timing.saturating_add(&rollup.timing);
for model in &rollup.by_model {
let entry = accumulator.by_model.entry(model.model.clone()).or_default();
entry.stages += model.stages;
@@ -715,7 +715,7 @@ pub(crate) fn run_stage_from_stage_id(
stage_id: &StageId,
name: impl Into,
status: StageState,
- duration_secs: Option,
+ wall_time_ms: Option,
started_at: Option>,
handler: StageHandler,
) -> RunStage {
@@ -724,7 +724,7 @@ pub(crate) fn run_stage_from_stage_id(
name: name.into(),
handler,
status,
- duration_secs,
+ wall_time_ms,
node_id: stage_id.node_id().to_string(),
visit: std::num::NonZeroU32::new(stage_id.visit())
.expect("StageId stores a non-zero visit"),
@@ -2237,7 +2237,13 @@ pub(crate) async fn reconcile_incomplete_runs_on_startup(
"Fabro server restarted before the run reached a terminal state.".to_string(),
);
let failure_event = workflow_event::Event::workflow_run_failed_from_error(
- &error, 0, reason, None, None, None, None,
+ &error,
+ fabro_types::RunTiming::default(),
+ reason,
+ None,
+ None,
+ None,
+ None,
);
workflow_event::append_event(&run_store, &summary.id, &failure_event).await?;
reconciled += 1;
@@ -2282,7 +2288,13 @@ async fn persist_shutdown_run_failures(
"Fabro server shut down before the run reached a terminal state.".to_string(),
);
let failure_event = workflow_event::Event::workflow_run_failed_from_error(
- &error, 0, reason, None, None, None, None,
+ &error,
+ fabro_types::RunTiming::default(),
+ reason,
+ None,
+ None,
+ None,
+ None,
);
workflow_event::append_event(&run_store, &run_id, &failure_event).await?;
}
@@ -2354,7 +2366,7 @@ async fn persist_cancelled_run_status(state: &AppState, run_id: RunId) -> anyhow
let failure_event = workflow_event::Event::workflow_run_failed_from_error(
&WorkflowError::Cancelled,
- 0,
+ fabro_types::RunTiming::default(),
FailureReason::Cancelled,
None,
None,
@@ -2390,7 +2402,7 @@ async fn fail_run_before_execution(
Ok(run_store) => {
let failure_event = workflow_event::Event::workflow_run_failed_from_error(
&WorkflowError::engine(message.clone()),
- 0,
+ fabro_types::RunTiming::default(),
reason,
None,
None,
@@ -2704,7 +2716,13 @@ async fn append_worker_exit_failure(
format!("Worker exited before emitting a terminal run event: {wait_status}"),
);
let failure_event = workflow_event::Event::workflow_run_failed_from_error(
- &error, 0, reason, None, None, None, None,
+ &error,
+ fabro_types::RunTiming::default(),
+ reason,
+ None,
+ None,
+ None,
+ None,
);
if let Err(err) = workflow_event::append_event(run_store, &run_id, &failure_event).await {
@@ -3365,7 +3383,7 @@ async fn execute_run_subprocess(state: Arc, run_id: RunId) {
let message = format!("Failed to spawn worker: {err}");
let failure_event = workflow_event::Event::workflow_run_failed_from_error(
&WorkflowError::engine_with_anyhow("Failed to spawn worker", err),
- 0,
+ fabro_types::RunTiming::default(),
FailureReason::LaunchFailed,
None,
None,
@@ -3385,7 +3403,7 @@ async fn execute_run_subprocess(state: Arc, run_id: RunId) {
let _ = child.start_kill();
let failure_event = workflow_event::Event::workflow_run_failed_from_error(
&WorkflowError::engine(message.clone()),
- 0,
+ fabro_types::RunTiming::default(),
FailureReason::LaunchFailed,
None,
None,
@@ -3413,7 +3431,7 @@ async fn execute_run_subprocess(state: Arc, run_id: RunId) {
let _ = child.start_kill();
let failure_event = workflow_event::Event::workflow_run_failed_from_error(
&WorkflowError::engine(message.clone()),
- 0,
+ fabro_types::RunTiming::default(),
FailureReason::LaunchFailed,
None,
None,
@@ -3432,7 +3450,7 @@ async fn execute_run_subprocess(state: Arc, run_id: RunId) {
let _ = child.start_kill();
let failure_event = workflow_event::Event::workflow_run_failed_from_error(
&WorkflowError::engine(message.clone()),
- 0,
+ fabro_types::RunTiming::default(),
FailureReason::LaunchFailed,
None,
None,
@@ -3464,7 +3482,7 @@ async fn execute_run_subprocess(state: Arc, run_id: RunId) {
let _ = child.start_kill();
let failure_event = workflow_event::Event::workflow_run_failed_from_error(
&WorkflowError::engine_with_source("Worker wait failed", err),
- 0,
+ fabro_types::RunTiming::default(),
FailureReason::Terminated,
None,
None,
diff --git a/lib/crates/fabro-server/src/server/handler/billing.rs b/lib/crates/fabro-server/src/server/handler/billing.rs
index cbaf7cc3e..c384287cd 100644
--- a/lib/crates/fabro-server/src/server/handler/billing.rs
+++ b/lib/crates/fabro-server/src/server/handler/billing.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use chrono::{DateTime, Utc};
-use fabro_types::{RunProjection, StageHandler, StageProjection, StageState};
+use fabro_types::{RunProjection, StageHandler, StageProjection, StageState, StageTiming};
use super::super::{
ApiError, AppState, BillingByModel, BillingStageRef, IntoResponse, Json, ListResponse,
@@ -54,7 +54,7 @@ async fn list_run_stages(
stage_id,
stage_id.node_id().to_string(),
stage.effective_state(),
- stage.runtime_secs(now),
+ stage.live_wall_time_ms(now),
stage.started_at,
handler,
)
@@ -97,23 +97,25 @@ async fn get_run_billing(
.map(|stage| (stage.node_id.as_str(), stage))
.collect::>();
let live_rows = live_billing_rows(&projection, Utc::now());
- let runtime_secs = live_rows.iter().map(|row| row.runtime_secs).sum::();
+ let totals_timing = live_rows.iter().fold(StageTiming::default(), |acc, row| {
+ acc.saturating_add(&row.timing)
+ });
let stages = live_rows
.into_iter()
.map(|row| {
let rollup_stage = rollup_by_node.get(row.node_id.as_str());
RunBillingStage {
- billing: rollup_stage
+ billing: rollup_stage
.map(|stage| stage.billing.clone())
.unwrap_or_default(),
- model: rollup_stage.and_then(|stage| stage.model.as_ref()).cloned(),
- runtime_secs: row.runtime_secs,
- stage: BillingStageRef {
+ model: rollup_stage.and_then(|stage| stage.model.as_ref()).cloned(),
+ timing: row.timing,
+ stage: BillingStageRef {
id: row.node_id.clone(),
name: row.node_id,
},
- started_at: row.started_at,
- state: row.state,
+ started_at: row.started_at,
+ state: row.state,
}
})
.collect::>();
@@ -122,14 +124,14 @@ async fn get_run_billing(
by_model,
stages,
totals: RunBillingTotals {
- cache_read_tokens: rollup.totals.cache_read_tokens,
+ cache_read_tokens: rollup.totals.cache_read_tokens,
cache_write_tokens: rollup.totals.cache_write_tokens,
- input_tokens: rollup.totals.input_tokens,
- output_tokens: rollup.totals.output_tokens,
- reasoning_tokens: rollup.totals.reasoning_tokens,
- runtime_secs,
- total_tokens: rollup.totals.total_tokens,
- total_usd_micros: rollup.totals.total_usd_micros,
+ input_tokens: rollup.totals.input_tokens,
+ output_tokens: rollup.totals.output_tokens,
+ reasoning_tokens: rollup.totals.reasoning_tokens,
+ timing: totals_timing.into(),
+ total_tokens: rollup.totals.total_tokens,
+ total_usd_micros: rollup.totals.total_usd_micros,
},
};
@@ -138,7 +140,7 @@ async fn get_run_billing(
struct LiveBillingRow {
node_id: String,
- runtime_secs: f64,
+ timing: StageTiming,
started_at: Option>,
state: Option,
latest_visit: u32,
@@ -158,7 +160,7 @@ fn live_billing_rows(projection: &RunProjection, now: DateTime) -> Vec) -> Vec= row.latest_visit {
row.latest_visit = stage_id.visit();
@@ -178,16 +181,23 @@ fn live_billing_rows(projection: &RunProjection, now: DateTime) -> Vec) -> Option {
- stage
- .duration_ms
- .map(|ms| ms as f64 / 1000.0)
- .or_else(|| stage.runtime_secs(now))
+/// Per-visit timing for a stage. For terminal visits, the stored breakdown is
+/// used directly. For in-flight visits, fall back to the live wall-clock since
+/// `started_at` (no active breakdown yet — that is only finalized at terminal
+/// event time in v1).
+fn billing_stage_timing(stage: &StageProjection, now: DateTime) -> StageTiming {
+ if let Some(timing) = stage.timing {
+ return timing;
+ }
+ if let Some(live_wall) = stage.live_wall_time_ms(now) {
+ return StageTiming::wall_only(live_wall);
+ }
+ StageTiming::default()
}
fn stage_has_billing_row(stage: &StageProjection) -> bool {
stage.completion.is_some()
- || stage.duration_ms.is_some()
+ || stage.timing.is_some()
|| !stage.usage.is_zero()
|| stage.started_at.is_some()
}
diff --git a/lib/crates/fabro-server/src/server/handler/system.rs b/lib/crates/fabro-server/src/server/handler/system.rs
index fcc279119..adb25634c 100644
--- a/lib/crates/fabro-server/src/server/handler/system.rs
+++ b/lib/crates/fabro-server/src/server/handler/system.rs
@@ -541,7 +541,7 @@ async fn get_aggregate_billing(
output_tokens: total_billing.output_tokens,
reasoning_tokens: total_billing.reasoning_tokens,
runs: agg.total_runs,
- runtime_secs: agg.total_runtime_secs,
+ timing: agg.total_timing,
total_tokens: total_billing.total_tokens,
total_usd_micros: total_billing.total_usd_micros,
},
diff --git a/lib/crates/fabro-server/src/server/tests.rs b/lib/crates/fabro-server/src/server/tests.rs
index 52affb51e..3736b02ca 100644
--- a/lib/crates/fabro-server/src/server/tests.rs
+++ b/lib/crates/fabro-server/src/server/tests.rs
@@ -2743,7 +2743,7 @@ async fn persist_cancelled_run_status_ignores_already_terminal_runs() {
let run_id = fixtures::RUN_1;
create_durable_run_with_events(&state, run_id, &[
workflow_event::Event::WorkflowRunCompleted {
- duration_ms: 1000,
+ timing: fabro_types::RunTiming::wall_only(1000),
artifact_count: 0,
status: "succeeded".to_string(),
reason: SuccessReason::Completed,
@@ -2779,7 +2779,7 @@ async fn delete_terminal_managed_run_does_not_send_cancel_signal() {
let run_id = fixtures::RUN_1;
create_durable_run_with_events(&state, run_id, &[
workflow_event::Event::WorkflowRunCompleted {
- duration_ms: 1000,
+ timing: fabro_types::RunTiming::wall_only(1000),
artifact_count: 0,
status: "succeeded".to_string(),
reason: SuccessReason::Completed,
@@ -2889,7 +2889,7 @@ async fn list_run_stages_projects_retrying_until_completion() {
node_id: "setup".to_string(),
name: "Setup".to_string(),
index: 0,
- duration_ms: 5,
+ timing: fabro_types::StageTiming::wall_only(5),
status: "succeeded".to_string(),
preferred_label: None,
suggested_next_ids: Vec::new(),
@@ -2930,14 +2930,14 @@ async fn list_run_stages_projects_retrying_until_completion() {
"work",
1,
&workflow_event::Event::StageFailed {
- node_id: "work".to_string(),
- name: "Work".to_string(),
- index: 1,
- failure: FailureDetail::new("try again", FailureCategory::TransientInfra),
- will_retry: true,
- duration_ms: 10,
- billing: None,
- actor: None,
+ node_id: "work".to_string(),
+ name: "Work".to_string(),
+ index: 1,
+ failure: FailureDetail::new("try again", FailureCategory::TransientInfra),
+ will_retry: true,
+ timing: fabro_types::StageTiming::wall_only(10),
+ billing: None,
+ actor: None,
},
)
.await;
@@ -2981,7 +2981,7 @@ async fn list_run_stages_projects_retrying_until_completion() {
node_id: "work".to_string(),
name: "Work".to_string(),
index: 1,
- duration_ms: 25,
+ timing: fabro_types::StageTiming::wall_only(25),
status: "partially_succeeded".to_string(),
preferred_label: None,
suggested_next_ids: Vec::new(),
@@ -3111,7 +3111,7 @@ async fn list_run_stages_distinguishes_visits() {
node_id: "verify".to_string(),
name: "Verify".to_string(),
index: 1,
- duration_ms: 1500,
+ timing: fabro_types::StageTiming::wall_only(1500),
status: "failed".to_string(),
preferred_label: None,
suggested_next_ids: Vec::new(),
@@ -3171,7 +3171,7 @@ async fn list_run_stages_distinguishes_visits() {
assert_eq!(first["visit"], 1);
assert_eq!(first["handler"], "command");
assert_eq!(first["status"], "failed");
- assert_eq!(first["duration_secs"], 1.5);
+ assert_eq!(first["wall_time_ms"], 1500);
let second = stage_entry(&body, "verify@2");
assert_eq!(second["node_id"], "verify");
@@ -3211,7 +3211,7 @@ async fn run_billing_dedups_retried_nodes_and_sums_their_durations() {
node_id: "verify".to_string(),
name: "Verify".to_string(),
index: 1,
- duration_ms: 1500,
+ timing: fabro_types::StageTiming::wall_only(1500),
status: "failed".to_string(),
preferred_label: None,
suggested_next_ids: Vec::new(),
@@ -3242,7 +3242,7 @@ async fn run_billing_dedups_retried_nodes_and_sums_their_durations() {
node_id: "verify".to_string(),
name: "Verify".to_string(),
index: 1,
- duration_ms: 800,
+ timing: fabro_types::StageTiming::wall_only(800),
status: "succeeded".to_string(),
preferred_label: None,
suggested_next_ids: Vec::new(),
@@ -3314,16 +3314,16 @@ async fn run_billing_dedups_retried_nodes_and_sums_their_durations() {
assert_eq!(stages[0]["stage"]["id"], "verify");
// Duration on the row is the sum across visits (1.5s + 0.8s = 2.3s).
assert!(
- (stages[0]["runtime_secs"].as_f64().unwrap() - 2.3).abs() < f64::EPSILON,
+ stages[0]["timing"]["wall_time_ms"].as_u64().unwrap() == 2300,
"row runtime_secs should sum visits, got {}",
- stages[0]["runtime_secs"]
+ stages[0]["timing"]["wall_time_ms"]
);
// Totals must not double-count: a single 2.3s, not 4.6s.
assert!(
- (body["totals"]["runtime_secs"].as_f64().unwrap() - 2.3).abs() < f64::EPSILON,
+ body["totals"]["timing"]["wall_time_ms"].as_u64().unwrap() == 2300,
"totals.runtime_secs should sum visits exactly once, got {}",
- body["totals"]["runtime_secs"]
+ body["totals"]["timing"]["wall_time_ms"]
);
}
@@ -3350,14 +3350,14 @@ async fn run_billing_sums_usage_across_retry_visits_and_uses_latest_model() {
"verify",
1,
&workflow_event::Event::StageFailed {
- node_id: "verify".to_string(),
- name: "Verify".to_string(),
- index: 1,
- failure: FailureDetail::new("try again", FailureCategory::TransientInfra),
- will_retry: true,
- duration_ms: 1200,
- billing: Some(failed_usage),
- actor: None,
+ node_id: "verify".to_string(),
+ name: "Verify".to_string(),
+ index: 1,
+ failure: FailureDetail::new("try again", FailureCategory::TransientInfra),
+ will_retry: true,
+ timing: fabro_types::StageTiming::wall_only(1200),
+ billing: Some(failed_usage),
+ actor: None,
},
)
.await;
@@ -3370,7 +3370,7 @@ async fn run_billing_sums_usage_across_retry_visits_and_uses_latest_model() {
node_id: "verify".to_string(),
name: "Verify".to_string(),
index: 1,
- duration_ms: 800,
+ timing: fabro_types::StageTiming::wall_only(800),
status: "succeeded".to_string(),
preferred_label: None,
suggested_next_ids: Vec::new(),
@@ -3393,7 +3393,7 @@ async fn run_billing_sums_usage_across_retry_visits_and_uses_latest_model() {
let mut latest_outcome: Outcome |