feat: merge develop into hare/develop

This commit is contained in:
2026-09-18 21:36:04 +09:00
202 changed files with 18312 additions and 3945 deletions
+6 -3
View File
@@ -6,9 +6,12 @@
"dev": "deno run -A npm:vite@7.2.7 dev",
"dev:backend": "cd ../.. && cargo run -p yoi-workspace-server --bin yoi-server -- serve --listen 127.0.0.1:8787",
"check": "deno run -A npm:@sveltejs/kit@2.49.4 sync && deno run -A npm:svelte-check@4.3.4 --tsconfig ./tsconfig.json",
"test": "deno test --allow-read=src,test,tests,../../docs --allow-env=LOG,VSCODE_TEXTMATE_DEBUG,NODE_ENV tests/workspace-model.test.ts tests/workspace-catalog.test.ts tests/profile-api.test.ts tests/skill-api.test.ts tests/tooltip-contract.test.ts tests/bevel-contract.test.ts tests/bevel-line-contract.test.ts tests/typography-contract.test.ts src/lib/workspace/auth/model.test.ts tests/auth-api.test.ts src/lib/workspace/api/http.test.ts src/lib/workspace/api/workers.test.ts src/lib/workspace/header/breadcrumb-model.test.ts src/lib/workspace/console/chat-submit.test.ts src/lib/workspace/console/composer-delivery.test.ts test/composer-history.test.ts tests/composer-paste.test.ts src/lib/workspace/console/composer-command.test.ts src/lib/workspace/console/composer-draft.test.ts src/lib/workspace/console/composer-completion.test.ts src/lib/workspace/console/markdown.test.ts test/console/ansi.test.ts src/lib/workspace/console/model.test.ts src/lib/workspace/companion/api.test.ts tests/workdir-api.test.ts src/lib/workspace/console/tasks.test.ts test/ticket-detail-route-reuse.test.ts test/repositories/ui.test.ts src/lib/workspace/console/worker-console.ui.test.ts src/lib/workspace/settings/model.test.ts tests/runtime-connection.test.ts tests/runtime-management.test.ts tests/runtime-management-source.test.ts src/lib/workspace/sidebar/override-stack.test.ts src/lib/workspace/sidebar/workers.test.ts src/lib/workspace/sidebar/workspace-switcher.test.ts src/lib/workspace/sidebar/worker-subscription.test.ts src/lib/workspace/sidebar/worker-launch.test.ts test/sidebar/worker-actions.test.ts src/lib/workspace/tickets/merge-request-resources.test.ts src/lib/workspace/tickets/ticket-panel.test.ts test/merge-request-status.test.ts test/config-source/decodal-grammar.test.ts test/config-source/editor-state.test.ts test/config-source/fixed-schema-wrapper.test.ts test/config-source/toolchain.test.ts test/config-source/wasm-parity.test.ts test/repository-access/api.test.ts test/repository-access/loader.test.ts test/repository-access/ui.test.ts test/repository-ssh-connection-ui.test.ts",
"test": "deno test --allow-read=src,test,tests,../../docs --allow-env=LOG,VSCODE_TEXTMATE_DEBUG,NODE_ENV tests/workspace-model.test.ts tests/repository-api.test.ts tests/workspace-catalog.test.ts tests/profile-api.test.ts tests/skill-api.test.ts tests/tooltip-contract.test.ts tests/bevel-contract.test.ts tests/bevel-line-contract.test.ts tests/typography-contract.test.ts src/lib/workspace/auth/model.test.ts tests/auth-api.test.ts src/lib/workspace/api/http.test.ts src/lib/workspace/api/workers.test.ts src/lib/workspace/header/breadcrumb-model.test.ts src/lib/workspace/console/chat-submit.test.ts src/lib/workspace/console/composer-delivery.test.ts test/composer-history.test.ts tests/composer-paste.test.ts src/lib/workspace/console/composer-command.test.ts src/lib/workspace/console/composer-draft.test.ts src/lib/workspace/console/composer-completion.test.ts test/console/ansi.test.ts src/lib/workspace/console/model.test.ts src/lib/workspace/console/run-status.test.ts src/lib/workspace/companion/api.test.ts tests/workdir-api.test.ts src/lib/workspace/console/tasks.test.ts test/ticket-detail-route-reuse.test.ts test/objective-pages.test.ts test/repositories/ui.test.ts src/lib/workspace/console/worker-console.ui.test.ts src/lib/workspace/settings/model.test.ts tests/runtime-connection.test.ts tests/runtime-management.test.ts tests/runtime-management-source.test.ts src/lib/workspace/sidebar/override-stack.test.ts src/lib/workspace/sidebar/workers.test.ts src/lib/workspace/sidebar/workspace-switcher.test.ts src/lib/workspace/sidebar/worker-subscription.test.ts src/lib/workspace/sidebar/worker-launch.test.ts test/sidebar/worker-actions.test.ts src/lib/workspace/tickets/merge-request-resources.test.ts src/lib/workspace/tickets/ticket-panel.test.ts test/merge-request-status.test.ts test/config-source/decodal-grammar.test.ts test/config-source/editor-state.test.ts test/config-source/fixed-schema-wrapper.test.ts test/config-source/toolchain.test.ts test/config-source/wasm-parity.test.ts test/repository-access/api.test.ts test/repository-access/loader.test.ts test/repository-access/ui.test.ts test/repository-ssh-connection-ui.test.ts",
"test:component": "deno run -A npm:vitest@3.2.4 run --config vitest.config.ts",
"build": "deno run -A npm:vite@7.2.7 build",
"preview": "deno run -A npm:vite@7.2.7 preview"
"preview": "deno run -A npm:vite@7.2.7 preview",
"generate:repository-api": "cd ../.. && cargo run -q -p server-api --example generate_repository_openapi_types",
"check:repository-api": "cd ../.. && cargo run -q -p server-api --example generate_repository_openapi_types -- --check"
},
"imports": {
"$lib/": "./src/lib/",
@@ -27,7 +30,7 @@
"cookie": "npm:cookie@0.6.0",
"devalue": "npm:devalue@5.6.4",
"set-cookie-parser": "npm:set-cookie-parser@2.7.2",
"shiki": "npm:shiki@3.13.0",
"shiki": "npm:shiki@4.4.2",
"svelte": "npm:svelte@5.45.6",
"svelte-check": "npm:svelte-check@4.3.4",
"typescript": "npm:typescript@5.9.3",
+398 -23
View File
@@ -9,23 +9,28 @@
"npm:@codemirror/state@6.7.1": "6.7.1",
"npm:@codemirror/view@6.43.8": "6.43.8",
"npm:@fontsource/ibm-plex-mono@5.3.0": "5.3.0",
"npm:@humanspeak/svelte-markdown@1.9.1": "1.9.1_shiki@4.4.2_svelte@5.45.6",
"npm:@lezer/common@1.5.2": "1.5.2",
"npm:@lezer/highlight@1.2.3": "1.2.3",
"npm:@sveltejs/adapter-static@3.0.9": "3.0.9_@sveltejs+kit@2.49.4__@sveltejs+vite-plugin-svelte@6.2.1___svelte@5.45.6___vite@7.2.7____yaml@2.9.0___yaml@2.9.0__svelte@5.45.6__typescript@5.9.3__vite@7.2.7___yaml@2.9.0__yaml@2.9.0_yaml@2.9.0",
"npm:@sveltejs/kit@2.49.4": "2.49.4_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.45.6__vite@7.2.7___yaml@2.9.0__yaml@2.9.0_svelte@5.45.6_typescript@5.9.3_vite@7.2.7__yaml@2.9.0_yaml@2.9.0",
"npm:@sveltejs/vite-plugin-svelte@6.2.1": "6.2.1_svelte@5.45.6_vite@7.2.7__yaml@2.9.0_yaml@2.9.0",
"npm:@testing-library/svelte@5.2.8": "5.2.8_svelte@5.45.6_vite@7.2.7__yaml@2.9.0_vitest@3.2.4__happy-dom@18.0.1_happy-dom@18.0.1",
"npm:clsx@2.1.1": "2.1.1",
"npm:cookie@0.6.0": "0.6.0",
"npm:decodal-codemirror@0.3.0": "0.3.0_@codemirror+language@6.12.4_@codemirror+view@6.43.8_@lezer+highlight@1.2.3_@lezer+lr@1.4.10",
"npm:devalue@5.6.4": "5.6.4",
"npm:gen-interface-jp@0.8.0": "0.8.0",
"npm:happy-dom@18.0.1": "18.0.1",
"npm:marked@18.0.13": "18.0.13",
"npm:set-cookie-parser@2.7.2": "2.7.2",
"npm:shiki@3.13.0": "3.13.0",
"npm:shiki@4.4.2": "4.4.2",
"npm:sv@*": "0.10.8",
"npm:svelte-check@4.3.4": "4.3.4_svelte@5.45.6_typescript@5.9.3",
"npm:svelte@5.45.6": "5.45.6",
"npm:typescript@5.9.3": "5.9.3",
"npm:vite@7.2.7": "7.2.7_yaml@2.9.0",
"npm:vitest@3.2.4": "3.2.4_happy-dom@18.0.1",
"npm:yaml@2.9.0": "2.9.0"
},
"jsr": {
@@ -40,6 +45,20 @@
}
},
"npm": {
"@babel/code-frame@7.29.7": {
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
"dependencies": [
"@babel/helper-validator-identifier",
"js-tokens@4.0.0",
"picocolors"
]
},
"@babel/helper-validator-identifier@7.29.7": {
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="
},
"@babel/runtime@7.29.7": {
"integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="
},
"@codemirror/autocomplete@6.20.0": {
"integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==",
"dependencies": [
@@ -217,6 +236,23 @@
"@fontsource/ibm-plex-mono@5.3.0": {
"integrity": "sha512-eTgnZjZEGk1QtD3ZstF+Vclo2HLAni8YMy34/DxllwZvyz1lR/1RF/xTiAquOBO7MvqBx8D2Ig2WCPMVfdZu7Q=="
},
"@humanspeak/memory-cache@1.2.0": {
"integrity": "sha512-im7GIIpgjdD6Fx68xCeiXw3XiDv30/WMRAaodMm+ep0ptYfEUd0cdNeTAylHGIRTJIeByKFHdTmPjAktyDM/JQ=="
},
"@humanspeak/svelte-markdown@1.9.1_shiki@4.4.2_svelte@5.45.6": {
"integrity": "sha512-2JjpATdZfhGQurV38LtkUkPkyQzM7eXku6LoDBeK6i69qqOe0OLmMwpqtMnhwG3ItdxXOt/3miPbSaNTFqMl5Q==",
"dependencies": [
"@humanspeak/memory-cache",
"github-slugger",
"htmlparser2",
"marked",
"shiki",
"svelte"
],
"optionalPeers": [
"shiki"
]
},
"@jridgewell/gen-mapping@0.3.13": {
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dependencies": [
@@ -390,44 +426,53 @@
"os": ["win32"],
"cpu": ["x64"]
},
"@shikijs/core@3.13.0": {
"integrity": "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA==",
"@shikijs/core@4.4.2": {
"integrity": "sha512-StyzbAyxg2/tBGf78gwbBkGyeQ73lf8UiJArFaQhTQIDqQOCKPCQFanvrs4/Yv3Yfyc+ONInJM6K+FMIf+P+kA==",
"dependencies": [
"@shikijs/primitive",
"@shikijs/types",
"@shikijs/vscode-textmate",
"@types/hast",
"hast-util-to-html"
]
},
"@shikijs/engine-javascript@3.13.0": {
"integrity": "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg==",
"@shikijs/engine-javascript@4.4.2": {
"integrity": "sha512-MnIkeqWdVPUWsxlx8gKLVCJFTsqrQJgpTPBPpQwaFeJ56lOnJxj5aN2LUFnfxUEcvOQuNocmbaMnVrCEln6rkw==",
"dependencies": [
"@shikijs/types",
"@shikijs/vscode-textmate",
"oniguruma-to-es"
]
},
"@shikijs/engine-oniguruma@3.13.0": {
"integrity": "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==",
"@shikijs/engine-oniguruma@4.4.2": {
"integrity": "sha512-GLhowz1+jixjz+wiZ3wMnOn1jTxiFCGl2PkXufivbnwPHKuyw1AYqu5/hbWhZZ2oAb0NP05WUJhYeigY14drnw==",
"dependencies": [
"@shikijs/types",
"@shikijs/vscode-textmate"
]
},
"@shikijs/langs@3.13.0": {
"integrity": "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==",
"@shikijs/langs@4.4.2": {
"integrity": "sha512-8DfeusD+Zdv/eYIDdXyJTUnSMHt+aAWjAOCXV20HNGAHRlInXpG8wh421v6B91WOm9TFwRLN+b/LG5F2NAIojg==",
"dependencies": [
"@shikijs/types"
]
},
"@shikijs/themes@3.13.0": {
"integrity": "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==",
"@shikijs/primitive@4.4.2": {
"integrity": "sha512-l6fQQKsOMlz72n38fztmSgZ76MO6KSWuw8o+GJ+FhmqrpC9pIOJNQNXGgbb5yX2AwpzlEHwsaLPnk/8o4Fm+rA==",
"dependencies": [
"@shikijs/types",
"@shikijs/vscode-textmate",
"@types/hast"
]
},
"@shikijs/themes@4.4.2": {
"integrity": "sha512-H0CFoL07ddDC2Dd6EdrPYNkRhUR6YCkJlnuYFceYYUJJA5TIm2b5B33qqiDYryBExgbKMndFJPb2u1gTuqO37g==",
"dependencies": [
"@shikijs/types"
]
},
"@shikijs/types@3.13.0": {
"integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==",
"@shikijs/types@4.4.2": {
"integrity": "sha512-PFYitV4vpDr/iPCIhnHp+Q4ftic5N5VeNJ3KQ1O8gn3h2ar8qgwMAXF7tq4m1CWaMS60fV4VqF6vfnWH4F7vqQ==",
"dependencies": [
"@shikijs/vscode-textmate",
"@types/hast"
@@ -498,14 +543,53 @@
"vitefu"
]
},
"@testing-library/dom@10.4.2": {
"integrity": "sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==",
"dependencies": [
"@babel/code-frame",
"@babel/runtime",
"@types/aria-query",
"aria-query@5.3.0",
"dom-accessibility-api",
"lz-string",
"picocolors",
"pretty-format"
]
},
"@testing-library/svelte@5.2.8_svelte@5.45.6_vite@7.2.7__yaml@2.9.0_vitest@3.2.4__happy-dom@18.0.1_happy-dom@18.0.1": {
"integrity": "sha512-ucQOtGsJhtawOEtUmbR4rRh53e6RbM1KUluJIXRmh6D4UzxR847iIqqjRtg9mHNFmGQ8Vkam9yVcR5d1mhIHKA==",
"dependencies": [
"@testing-library/dom",
"svelte",
"vite",
"vitest"
],
"optionalPeers": [
"vite",
"vitest"
]
},
"@types/aria-query@5.0.4": {
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="
},
"@types/chai@5.2.3": {
"integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
"dependencies": [
"@types/deep-eql",
"assertion-error"
]
},
"@types/cookie@0.6.0": {
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
},
"@types/deep-eql@4.0.2": {
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="
},
"@types/estree@1.0.8": {
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
},
"@types/hast@3.0.4": {
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
"@types/hast@3.0.5": {
"integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==",
"dependencies": [
"@types/unist"
]
@@ -516,9 +600,18 @@
"@types/unist"
]
},
"@types/node@20.19.43": {
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
"dependencies": [
"undici-types"
]
},
"@types/unist@3.0.3": {
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
},
"@types/whatwg-mimetype@3.0.2": {
"integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="
},
"@typescript-eslint/types@8.57.2": {
"integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="
},
@@ -526,25 +619,114 @@
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"deprecated": true
},
"@vitest/expect@3.2.4": {
"integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
"dependencies": [
"@types/chai",
"@vitest/spy",
"@vitest/utils",
"chai",
"tinyrainbow"
]
},
"@vitest/mocker@3.2.4_vite@7.2.7__yaml@2.9.0": {
"integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
"dependencies": [
"@vitest/spy",
"estree-walker",
"magic-string",
"vite"
],
"optionalPeers": [
"vite"
]
},
"@vitest/pretty-format@3.2.4": {
"integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
"dependencies": [
"tinyrainbow"
]
},
"@vitest/runner@3.2.4": {
"integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
"dependencies": [
"@vitest/utils",
"pathe",
"strip-literal"
]
},
"@vitest/snapshot@3.2.4": {
"integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
"dependencies": [
"@vitest/pretty-format",
"magic-string",
"pathe"
]
},
"@vitest/spy@3.2.4": {
"integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
"dependencies": [
"tinyspy"
]
},
"@vitest/utils@3.2.4": {
"integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
"dependencies": [
"@vitest/pretty-format",
"loupe",
"tinyrainbow"
]
},
"acorn@8.16.0": {
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"bin": true
},
"ansi-regex@5.0.1": {
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles@5.2.0": {
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
},
"aria-query@5.3.0": {
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dependencies": [
"dequal"
]
},
"aria-query@5.3.2": {
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="
},
"assertion-error@2.0.1": {
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="
},
"axobject-query@4.1.0": {
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="
},
"cac@6.7.14": {
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="
},
"ccount@2.0.1": {
"integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="
},
"chai@5.3.3": {
"integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
"dependencies": [
"assertion-error",
"check-error",
"deep-eql",
"loupe",
"pathval"
]
},
"character-entities-html4@2.1.0": {
"integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="
},
"character-entities-legacy@3.0.0": {
"integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="
},
"check-error@2.1.3": {
"integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="
},
"chokidar@4.0.3": {
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dependencies": [
@@ -578,6 +760,9 @@
"@lezer/lr"
]
},
"deep-eql@5.0.2": {
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="
},
"deepmerge@4.3.1": {
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
},
@@ -593,6 +778,40 @@
"dequal"
]
},
"dom-accessibility-api@0.5.16": {
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="
},
"dom-serializer@3.1.1": {
"integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==",
"dependencies": [
"domelementtype",
"domhandler",
"entities"
]
},
"domelementtype@3.0.0": {
"integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg=="
},
"domhandler@6.0.1": {
"integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==",
"dependencies": [
"domelementtype"
]
},
"domutils@4.0.2": {
"integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==",
"dependencies": [
"dom-serializer",
"domelementtype",
"domhandler"
]
},
"entities@8.1.0": {
"integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA=="
},
"es-module-lexer@1.7.0": {
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="
},
"esbuild@0.25.12": {
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"optionalDependencies": [
@@ -636,6 +855,15 @@
"@typescript-eslint/types"
]
},
"estree-walker@3.0.3": {
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dependencies": [
"@types/estree"
]
},
"expect-type@1.4.0": {
"integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="
},
"fdir@6.5.0_picomatch@4.0.4": {
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dependencies": [
@@ -653,6 +881,17 @@
"gen-interface-jp@0.8.0": {
"integrity": "sha512-7rIytSAfuugVjYithrQseT0zqeUIkJd0LY3Ugdfbk/FxzMSeagcskeZlof5uWJ5liW3Zhn7pdLHq2OIvAftx+A=="
},
"github-slugger@2.0.0": {
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
},
"happy-dom@18.0.1": {
"integrity": "sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==",
"dependencies": [
"@types/node",
"@types/whatwg-mimetype",
"whatwg-mimetype"
]
},
"hast-util-to-html@9.0.5": {
"integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
"dependencies": [
@@ -678,24 +917,50 @@
"html-void-elements@3.0.0": {
"integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="
},
"htmlparser2@12.0.0": {
"integrity": "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==",
"dependencies": [
"domelementtype",
"domhandler",
"domutils",
"entities"
]
},
"is-reference@3.0.3": {
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
"dependencies": [
"@types/estree"
]
},
"js-tokens@4.0.0": {
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-tokens@9.0.1": {
"integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="
},
"kleur@4.1.5": {
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="
},
"locate-character@3.0.0": {
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
},
"loupe@3.2.1": {
"integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="
},
"lz-string@1.5.0": {
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"bin": true
},
"magic-string@0.30.21": {
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dependencies": [
"@jridgewell/sourcemap-codec"
]
},
"marked@18.0.13": {
"integrity": "sha512-xTxVzZsBFwunP6HDmtBkabUQEYArnP7/rMDGmPj9SlrKlQ4i8MdYVow+nJL0eOqwpUqhzBoTBRADGN6uYwPyOw==",
"bin": true
},
"mdast-util-to-hast@13.2.1": {
"integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
"dependencies": [
@@ -750,17 +1015,23 @@
"obug@2.1.1": {
"integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="
},
"oniguruma-parser@0.12.1": {
"integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="
"oniguruma-parser@0.12.2": {
"integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="
},
"oniguruma-to-es@4.3.4": {
"integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==",
"oniguruma-to-es@4.3.6": {
"integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==",
"dependencies": [
"oniguruma-parser",
"regex",
"regex-recursion"
]
},
"pathe@2.0.3": {
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="
},
"pathval@2.0.1": {
"integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="
},
"picocolors@1.1.1": {
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
@@ -775,9 +1046,20 @@
"source-map-js"
]
},
"pretty-format@27.5.1": {
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dependencies": [
"ansi-regex",
"ansi-styles",
"react-is"
]
},
"property-information@7.1.0": {
"integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="
},
"react-is@17.0.2": {
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"readdirp@4.1.2": {
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="
},
@@ -840,8 +1122,8 @@
"set-cookie-parser@2.7.2": {
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="
},
"shiki@3.13.0": {
"integrity": "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g==",
"shiki@4.4.2": {
"integrity": "sha512-P8F/dFhRevaw2uSdeIYlq/5SXZNY85DPtmXQ947gD1Zj2JqO5AkNvVVBar0Me9JkFx3uzVud/qOtP5ek9NEQGA==",
"dependencies": [
"@shikijs/core",
"@shikijs/engine-javascript",
@@ -853,6 +1135,9 @@
"@types/hast"
]
},
"siginfo@2.0.0": {
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="
},
"sirv@3.0.2": {
"integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
"dependencies": [
@@ -867,6 +1152,12 @@
"space-separated-tokens@2.0.2": {
"integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="
},
"stackback@0.0.2": {
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="
},
"std-env@3.10.0": {
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="
},
"stringify-entities@4.0.4": {
"integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
"dependencies": [
@@ -874,6 +1165,12 @@
"character-entities-legacy"
]
},
"strip-literal@3.1.0": {
"integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
"dependencies": [
"js-tokens@9.0.1"
]
},
"style-mod@4.1.3": {
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="
},
@@ -902,7 +1199,7 @@
"@sveltejs/acorn-typescript",
"@types/estree",
"acorn",
"aria-query",
"aria-query@5.3.2",
"axobject-query",
"clsx",
"devalue",
@@ -914,6 +1211,12 @@
"zimmerframe"
]
},
"tinybench@2.9.0": {
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="
},
"tinyexec@0.3.2": {
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="
},
"tinyglobby@0.2.15": {
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dependencies": [
@@ -921,6 +1224,15 @@
"picomatch"
]
},
"tinypool@1.1.1": {
"integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="
},
"tinyrainbow@2.0.0": {
"integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="
},
"tinyspy@4.0.6": {
"integrity": "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg=="
},
"totalist@3.0.1": {
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="
},
@@ -931,6 +1243,9 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"bin": true
},
"undici-types@6.21.0": {
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
},
"unist-util-is@6.0.1": {
"integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
"dependencies": [
@@ -978,6 +1293,17 @@
"vfile-message"
]
},
"vite-node@3.2.4": {
"integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
"dependencies": [
"cac",
"debug",
"es-module-lexer",
"pathe",
"vite"
],
"bin": true
},
"vite@7.2.7_yaml@2.9.0": {
"integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==",
"dependencies": [
@@ -1006,9 +1332,53 @@
"vite"
]
},
"vitest@3.2.4_happy-dom@18.0.1": {
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dependencies": [
"@types/chai",
"@vitest/expect",
"@vitest/mocker",
"@vitest/pretty-format",
"@vitest/runner",
"@vitest/snapshot",
"@vitest/spy",
"@vitest/utils",
"chai",
"debug",
"expect-type",
"happy-dom",
"magic-string",
"pathe",
"picomatch",
"std-env",
"tinybench",
"tinyexec",
"tinyglobby",
"tinypool",
"tinyrainbow",
"vite",
"vite-node",
"why-is-node-running"
],
"optionalPeers": [
"happy-dom"
],
"bin": true
},
"w3c-keyname@2.2.8": {
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
},
"whatwg-mimetype@3.0.0": {
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="
},
"why-is-node-running@2.3.0": {
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
"dependencies": [
"siginfo",
"stackback"
],
"bin": true
},
"yaml@2.9.0": {
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
"bin": true
@@ -1037,7 +1407,7 @@
"npm:decodal-codemirror@0.3.0",
"npm:devalue@5.6.4",
"npm:set-cookie-parser@2.7.2",
"npm:shiki@3.13.0",
"npm:shiki@4.4.2",
"npm:svelte-check@4.3.4",
"npm:svelte@5.45.6",
"npm:typescript@5.9.3",
@@ -1046,7 +1416,12 @@
"packageJson": {
"dependencies": [
"npm:@fontsource/ibm-plex-mono@5.3.0",
"npm:@humanspeak/svelte-markdown@1.9.1",
"npm:@testing-library/svelte@5.2.8",
"npm:gen-interface-jp@0.8.0",
"npm:happy-dom@18.0.1",
"npm:shiki@4.4.2",
"npm:vitest@3.2.4",
"npm:yaml@2.9.0"
]
}
+7
View File
@@ -5,7 +5,14 @@
"type": "module",
"dependencies": {
"@fontsource/ibm-plex-mono": "5.3.0",
"@humanspeak/svelte-markdown": "1.9.1",
"gen-interface-jp": "0.8.0",
"shiki": "4.4.2",
"yaml": "2.9.0"
},
"devDependencies": {
"@testing-library/svelte": "5.2.8",
"happy-dom": "18.0.1",
"vitest": "3.2.4"
}
}
+20
View File
@@ -0,0 +1,20 @@
# Generated frontend contracts
Checked-in files in this directory are generated API contracts. Do not edit them by hand.
## Repository list/detail/create API
`repository-api.ts` is the only TypeScript declaration authority for the Repository list, detail, and create request/response/error wire types. It is generated from the schema closure of the five Repository operations in the canonical `openapi/server-api.json` artifact. The generated header pins the in-repository generator name/version/options, canonical input path and digest, and output path.
Regenerate or verify it from the repository root:
```sh
cargo run -q -p server-api --example generate_repository_openapi_types
cargo run -q -p server-api --example generate_repository_openapi_types -- --check
```
The generator fails closed on unsupported or lossy OpenAPI constructs, including ambiguous nullable unions, external references, semantic `$ref` siblings, unimplemented ordinary schema keywords, unsupported enum literals/formats, and integer ranges that cannot be represented safely by JavaScript numbers. Its one explicit format mapping is canonical `uint64` to a nonnegative JavaScript safe `number`; the runtime parser enforces the same range. The legacy TypeScript generator imports the two shared Repository source projection types it still needs and no longer declares the migrated Repository list/detail types.
## Other generated contracts
The remaining files retain their generator command in their header. `legacy-server-api.ts` is transitional: new bounded domain slices should use a focused contract artifact instead of adding another API surface to it.
+22 -79
View File
@@ -1,107 +1,50 @@
// Generated from workspace-api. Do not edit by hand.
// Regenerate: cargo run -q -p workspace-api --features typescript --example generate_auth_api_types > web/workspace/src/lib/generated/auth-api.ts
// Generated from server-api. Do not edit by hand.
// Regenerate: cargo run -q -p server-api --features typescript --example generate_auth_api_types > web/workspace/src/lib/generated/auth-api.ts
export type AuthPublicConfig = {
rp_id: string;
origin: string;
public_base_url: string;
cookie_name: string;
};
export type AuthPublicConfig = { rp_id: string, origin: string, public_base_url: string, cookie_name: string, };
export type ActorAuthMethod = "browser_session" | "api_token";
export type AuthenticatedUser = {
user_id: string;
account_id: string;
handle: string;
display_name: string;
};
export type AuthenticatedUser = { user_id: string, account_id: string, handle: string, display_name: string, };
export type RequestActor = {
user_id: string;
account_id: string;
handle: string;
display_name: string;
auth_method: ActorAuthMethod;
};
export type RequestActor = { user_id: string, account_id: string, handle: string, display_name: string, auth_method: ActorAuthMethod, };
export type WhoamiResponse = { actor: RequestActor | null };
export type WhoamiResponse = { actor: RequestActor | null, };
export type AuthBootstrapUserRequest = {
handle: string;
display_name?: string | null;
};
export type AuthBootstrapUserRequest = { handle: string, display_name?: string | null, };
export type AuthUserResponse = { user: AuthenticatedUser };
export type AuthUserResponse = { user: AuthenticatedUser, };
export type PasskeyRegistrationOptionsRequest = {
handle: string;
display_name?: string | null;
browser_origin?: string | null;
};
export type PasskeyRegistrationOptionsRequest = { handle: string, display_name?: string | null, browser_origin?: string | null, };
export type PasskeyRegistrationOptionsResponse = {
challenge_id: string;
public_key: unknown;
};
export type PasskeyRegistrationOptionsResponse = { challenge_id: string, public_key: unknown, };
export type PasskeyRegistrationCompleteRequest = {
challenge_id: string;
credential: unknown;
};
export type PasskeyRegistrationCompleteRequest = { challenge_id: string, credential: unknown, };
export type PasskeyLoginOptionsRequest = {
handle?: string | null;
browser_origin?: string | null;
};
export type PasskeyLoginOptionsRequest = { handle?: string | null, browser_origin?: string | null, };
export type PasskeyLoginOptionsResponse = {
challenge_id: string;
public_key: unknown;
};
export type PasskeyLoginOptionsResponse = { challenge_id: string, public_key: unknown, };
export type PasskeyLoginCompleteRequest = {
challenge_id: string;
credential: unknown;
};
export type PasskeyLoginCompleteRequest = { challenge_id: string, credential: unknown, };
export type DeviceLoginStartRequest = { client_name?: string | null };
export type DeviceLoginStartRequest = { client_name?: string | null, };
export type DeviceLoginStartResponse = {
device_code: string;
user_code: string;
verification_uri: string;
verification_uri_complete: string;
expires_in: number;
interval: number;
};
export type DeviceLoginStartResponse = { device_code: string, user_code: string, verification_uri: string, verification_uri_complete: string, expires_in: number, interval: number, };
export type DeviceLoginApproveRequest = { user_code: string };
export type DeviceLoginApproveRequest = { user_code: string, };
export type DeviceLoginApprovalStatus = "approved";
export type DeviceLoginApproveResponse = {
status: DeviceLoginApprovalStatus;
user: AuthenticatedUser;
};
export type DeviceLoginApproveResponse = { status: DeviceLoginApprovalStatus, user: AuthenticatedUser, };
export type DeviceLoginPollRequest = { device_code: string };
export type DeviceLoginPollRequest = { device_code: string, };
export type DeviceAccessTokenType = "Bearer";
export type DeviceLoginPollStatus =
| "pending"
| "approved"
| "expired"
| "denied"
| "consumed";
export type DeviceLoginPollStatus = "pending" | "approved" | "expired" | "denied" | "consumed";
export type DeviceLoginPollResponse = {
status: DeviceLoginPollStatus;
access_token?: string | null;
token_type?: DeviceAccessTokenType | null;
};
export type DeviceLoginPollResponse = { status: DeviceLoginPollStatus, access_token?: string | null, token_type?: DeviceAccessTokenType | null, };
export type LogoutStatus = "logged_out";
export type LogoutResponse = { status: LogoutStatus };
export type LogoutResponse = { status: LogoutStatus, };
@@ -1,64 +1,30 @@
// Generated by `cargo run -p workspace-api --features typescript --example generate_companion_api_types`.
// Generated by `cargo run -p server-api --features typescript --example generate_companion_api_types`.
// Do not edit manually.
export type DiagnosticSeverity = "info" | "warning" | "error";
export type Diagnostic = {
code: string;
severity: DiagnosticSeverity;
message: string;
};
export type Diagnostic = { code: string, severity: DiagnosticSeverity, message: string, };
export type WorkspaceWorkerSubject = {
"kind": "runtime_worker";
runtime_id: string;
worker_id: string;
};
export type WorkspaceWorkerSubject = { "kind": "runtime_worker", runtime_id: string, worker_id: string, };
export type WorkspaceWorkerDiscoveryItem = {
subject: WorkspaceWorkerSubject;
resource_key: string;
display_name: string;
profile: string | null;
status?: string | null;
};
export type WorkspaceWorkerDiscoveryItem = { subject: WorkspaceWorkerSubject, resource_key: string, display_name: string, profile: string | null, status?: string | null, };
export type CompanionLifecycleState = "idle" | "running" | "stopped";
export type CompanionMessageDisposition = "accepted" | "rejected";
export type CompanionTransportSummary = { mode: string; available: boolean };
export type CompanionTransportSummary = { mode: string, available: boolean, };
export type CompanionStatusResponse = {
state: CompanionLifecycleState;
worker: WorkspaceWorkerDiscoveryItem | null;
transport: CompanionTransportSummary;
diagnostics: Array<Diagnostic>;
};
export type CompanionStatusResponse = { state: CompanionLifecycleState, worker: WorkspaceWorkerDiscoveryItem | null, transport: CompanionTransportSummary, diagnostics: Array<Diagnostic>, };
export type CompanionMessageRequest = { content: string };
export type CompanionMessageRequest = { content: string, };
export type CompanionCancelRequest = { reason?: string | null };
export type CompanionCancelRequest = { reason?: string | null, };
export type CompanionMessageResponse = {
state: CompanionMessageDisposition;
message: string;
};
export type CompanionMessageResponse = { state: CompanionMessageDisposition, message: string, };
export type CompanionTranscriptRole = "user" | "assistant";
export type CompanionTranscriptItem = {
sequence: number;
role: CompanionTranscriptRole;
content: string;
created_at: string;
};
export type CompanionTranscriptItem = { sequence: number, role: CompanionTranscriptRole, content: string, created_at: string, };
export type CompanionTranscriptProjection = {
state: CompanionLifecycleState;
start: number;
limit: number;
total: number;
next: number | null;
items: Array<CompanionTranscriptItem>;
};
export type CompanionTranscriptProjection = { state: CompanionLifecycleState, start: number, limit: number, total: number, next: number | null, items: Array<CompanionTranscriptItem>, };
@@ -0,0 +1,140 @@
// This file is generated by `cargo run -p server-api --features typescript --example generate_legacy_typescript | deno fmt -`.
// Do not edit this file directly.
import type { RepositoryObservedStatus, RepositorySource } from "./repository-api";
export type WorkspaceSummary = { workspace_id: string, owner_account_id: string, display_name: string, state: string, created_at: string, updated_at: string, };
export type WorkspaceCatalogListResponse = Array<WorkspaceSummary>;
export type WorkspaceRepositoryRecord = { workspace_id: string, repository_key: string, kind: string, provider: string | null, source: RepositorySource, default_ref: string | null, source_revision: number, source_fingerprint: string, observed_status: RepositoryObservedStatus, observed_at: string | null, created_at: string, updated_at: string, };
export type WorkspaceCreateResponse = { workspace: WorkspaceSummary, repository: WorkspaceRepositoryRecord, config_revision: number, request_fingerprint: string, replayed: boolean, };
export type WorkspaceAuthConfig = { "Passkey": { rp_id: string, origin: string, public_base_url: string, cookie_name: string, } };
export type WorkspacePermissionSummary = { manage_repositories: boolean, manage_secrets: boolean, manage_runtimes: boolean, delete_workspace: boolean, };
export type WorkspaceDeletionState = "queued" | "running" | "blocked" | "failed" | "succeeded";
export type WorkspaceDeletionBlockerKind = "last_accessible_workspace" | "revision_conflict" | "dirty_workdir" | "worker_removal_blocked" | "workdir_removal_blocked" | "retention_hold" | "cleanup_unavailable";
export type WorkspaceDeletionBlocker = { kind: WorkspaceDeletionBlockerKind, resource_kind: string | null, resource_key: string | null, message: string, };
export type WorkspaceDeletionResourceCounts = { workers: number, workdirs: number, repositories: number, runtime_bindings: number, secrets: number, artifacts: number, };
export type WorkspaceDeletionPreflightResponse = { workspace_id: string, display_name: string,
/**
* Opaque persisted Workspace metadata revision used as a CAS fence.
*/
expected_revision: string, can_delete: boolean, resources: WorkspaceDeletionResourceCounts, blockers: Array<WorkspaceDeletionBlocker>, };
export type WorkspaceDeletionRequest = { operation_id: string, expected_revision: string, confirmation: string, };
export type WorkspaceDeletionOperationResponse = { operation_id: string, workspace_id: string, display_name: string, state: WorkspaceDeletionState, resources: WorkspaceDeletionResourceCounts, child_operation_ids: Array<string>, blockers: Array<WorkspaceDeletionBlocker>, failure_category: string | null, created_at: string, updated_at: string, completed_at: string | null, };
export type DiagnosticSeverity = "info" | "warning" | "error";
export type Diagnostic = { code: string, severity: DiagnosticSeverity, message: string, };
export type WorkspaceExtensionPointState = { status: string, note: string, diagnostics: Array<Diagnostic>, };
export type WorkspaceExtensionPoints = { store: string, event_stream: WorkspaceExtensionPointState, host_worker_bridge: WorkspaceExtensionPointState, companion_console: WorkspaceExtensionPointState, };
export type WorkspaceResponse = { workspace_id: string, display_name: string, record_authority: string, schema_version: number, auth: WorkspaceAuthConfig, permissions: WorkspacePermissionSummary, extension_points: WorkspaceExtensionPoints, };
export type WorkspaceMetadataSettingsResponse = { workspace_id: string, display_name: string, created_at: string, revision: string, source: string, diagnostics: Array<Diagnostic>, };
export type UpdateWorkspaceMetadataRequest = { display_name: string, revision: string, };
export type WorkspaceMetadataMutationResponse = { workspace: WorkspaceMetadataSettingsResponse, diagnostics: Array<Diagnostic>, };
export type WorkspaceSigningIdentityState = "pending_provisioning" | "active";
export type WorkspaceSigningIdentityPublic = { workspace_id: string, key_id: string, algorithm: string, public_key?: string, public_key_fingerprint?: string, revision: number, state: WorkspaceSigningIdentityState, created_at: string, provisioned_at?: string, };
export type WorkspacePublicIdentityBundle = { workspace_id: string, backend_url: string, key_id: string, algorithm: string, public_key: string, public_key_fingerprint: string, revision: number, };
export type WorkspaceSigningIdentityResponse = { identity: WorkspaceSigningIdentityPublic, public_bundle?: WorkspacePublicIdentityBundle, };
export type ProfileSettingsResponse = { workspace_id: string, registry_revision: string, config_revision?: number | null, tree_digest?: string | null, projection_digest?: string | null, default_profile?: string | null, profiles: Array<WorkspaceProfileSummary>, sources: Array<WorkspaceProfileSourceSummary>, diagnostics: Array<Diagnostic>, };
export type WorkspaceProfileSummary = { profile_id: string, selector: string, label: string, source_kind: string, profile_source_id?: string | null, description?: string | null, editable: boolean, is_default: boolean, diagnostics: Array<Diagnostic>, };
export type WorkspaceProfileSourceSummary = { profile_source_id: string, display_path: string, kind: string, content_type: string, content_digest: string, provenance: WorkspaceProfileSourceProvenance, editable: boolean, revision: string, size_bytes: number, diagnostics: Array<Diagnostic>, };
export type WorkspaceProfileSourceProvenance = "project_profile_source_tree";
export type GitCommitSummary = { hash: string, short_hash: string, summary: string, author_name: string, author_email: string, author_date: string, parents: Array<string>, refs: Array<string>, };
export type RepositorySshConnectionProbeRequest = { runtime_id: string, };
export type RepositorySshHostKeyCandidate = { algorithm: string, host_key: string, fingerprint: string, };
export type RepositorySshConnectionTrustState = "untrusted" | "verified" | "changed";
export type RepositorySshConnectionProbeResponse = { workspace_id: string, repository_key: string, runtime_id: string, hostname: string, port: number, trust_state: RepositorySshConnectionTrustState, host_trust_id: string, expected_host_trust_revision: number | null, candidates: Array<RepositorySshHostKeyCandidate>, };
export type ConfirmRepositorySshHostTrustRequest = { operation_id: string, runtime_id: string, host_key: string, expected_host_trust_revision: number | null, };
export type RepositoryLogResponse = { workspace_id: string, repository_key: string, default_selector?: string | null, limit: number, items: Array<GitCommitSummary>, diagnostics: Array<Diagnostic>, };
export type RuntimeSourceKind = "embedded_worker_runtime" | "remote_http";
export type RuntimeSourceStatus = "active" | "reserved";
export type RuntimeIdentityAuthority = "runtime_registry_projection" | "server_runtime_configuration";
export type RuntimeSourceSummary = { kind: RuntimeSourceKind, status: RuntimeSourceStatus, identity_authority: RuntimeIdentityAuthority, note: string, };
export type RuntimeSummary = { runtime_id: string, label: string, kind: string, status: string, source: RuntimeSourceSummary, host_ids: Array<string>, worker_creation_available: boolean, os: string, arch: string, diagnostics: Array<Diagnostic>, };
export type WorkspaceRuntimeBindingState = "configured" | "verified" | "revoked";
export type RuntimeConnectionDisplayState = "configured" | "verified" | "unavailable" | "revoked";
export type RuntimeVerificationOutcome = "verified" | "challenge_issued" | "verification_failed" | "connectivity_failed";
export type RuntimeVerificationEvidenceSummary = { verified_at: string | null, last_checked_at: string, last_outcome: RuntimeVerificationOutcome, binding_revision: number, workspace_key_id: string, workspace_identity_revision: number, workspace_trust_generation: number, runtime_public_key_fingerprint: string, runtime_identity_revision: number, };
export type WorkspaceRuntimeBindingSummary = { state: WorkspaceRuntimeBindingState, connection_state: RuntimeConnectionDisplayState, revision: number, workspace_key_id?: string | null, workspace_key_generation?: number | null, verification?: RuntimeVerificationEvidenceSummary | null, };
export type RuntimeManagementSummary = { built_in: boolean, config_managed: boolean, removable: boolean, endpoint_configured: boolean, token_ref_configured: boolean, binding?: WorkspaceRuntimeBindingSummary | null, };
export type WorkspaceRuntimeResource = { management: RuntimeManagementSummary, runtime_id: string, label: string, kind: string, status: string, source: RuntimeSourceSummary, host_ids: Array<string>, worker_creation_available: boolean, os: string, arch: string, diagnostics: Array<Diagnostic>, };
export type RuntimeTrustKeyStatus = "unconfigured" | "active" | "revoked";
export type RuntimeTrustKeyState = { status: RuntimeTrustKeyStatus, fingerprint?: string | null, revision?: number | null, created_at?: string | null, updated_at?: string | null, revoked_at?: string | null, };
export type RuntimeTrustAuditAction = "created" | "replaced" | "reactivated" | "revoked";
export type RuntimeTrustAuditEntry = { action: RuntimeTrustAuditAction, actor_account_id: string, old_fingerprint?: string | null, new_fingerprint?: string | null, revision: number, at: string, };
export type WorkspaceRuntimeDetail = { workspace_id: string, runtime: WorkspaceRuntimeResource, endpoint?: string | null, trust_key: RuntimeTrustKeyState, recent_audit: Array<RuntimeTrustAuditEntry>, };
export type RuntimeTrustKeyRevealResponse = { public_key: string, };
export type RevokeRuntimeTrustKeyRequest = { expected_revision: number, };
export type RemoveRuntimeRequest = { operation_id: string, expected_binding_revision: number, };
export type RuntimeRemovalOperationState = "pending" | "cleanup_pending" | "succeeded" | "failed";
export type RuntimeRemovalOperationResponse = { operation_id: string, workspace_id: string, runtime_id: string, state: RuntimeRemovalOperationState, binding_removed: boolean, runtime_registration_removed: boolean | null, failure_category?: string | null, created_at: string, updated_at: string, completed_at?: string | null, };
export type RuntimeTrustConflictKind = "stale_revision" | "fingerprint_in_use";
export type RuntimeTrustConflictResponse = { error: RuntimeTrustConflictKind, message: string, current_revision?: number, current_fingerprint?: string | null, };
export type RuntimePublicIdentityBundle = { identity_id: string, public_key: string, };
export type CreateRemoteRuntimeRequest = { public_bundle: RuntimePublicIdentityBundle, display_name?: string | null, endpoint: string, expected_revision?: number | null, };
export type UpdateRemoteRuntimeRequest = { display_name?: string | null, endpoint: string, };
export type RuntimeConnectionTestStatus = "compatible" | "failed";
export type RuntimeConnectionTestFailureKind = "authentication" | "authorization" | "network_unreachable" | "timeout" | "tls_or_transport" | "malformed_response" | "protocol_version_mismatch" | "runtime_identity_mismatch" | "configuration";
export type RuntimeConnectionTestResponse = { workspace_id: string, runtime_id: string, binding_revision: number, connection_state: RuntimeConnectionDisplayState, verification: RuntimeVerificationEvidenceSummary | null, checked_at: string, status: RuntimeConnectionTestStatus, failure_kind: RuntimeConnectionTestFailureKind | null, expected_protocol_version: number, actual_protocol_version: number | null, diagnostics: Array<Diagnostic>, };
+13 -87
View File
@@ -1,100 +1,26 @@
// Generated from workspace-api. Do not edit by hand.
// Regenerate: cargo run -q -p workspace-api --features typescript --example generate_memory_api_types > web/workspace/src/lib/generated/memory-api.ts
// Generated from server-api. Do not edit by hand.
// Regenerate: cargo run -q -p server-api --features typescript --example generate_memory_api_types > web/workspace/src/lib/generated/memory-api.ts
export type DiagnosticSeverity = "info" | "warning" | "error";
export type Diagnostic = {
code: string;
severity: DiagnosticSeverity;
message: string;
};
export type Diagnostic = { code: string, severity: DiagnosticSeverity, message: string, };
export type MemoryDocumentResponse = {
body_md: string;
created_at: string;
updated_at: string;
bytes: number;
record_source: string;
};
export type MemoryDocumentResponse = { body_md: string, created_at: string, updated_at: string, bytes: number, record_source: string, };
export type MemoryCandidateKind =
| "preference"
| "working_assumption"
| "constraint"
| "decision"
| "open_question"
| "lesson";
export type MemoryCandidateKind = "preference" | "working_assumption" | "constraint" | "decision" | "open_question" | "lesson";
export type MemoryEvidenceOriginKind =
| "human_input"
| "worker_input"
| "flow_instruction"
| "backend_instruction"
| "model_output"
| "tool_output"
| "derived_summary"
| "legacy_unknown";
export type MemoryEvidenceOriginKind = "human_input" | "worker_input" | "flow_instruction" | "backend_instruction" | "model_output" | "tool_output" | "derived_summary" | "legacy_unknown";
export type MemoryEvidenceOrigin = {
kind: MemoryEvidenceOriginKind;
account_id?: string | null;
workspace_id?: string | null;
runtime_id?: string | null;
worker_id?: string | null;
flow_selector?: string | null;
flow_definition_id?: string | null;
flow_definition_revision?: number | null;
};
export type MemoryEvidenceOrigin = { kind: MemoryEvidenceOriginKind, account_id?: string | null, workspace_id?: string | null, runtime_id?: string | null, worker_id?: string | null, flow_selector?: string | null, flow_definition_id?: string | null, flow_definition_revision?: number | null, };
export type MemorySourceRef = { segment_id: string; range: [number, number] };
export type MemorySourceRef = { segment_id: string, range: [number, number], };
export type MemoryStagingEvidence = {
id: string;
kind: string;
entry_range: [number, number] | null;
origin?: MemoryEvidenceOrigin | null;
excerpt: string | null;
summary: string | null;
};
export type MemoryStagingEvidence = { id: string, kind: string, entry_range: [number, number] | null, origin?: MemoryEvidenceOrigin | null, excerpt: string | null, summary: string | null, };
export type MemorySourceEvidenceRef = {
session_id: string | null;
segment_id: string | null;
entry_range: [number, number] | null;
evidence_id: string | null;
origin?: MemoryEvidenceOrigin | null;
evidence_kind: string | null;
label: string | null;
summary: string | null;
};
export type MemorySourceEvidenceRef = { session_id: string | null, segment_id: string | null, entry_range: [number, number] | null, evidence_id: string | null, origin?: MemoryEvidenceOrigin | null, evidence_kind: string | null, label: string | null, summary: string | null, };
export type MemoryStagingRecord = {
schema_version: number;
id: string;
extract_run_id: string;
source: MemorySourceRef;
kind: MemoryCandidateKind;
claim: string;
why_useful: string;
staleness: string | null;
evidence: Array<MemoryStagingEvidence>;
source_refs: Array<MemorySourceEvidenceRef>;
};
export type MemoryStagingRecord = { schema_version: number, id: string, extract_run_id: string, source: MemorySourceRef, kind: MemoryCandidateKind, claim: string, why_useful: string, staleness: string | null, evidence: Array<MemoryStagingEvidence>, source_refs: Array<MemorySourceEvidenceRef>, };
export type MemoryStagingEntry = {
id: string;
byte_len: number;
record: MemoryStagingRecord;
};
export type MemoryStagingEntry = { id: string, byte_len: number, record: MemoryStagingRecord, };
export type MemoryStagingListResponse = {
limit: number;
returned_count: number;
total_valid_count: number;
invalid_count: number;
truncated: boolean;
order: string;
record_authority: string;
items: Array<MemoryStagingEntry>;
diagnostics: Array<Diagnostic>;
};
export type MemoryStagingListResponse = { limit: number, returned_count: number, total_valid_count: number, invalid_count: number, truncated: boolean, order: string, record_authority: string, items: Array<MemoryStagingEntry>, diagnostics: Array<Diagnostic>, };
+8 -1
View File
@@ -232,6 +232,8 @@ export type SubscriptionWorkerIds = Array<SubscriptionWorkerId>;
export type SubscriptionWorkerState = "idle" | "running" | "paused" | "stopped";
export type SubscriptionWorkerAvailability = "observed" | "unavailable";
export type EventSubscriptionSelector = { "topic": "runtime_workers" } | { "topic": "worker_lifecycle", worker_ids: SubscriptionWorkerIds, } | { "topic": "worker_protocol", worker_id: SubscriptionWorkerId, runtime_id?: string | null, } | { "topic": "workspace_workers" } | { "topic": "workspace_workdirs" };
export type SubscriptionWorker = { worker_id: SubscriptionWorkerId,
@@ -245,6 +247,11 @@ runtime_id?: string | null,
* Workspace-facing projections must populate it before publishing the Worker.
*/
resource_key?: string | null,
/**
* Freshness of the Runtime-backed observation carried by this projection.
* `Unavailable` preserves catalog membership without claiming that execution stopped.
*/
availability: SubscriptionWorkerAvailability,
/**
* Producer-owned monotonic revision for this Worker subject.
*/
@@ -286,7 +293,7 @@ export type SubscriptionFramePayload = { "frame": "request", "message": Subscrip
export type SubscriptionFrame = { protocol_version: number, } & ({ "frame": "request", "message": SubscriptionRequest } | { "frame": "response", "message": SubscriptionResponse } | { "frame": "event", "message": SubscriptionEvent } | { "frame": "worker_protocol", "message": SubscriptionWorkerProtocolMethod });
export type Method = { "method": "submit", "params": { submission_request_id: string, input: Array<Segment>, } } | { "method": "notify", "params": { notification_request_id: string, message: string, auto_run?: boolean, } } | { "method": "worker_event", "params": WorkerEvent } | { "method": "list_pending_submissions" } | { "method": "cancel_pending_submission", "params": { submission_id: string, expected_revision: number, } } | { "method": "clear_pending_submissions", "params": { expected_revision: number, } } | { "method": "continue_pending", "params": { expected_revision: number, expected_head_id: string, } } | { "method": "resume", "params": { command: WorkerCommandEnvelope, } } | { "method": "cancel", "params": { command: WorkerCommandEnvelope, } } | { "method": "pause", "params": { command: WorkerCommandEnvelope, } } | { "method": "compact", "params": { command: WorkerCommandEnvelope, } } | { "method": "list_rewind_targets" } | { "method": "rewind_to", "params": { target: RewindTargetId, expected_head_entries: number, } } | { "method": "shutdown", "params": { command: WorkerCommandEnvelope, } } | { "method": "list_completions", "params": { kind: CompletionKind, prefix: string, } } | { "method": "list_workers" } | { "method": "restore_worker", "params": { name: string, } } | { "method": "register_peer", "params": { name: string, } };
export type Method = { "method": "submit", "params": { submission_request_id: string, input: Array<Segment>, } } | { "method": "notify", "params": { notification_request_id: string, message: string, } } | { "method": "worker_event", "params": WorkerEvent } | { "method": "list_pending_submissions" } | { "method": "cancel_pending_submission", "params": { submission_id: string, expected_revision: number, } } | { "method": "clear_pending_submissions", "params": { expected_revision: number, } } | { "method": "continue_pending", "params": { expected_revision: number, expected_head_id: string, } } | { "method": "resume", "params": { command: WorkerCommandEnvelope, } } | { "method": "cancel", "params": { command: WorkerCommandEnvelope, } } | { "method": "pause", "params": { command: WorkerCommandEnvelope, } } | { "method": "compact", "params": { command: WorkerCommandEnvelope, } } | { "method": "list_rewind_targets" } | { "method": "rewind_to", "params": { target: RewindTargetId, expected_head_entries: number, } } | { "method": "shutdown", "params": { command: WorkerCommandEnvelope, } } | { "method": "list_completions", "params": { kind: CompletionKind, prefix: string, } } | { "method": "list_workers" } | { "method": "restore_worker", "params": { name: string, } } | { "method": "register_peer", "params": { name: string, } };
export type Event = { "event": "submission_accepted", "data": { submission_request_id: string, submission_id: string, disposition: SubmissionDisposition, } } | { "event": "submission_rejected", "data": { submission_request_id: string, message: string, } } | { "event": "pending_submissions_changed", "data": { pending: PendingSubmissionsSnapshot, } } | { "event": "user_message", "data": { segments: Array<Segment>, } } | { "event": "system_item", "data": { item: unknown, } } | { "event": "invoke_start", "data": { kind: InvokeKind, } } | { "event": "turn_start", "data": { turn: number, } } | { "event": "turn_end", "data": { turn: number, result: TurnResult, } } | { "event": "llm_call_start", "data": { llm_call: number, } } | { "event": "llm_call_end", "data": { llm_call: number, } } | { "event": "llm_retry", "data": { llm_call: number,
/**
@@ -1,93 +1,26 @@
// Generated from workspace-api. Do not edit by hand.
// Regenerate: cargo run -q -p workspace-api --features typescript --example generate_repository_access_types > web/workspace/src/lib/generated/repository-access-api.ts
// Generated from server-api. Do not edit by hand.
// Regenerate: cargo run -q -p server-api --features typescript --example generate_repository_access_types > web/workspace/src/lib/generated/repository-access-api.ts
export type RepositorySshCredential = {
credential_id: string;
workspace_id: string;
name: string;
public_key_algorithm: string;
public_key_fingerprint: string;
current_revision: number;
status: string;
created_at: string;
rotated_at: string | null;
referenced_repositories: Array<string>;
};
export type RepositorySshCredential = { credential_id: string, workspace_id: string, name: string, public_key_algorithm: string, public_key_fingerprint: string, current_revision: number, status: string, created_at: string, rotated_at: string | null, referenced_repositories: Array<string>, };
export type CreateRepositorySshCredentialRequest = {
operation_id: string;
credential_id: string;
name: string;
private_key: string;
passphrase: string | null;
};
export type CreateRepositorySshCredentialRequest = { operation_id: string, credential_id: string, name: string, private_key: string, passphrase: string | null, };
export type GenerateRepositorySshCredentialRequest = {
operation_id: string;
credential_id: string;
name: string;
};
export type GenerateRepositorySshCredentialRequest = { operation_id: string, credential_id: string, name: string, };
export type RepositorySshPublicKey = {
credential_id: string;
current_revision: number;
public_key_algorithm: string;
public_key_fingerprint: string;
public_key: string;
};
export type RepositorySshPublicKey = { credential_id: string, current_revision: number, public_key_algorithm: string, public_key_fingerprint: string, public_key: string, };
export type RotateRepositorySshCredentialRequest = {
operation_id: string;
expected_revision: number;
private_key: string;
passphrase: string | null;
};
export type RotateRepositorySshCredentialRequest = { operation_id: string, expected_revision: number, private_key: string, passphrase: string | null, };
export type DeleteRepositorySshCredentialRequest = {
operation_id: string;
expected_revision: number;
};
export type DeleteRepositorySshCredentialRequest = { operation_id: string, expected_revision: number, };
export type RepositorySshHostTrust = {
host_trust_id: string;
workspace_id: string;
hostname: string;
port: number;
key_algorithm: string;
host_key: string;
fingerprint: string;
current_revision: number;
created_at: string;
updated_at: string;
referenced_repositories: Array<string>;
};
export type RepositorySshHostTrust = { host_trust_id: string, workspace_id: string, hostname: string, port: number, key_algorithm: string, host_key: string, fingerprint: string, current_revision: number, created_at: string, updated_at: string, referenced_repositories: Array<string>, };
export type PutRepositorySshHostTrustRequest = {
operation_id: string;
host_trust_id: string;
hostname: string;
port: number;
host_key: string;
expected_revision: number | null;
};
export type PutRepositorySshHostTrustRequest = { operation_id: string, host_trust_id: string, hostname: string, port: number, host_key: string, expected_revision: number | null, };
export type DeleteRepositorySshHostTrustRequest = {
operation_id: string;
expected_revision: number;
};
export type DeleteRepositorySshHostTrustRequest = { operation_id: string, expected_revision: number, };
export type RepositoryAccessMode = "read_only" | "read_write";
export type RepositorySshAccessBinding = {
repository_key: string;
credential_id: string;
host_trust_id: string;
access: RepositoryAccessMode;
};
export type RepositorySshAccessBinding = { repository_key: string, credential_id: string, host_trust_id: string, access: RepositoryAccessMode, };
export type RepositoryAccessProjection = {
workspace_id: string;
config_revision: number;
projection_digest: string;
bindings: Array<RepositorySshAccessBinding>;
};
export type RepositoryAccessProjection = { workspace_id: string, config_revision: number, projection_digest: string, bindings: Array<RepositorySshAccessBinding>, };
@@ -0,0 +1,34 @@
// @generated by yoi-repository-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:bac8ba9340cf541183e96715dd4ccd8367f965cdcfc398e6d5091f26a903e1ef)
// Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output
// Output: web/workspace/src/lib/generated/repository-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check
export type CreateWorkspaceRepositoryRequest = { default_ref?: string | null; repository_key: string; source: string; };
export type CreateWorkspaceRepositoryResponse = { replayed: boolean; repository_key: string; workspace_id: string; };
export type Diagnostic = { code: string; message: string; severity: DiagnosticSeverity; };
export type DiagnosticSeverity = "info" | "warning" | "error";
export type GitRemoteSummary = { fetch_url: string; name: string; };
export type GitRepositorySummary = { branch?: string | null; dirty: boolean; head?: string | null; remotes: Array<GitRemoteSummary>; status: string; };
export type RepositoryApiError = { diagnostics?: Array<Diagnostic>; error: string; message: string; };
export type RepositoryDetailResponse = { item: RepositorySummary; source: string; workspace_id: string; };
export type RepositoryDiagnostic = { code: string; message: string; severity: string; };
export type RepositoryListResponse = { diagnostics: Array<Diagnostic>; items: Array<RepositorySummary>; source: string; workspace_id: string; };
export type RepositoryObservedStatus = "unverified" | "ready" | "invalid";
export type RepositorySource = { kind: RepositorySourceKind; uri: string; };
export type RepositorySourceKind = "local_path" | "file" | "ssh" | "https" | "invalid";
export type RepositorySummary = { default_selector?: string | null; diagnostics?: Array<RepositoryDiagnostic> | null; git?: GitRepositorySummary | null; kind: string; observed_at?: string | null; observed_status: RepositoryObservedStatus; provider: string; record_authority: string; repository_key: string; source: RepositorySource; source_fingerprint: string; source_revision: number; };
+9 -56
View File
@@ -1,5 +1,5 @@
// Generated from workspace-api. Do not edit by hand.
// Regenerate: cargo run -q -p workspace-api --features typescript --example generate_skill_api_types > web/workspace/src/lib/generated/skill-api.ts
// Generated from server-api. Do not edit by hand.
// Regenerate: cargo run -q -p server-api --features typescript --example generate_skill_api_types > web/workspace/src/lib/generated/skill-api.ts
export const SKILL_API_AUTHORITY = "workspace-config-skills-v1" as const;
@@ -20,69 +20,22 @@ export const SKILL_API_LIMITS = {
export type SkillDiagnosticSeverity = "error" | "warning";
export type SkillDiagnostic = {
severity: SkillDiagnosticSeverity;
code: string;
message: string;
source?: string;
};
export type SkillDiagnostic = { severity: SkillDiagnosticSeverity, code: string, message: string, source?: string, };
export type SkillSourceKind = "builtin" | "workspace";
export type SkillProvenance = {
kind: SkillSourceKind;
id: string;
virtual_path?: string;
revision?: number;
source_digest?: string;
tree_digest?: string;
};
export type SkillProvenance = { kind: SkillSourceKind, id: string, virtual_path?: string, revision?: number, source_digest?: string, tree_digest?: string, };
export type SkillActivationStatus = "active" | "inactive";
export type SkillProjectionStatus = "valid" | "invalid";
export type SkillProjectionIdentity = {
config_revision: number;
tree_digest: string;
};
export type SkillProjectionIdentity = { config_revision: number, tree_digest: string, };
export type SkillResourceRef = {
kind: string;
name: string;
supported: boolean;
diagnostic?: string;
};
export type SkillResourceRef = { kind: string, name: string, supported: boolean, diagnostic?: string, };
export type SkillCatalogEntry = {
name: string;
description: string;
activation_status: SkillActivationStatus;
projection_status: SkillProjectionStatus;
provenance: SkillProvenance;
overrides: Array<SkillProvenance>;
diagnostics: Array<SkillDiagnostic>;
};
export type SkillCatalogEntry = { name: string, description: string, activation_status: SkillActivationStatus, projection_status: SkillProjectionStatus, provenance: SkillProvenance, overrides: Array<SkillProvenance>, diagnostics: Array<SkillDiagnostic>, };
export type SkillCatalogResponse = {
authority: string;
projection: SkillProjectionIdentity;
entries: Array<SkillCatalogEntry>;
diagnostics: Array<SkillDiagnostic>;
};
export type SkillCatalogResponse = { authority: string, projection: SkillProjectionIdentity, entries: Array<SkillCatalogEntry>, diagnostics: Array<SkillDiagnostic>, };
export type SkillDetailResponse = {
authority: string;
projection: SkillProjectionIdentity;
name: string;
description: string;
provenance: SkillProvenance;
overrides: Array<SkillProvenance>;
diagnostics: Array<SkillDiagnostic>;
activation_status: SkillActivationStatus;
projection_status: SkillProjectionStatus;
body: string;
allowed_tools: Array<string>;
allowed_tools_status: string;
resources: Array<SkillResourceRef>;
};
export type SkillDetailResponse = { authority: string, projection: SkillProjectionIdentity, name: string, description: string, provenance: SkillProvenance, overrides: Array<SkillProvenance>, diagnostics: Array<SkillDiagnostic>, activation_status: SkillActivationStatus, projection_status: SkillProjectionStatus, body: string, allowed_tools: Array<string>, allowed_tools_status: string, resources: Array<SkillResourceRef>, };
+11 -64
View File
@@ -1,77 +1,24 @@
// Generated from workspace-api. Do not edit by hand.
// Regenerate: cargo run -q -p workspace-api --features typescript --example generate_workdir_api_types > web/workspace/src/lib/generated/workdir-api.ts
// Generated from server-api. Do not edit by hand.
// Regenerate: cargo run -q -p server-api --features typescript --example generate_workdir_api_types > web/workspace/src/lib/generated/workdir-api.ts
export type DiagnosticSeverity = "info" | "warning" | "error";
export type Diagnostic = {
code: string;
severity: DiagnosticSeverity;
message: string;
};
export type Diagnostic = { code: string, severity: DiagnosticSeverity, message: string, };
export type WorkingDirectoryMaterializerKind = "runtime_git_clone";
export type WorkingDirectoryStatusKind =
| "active"
| "cleanup_pending"
| "corrupted"
| "not_found"
| "unknown";
export type WorkingDirectoryStatusKind = "active" | "cleanup_pending" | "corrupted" | "not_found" | "unknown";
export type WorkingDirectoryCleanupTarget = {
kind: string;
working_directory_id: string;
repository_key: string;
};
export type WorkingDirectoryCleanupTarget = { kind: string, working_directory_id: string, repository_key: string, };
export type WorkingDirectoryOccupancy = {
runtime_id: string;
worker_id: string;
display_name: string;
linked_at: string;
};
export type WorkingDirectoryOccupancy = { runtime_id: string, worker_id: string, display_name: string, linked_at: string, };
export type WorkingDirectorySummary = {
working_directory_id: string;
repository_key: string;
creation_selector?: string | null;
creation_ref?: string | null;
creation_tree?: string | null;
current_selector?: string | null;
current_ref?: string | null;
current_tree?: string | null;
observed_at_epoch_seconds?: number | null;
materializer_kind: WorkingDirectoryMaterializerKind;
cleanup_target?: WorkingDirectoryCleanupTarget | null;
status: WorkingDirectoryStatusKind;
cleanliness?: string | null;
primary_worker_id?: string | null;
occupied_by?: WorkingDirectoryOccupancy | null;
};
export type WorkingDirectorySummary = { working_directory_id: string, repository_key: string, creation_selector?: string | null, creation_ref?: string | null, creation_tree?: string | null, current_selector?: string | null, current_ref?: string | null, current_tree?: string | null, observed_at_epoch_seconds?: number | null, materializer_kind: WorkingDirectoryMaterializerKind, cleanup_target?: WorkingDirectoryCleanupTarget | null, status: WorkingDirectoryStatusKind, cleanliness?: string | null, primary_worker_id?: string | null, occupied_by?: WorkingDirectoryOccupancy | null, };
export type WorkingDirectoryCreateRequest = {
runtime_id?: string | null;
repository_key: string;
selector?: string | null;
operation_id?: string | null;
};
export type WorkingDirectoryCreateRequest = { runtime_id?: string | null, repository_key: string, selector?: string | null, operation_id?: string | null, };
export type WorkingDirectoryListResponse = {
workspace_id: string;
items: Array<WorkingDirectorySummary>;
diagnostics: Array<Diagnostic>;
};
export type WorkingDirectoryListResponse = { workspace_id: string, items: Array<WorkingDirectorySummary>, diagnostics: Array<Diagnostic>, };
export type WorkingDirectoryDetailResponse = {
workspace_id: string;
runtime_id: string;
item: WorkingDirectorySummary;
diagnostics: Array<Diagnostic>;
};
export type WorkingDirectoryDetailResponse = { workspace_id: string, runtime_id: string, item: WorkingDirectorySummary, diagnostics: Array<Diagnostic>, };
export type WorkingDirectoryCreateResponse = {
workspace_id: string;
runtime_id: string;
item: WorkingDirectorySummary;
diagnostics: Array<Diagnostic>;
};
export type WorkingDirectoryCreateResponse = { workspace_id: string, runtime_id: string, item: WorkingDirectorySummary, diagnostics: Array<Diagnostic>, };
@@ -1,183 +1,52 @@
// Generated from workspace-api. Do not edit by hand.
// Regenerate: cargo run -q -p workspace-api --features typescript --example generate_worker_launch_api_types > web/workspace/src/lib/generated/worker-launch-api.ts
// Generated from server-api. Do not edit by hand.
// Regenerate: cargo run -q -p server-api --features typescript --example generate_worker_launch_api_types > web/workspace/src/lib/generated/worker-launch-api.ts
import type { Segment } from "./protocol";
export type DiagnosticSeverity = "info" | "warning" | "error";
export type Diagnostic = {
code: string;
severity: DiagnosticSeverity;
message: string;
};
export type Diagnostic = { code: string, severity: DiagnosticSeverity, message: string, };
export type WorkingDirectoryMaterializerKind = "runtime_git_clone";
export type WorkingDirectoryStatusKind =
| "active"
| "cleanup_pending"
| "corrupted"
| "not_found"
| "unknown";
export type WorkingDirectoryStatusKind = "active" | "cleanup_pending" | "corrupted" | "not_found" | "unknown";
export type WorkingDirectoryCleanupTarget = {
kind: string;
working_directory_id: string;
repository_key: string;
};
export type WorkingDirectoryCleanupTarget = { kind: string, working_directory_id: string, repository_key: string, };
export type RuntimeWorkingDirectoryCleanupTarget = {
kind: string;
working_directory_id: string;
repository_id: string;
};
export type RuntimeWorkingDirectoryCleanupTarget = { kind: string, working_directory_id: string, repository_id: string, };
export type RuntimeWorkingDirectorySummary = {
working_directory_id: string;
repository_id: string;
creation_selector?: string | null;
creation_ref?: string | null;
creation_tree?: string | null;
current_selector?: string | null;
current_ref?: string | null;
current_tree?: string | null;
observed_at_epoch_seconds?: number | null;
materializer_kind: WorkingDirectoryMaterializerKind;
cleanup_target?: RuntimeWorkingDirectoryCleanupTarget | null;
status: WorkingDirectoryStatusKind;
cleanliness?: string | null;
primary_worker_id?: string | null;
occupied_by?: WorkingDirectoryOccupancy | null;
};
export type RuntimeWorkingDirectorySummary = { working_directory_id: string, repository_id: string, creation_selector?: string | null, creation_ref?: string | null, creation_tree?: string | null, current_selector?: string | null, current_ref?: string | null, current_tree?: string | null, observed_at_epoch_seconds?: number | null, materializer_kind: WorkingDirectoryMaterializerKind, cleanup_target?: RuntimeWorkingDirectoryCleanupTarget | null, status: WorkingDirectoryStatusKind, cleanliness?: string | null, primary_worker_id?: string | null, occupied_by?: WorkingDirectoryOccupancy | null, };
export type WorkingDirectoryOccupancy = {
runtime_id: string;
worker_id: string;
display_name: string;
linked_at: string;
};
export type WorkingDirectoryOccupancy = { runtime_id: string, worker_id: string, display_name: string, linked_at: string, };
export type WorkingDirectorySummary = {
working_directory_id: string;
repository_key: string;
creation_selector?: string | null;
creation_ref?: string | null;
creation_tree?: string | null;
current_selector?: string | null;
current_ref?: string | null;
current_tree?: string | null;
observed_at_epoch_seconds?: number | null;
materializer_kind: WorkingDirectoryMaterializerKind;
cleanup_target?: WorkingDirectoryCleanupTarget | null;
status: WorkingDirectoryStatusKind;
cleanliness?: string | null;
primary_worker_id?: string | null;
occupied_by?: WorkingDirectoryOccupancy | null;
};
export type WorkingDirectorySummary = { working_directory_id: string, repository_key: string, creation_selector?: string | null, creation_ref?: string | null, creation_tree?: string | null, current_selector?: string | null, current_ref?: string | null, current_tree?: string | null, observed_at_epoch_seconds?: number | null, materializer_kind: WorkingDirectoryMaterializerKind, cleanup_target?: WorkingDirectoryCleanupTarget | null, status: WorkingDirectoryStatusKind, cleanliness?: string | null, primary_worker_id?: string | null, occupied_by?: WorkingDirectoryOccupancy | null, };
export type WorkerWorkspaceSummary = {
visibility: string;
identity: string;
workspace_id?: string | null;
};
export type WorkerWorkspaceSummary = { visibility: string, identity: string, workspace_id?: string | null, };
export type WorkerImplementationSummary = {
kind: string;
display_hint: string;
};
export type WorkerImplementationSummary = { kind: string, display_hint: string, };
export type WorkerCapabilitySummary = {
can_stop: boolean;
can_spawn_followup: boolean;
};
export type WorkerCapabilitySummary = { can_stop: boolean, can_spawn_followup: boolean, };
export type WorkerLaunchWorkerSummary = {
runtime_id: string;
worker_id: string;
host_id: string;
display_name: string;
label: string;
profile: string | null;
singleton_key: string | null;
tags: Array<string>;
workspace: WorkerWorkspaceSummary;
state: string;
last_seen_at: string | null;
pinned: boolean;
retention_state: string;
implementation: WorkerImplementationSummary;
capabilities: WorkerCapabilitySummary;
working_directory?: RuntimeWorkingDirectorySummary | null;
diagnostics: Array<Diagnostic>;
};
export type WorkerLaunchWorkerSummary = { runtime_id: string, worker_id: string, host_id: string, display_name: string, label: string, profile: string | null, singleton_key: string | null, tags: Array<string>, workspace: WorkerWorkspaceSummary, state: string, last_seen_at: string | null, pinned: boolean, retention_state: string, implementation: WorkerImplementationSummary, capabilities: WorkerCapabilitySummary, working_directory?: RuntimeWorkingDirectorySummary | null, diagnostics: Array<Diagnostic>, };
export type WorkerLaunchRuntimeOption = {
runtime_id: string;
display_name: string;
built_in: boolean;
worker_creation_available: boolean;
working_directory_required: boolean;
status: string;
diagnostics: Array<Diagnostic>;
};
export type WorkerLaunchRuntimeOption = { runtime_id: string, display_name: string, built_in: boolean, worker_creation_available: boolean, working_directory_required: boolean, status: string, diagnostics: Array<Diagnostic>, };
export type WorkerLaunchProfileCandidate = {
id: string;
label: string;
description: string;
};
export type WorkerLaunchProfileCandidate = { id: string, label: string, description: string, };
export type WorkingDirectoryRepositoryOption = {
repository_key: string;
default_selector?: string | null;
};
export type WorkingDirectoryRepositoryOption = { repository_key: string, default_selector?: string | null, };
export type WorkerLaunchOptionsResponse = {
workspace_id: string;
runtimes: Array<WorkerLaunchRuntimeOption>;
default_profile: string | null;
profiles: Array<WorkerLaunchProfileCandidate>;
repositories: Array<WorkingDirectoryRepositoryOption>;
working_directories: Array<WorkingDirectorySummary>;
diagnostics: Array<Diagnostic>;
};
export type WorkerLaunchOptionsResponse = { workspace_id: string, runtimes: Array<WorkerLaunchRuntimeOption>, default_profile: string | null, profiles: Array<WorkerLaunchProfileCandidate>, repositories: Array<WorkingDirectoryRepositoryOption>, working_directories: Array<WorkingDirectorySummary>, diagnostics: Array<Diagnostic>, };
export type BrowserWorkerWorkingDirectorySelection = {
working_directory_id: string;
relative_cwd: string | null;
};
export type BrowserWorkerWorkingDirectorySelection = { working_directory_id: string, relative_cwd: string | null, };
export type CreateWorkspaceWorkerTicketAssignmentRequest = {
ticket_id: string;
operation_id: string;
};
export type CreateWorkspaceWorkerTicketAssignmentRequest = { ticket_id: string, operation_id: string, };
export type CreateWorkspaceWorkerRequest = {
runtime_id: string;
display_name: string;
profile: string | null;
ticket_assignment: CreateWorkspaceWorkerTicketAssignmentRequest | null;
initial_submit: Array<Segment>;
working_directory: BrowserWorkerWorkingDirectorySelection | null;
/**
* Backend idempotency key used only for authenticated Worker-owned spawn/control.
*/
control_operation_id: string | null;
};
export type CreateWorkspaceWorkerRequest = { runtime_id: string, display_name: string, profile: string | null, ticket_assignment: CreateWorkspaceWorkerTicketAssignmentRequest | null, initial_submit: Array<Segment>, working_directory: BrowserWorkerWorkingDirectorySelection | null,
/**
* Backend idempotency key used only for authenticated Worker-owned spawn/control.
*/
control_operation_id: string | null, };
export type BrowserCreateWorkerResponse = {
workspace_id: string;
runtime_id: string;
worker_id: string;
console_href: string;
worker: WorkerLaunchWorkerSummary;
diagnostics: Array<Diagnostic>;
};
export type BrowserCreateWorkerResponse = { workspace_id: string, runtime_id: string, worker_id: string, console_href: string, worker: WorkerLaunchWorkerSummary, diagnostics: Array<Diagnostic>, };
export type BrowserWorkspaceOrchestratorResponse = {
workspace_id: string;
online: boolean;
disposition: string;
worker?: WorkerLaunchWorkerSummary | null;
diagnostics: Array<Diagnostic>;
};
export type BrowserWorkspaceOrchestratorResponse = { workspace_id: string, online: boolean, disposition: string, worker?: WorkerLaunchWorkerSummary | null, diagnostics: Array<Diagnostic>, };
@@ -1,552 +0,0 @@
// This file is generated by `cargo run -p workspace-api --features typescript --example generate_typescript | deno fmt -`.
// Do not edit this file directly.
export type WorkspaceSummary = {
workspace_id: string;
owner_account_id: string;
display_name: string;
state: string;
created_at: string;
updated_at: string;
};
export type WorkspaceCatalogListResponse = Array<WorkspaceSummary>;
export type WorkspaceRepositoryRecord = {
workspace_id: string;
repository_key: string;
kind: string;
provider: string | null;
source: RepositorySource;
default_ref: string | null;
source_revision: number;
source_fingerprint: string;
observed_status: RepositoryObservedStatus;
observed_at: string | null;
created_at: string;
updated_at: string;
};
export type WorkspaceCreateResponse = {
workspace: WorkspaceSummary;
repository: WorkspaceRepositoryRecord;
config_revision: number;
request_fingerprint: string;
replayed: boolean;
};
export type WorkspaceAuthConfig = {
"Passkey": {
rp_id: string;
origin: string;
public_base_url: string;
cookie_name: string;
};
};
export type WorkspacePermissionSummary = {
manage_repositories: boolean;
manage_secrets: boolean;
manage_runtimes: boolean;
delete_workspace: boolean;
};
export type WorkspaceDeletionState =
| "queued"
| "running"
| "blocked"
| "failed"
| "succeeded";
export type WorkspaceDeletionBlockerKind =
| "last_accessible_workspace"
| "revision_conflict"
| "dirty_workdir"
| "worker_removal_blocked"
| "workdir_removal_blocked"
| "retention_hold"
| "cleanup_unavailable";
export type WorkspaceDeletionBlocker = {
kind: WorkspaceDeletionBlockerKind;
resource_kind: string | null;
resource_key: string | null;
message: string;
};
export type WorkspaceDeletionResourceCounts = {
workers: number;
workdirs: number;
repositories: number;
runtime_bindings: number;
secrets: number;
artifacts: number;
};
export type WorkspaceDeletionPreflightResponse = {
workspace_id: string;
display_name: string;
/**
* Opaque persisted Workspace metadata revision used as a CAS fence.
*/
expected_revision: string;
can_delete: boolean;
resources: WorkspaceDeletionResourceCounts;
blockers: Array<WorkspaceDeletionBlocker>;
};
export type WorkspaceDeletionRequest = {
operation_id: string;
expected_revision: string;
confirmation: string;
};
export type WorkspaceDeletionOperationResponse = {
operation_id: string;
workspace_id: string;
display_name: string;
state: WorkspaceDeletionState;
resources: WorkspaceDeletionResourceCounts;
child_operation_ids: Array<string>;
blockers: Array<WorkspaceDeletionBlocker>;
failure_category: string | null;
created_at: string;
updated_at: string;
completed_at: string | null;
};
export type DiagnosticSeverity = "info" | "warning" | "error";
export type Diagnostic = {
code: string;
severity: DiagnosticSeverity;
message: string;
};
export type WorkspaceExtensionPointState = {
status: string;
note: string;
diagnostics: Array<Diagnostic>;
};
export type WorkspaceExtensionPoints = {
store: string;
event_stream: WorkspaceExtensionPointState;
host_worker_bridge: WorkspaceExtensionPointState;
companion_console: WorkspaceExtensionPointState;
};
export type WorkspaceResponse = {
workspace_id: string;
display_name: string;
record_authority: string;
schema_version: number;
auth: WorkspaceAuthConfig;
permissions: WorkspacePermissionSummary;
extension_points: WorkspaceExtensionPoints;
};
export type WorkspaceMetadataSettingsResponse = {
workspace_id: string;
display_name: string;
created_at: string;
revision: string;
source: string;
diagnostics: Array<Diagnostic>;
};
export type UpdateWorkspaceMetadataRequest = {
display_name: string;
revision: string;
};
export type WorkspaceMetadataMutationResponse = {
workspace: WorkspaceMetadataSettingsResponse;
diagnostics: Array<Diagnostic>;
};
export type WorkspaceSigningIdentityState = "pending_provisioning" | "active";
export type WorkspaceSigningIdentityPublic = {
workspace_id: string;
key_id: string;
algorithm: string;
public_key?: string;
public_key_fingerprint?: string;
revision: number;
state: WorkspaceSigningIdentityState;
created_at: string;
provisioned_at?: string;
};
export type WorkspacePublicIdentityBundle = {
workspace_id: string;
backend_url: string;
key_id: string;
algorithm: string;
public_key: string;
public_key_fingerprint: string;
revision: number;
};
export type WorkspaceSigningIdentityResponse = {
identity: WorkspaceSigningIdentityPublic;
public_bundle?: WorkspacePublicIdentityBundle;
};
export type ProfileSettingsResponse = {
workspace_id: string;
registry_revision: string;
config_revision?: number | null;
tree_digest?: string | null;
projection_digest?: string | null;
default_profile?: string | null;
profiles: Array<WorkspaceProfileSummary>;
sources: Array<WorkspaceProfileSourceSummary>;
diagnostics: Array<Diagnostic>;
};
export type WorkspaceProfileSummary = {
profile_id: string;
selector: string;
label: string;
source_kind: string;
profile_source_id?: string | null;
description?: string | null;
editable: boolean;
is_default: boolean;
diagnostics: Array<Diagnostic>;
};
export type WorkspaceProfileSourceSummary = {
profile_source_id: string;
display_path: string;
kind: string;
content_type: string;
content_digest: string;
provenance: WorkspaceProfileSourceProvenance;
editable: boolean;
revision: string;
size_bytes: number;
diagnostics: Array<Diagnostic>;
};
export type WorkspaceProfileSourceProvenance = "project_profile_source_tree";
export type RepositorySourceKind =
| "local_path"
| "file"
| "ssh"
| "https"
| "invalid";
export type RepositorySource = {
kind: RepositorySourceKind;
/**
* Canonical source representation. This is an absolute local path for
* `local_path`, and a normalized URI/remote specification otherwise.
*/
uri: string;
};
export type RepositoryObservedStatus = "unverified" | "ready" | "invalid";
export type RepositoryDiagnostic = {
severity: string;
code: string;
message: string;
};
export type GitRemoteSummary = { name: string; fetch_url: string };
export type GitRepositorySummary = {
status: string;
head: string | null;
branch: string | null;
dirty: boolean;
remotes: Array<GitRemoteSummary>;
};
export type RepositorySummary = {
repository_key: string;
kind: string;
provider: string;
source: RepositorySource;
source_revision: number;
source_fingerprint: string;
observed_status: RepositoryObservedStatus;
observed_at?: string | null;
default_selector?: string | null;
record_authority: string;
git?: GitRepositorySummary | null;
diagnostics?: Array<RepositoryDiagnostic> | null;
};
export type GitCommitSummary = {
hash: string;
short_hash: string;
summary: string;
author_name: string;
author_email: string;
author_date: string;
parents: Array<string>;
refs: Array<string>;
};
export type RepositoryListResponse = {
workspace_id: string;
items: Array<RepositorySummary>;
source: string;
diagnostics: Array<Diagnostic>;
};
export type RepositoryDetailResponse = {
workspace_id: string;
item: RepositorySummary;
source: string;
};
export type RepositorySshConnectionProbeRequest = { runtime_id: string };
export type RepositorySshHostKeyCandidate = {
algorithm: string;
host_key: string;
fingerprint: string;
};
export type RepositorySshConnectionTrustState =
| "untrusted"
| "verified"
| "changed";
export type RepositorySshConnectionProbeResponse = {
workspace_id: string;
repository_key: string;
runtime_id: string;
hostname: string;
port: number;
trust_state: RepositorySshConnectionTrustState;
host_trust_id: string;
expected_host_trust_revision: number | null;
candidates: Array<RepositorySshHostKeyCandidate>;
};
export type ConfirmRepositorySshHostTrustRequest = {
operation_id: string;
runtime_id: string;
host_key: string;
expected_host_trust_revision: number | null;
};
export type RepositoryLogResponse = {
workspace_id: string;
repository_key: string;
default_selector?: string | null;
limit: number;
items: Array<GitCommitSummary>;
diagnostics: Array<Diagnostic>;
};
export type RuntimeSourceKind = "embedded_worker_runtime" | "remote_http";
export type RuntimeSourceStatus = "active" | "reserved";
export type RuntimeIdentityAuthority =
| "runtime_registry_projection"
| "server_runtime_configuration";
export type RuntimeSourceSummary = {
kind: RuntimeSourceKind;
status: RuntimeSourceStatus;
identity_authority: RuntimeIdentityAuthority;
note: string;
};
export type RuntimeSummary = {
runtime_id: string;
label: string;
kind: string;
status: string;
source: RuntimeSourceSummary;
host_ids: Array<string>;
worker_creation_available: boolean;
os: string;
arch: string;
diagnostics: Array<Diagnostic>;
};
export type WorkspaceRuntimeBindingState =
| "configured"
| "verified"
| "revoked";
export type RuntimeConnectionDisplayState =
| "configured"
| "verified"
| "unavailable"
| "revoked";
export type RuntimeVerificationOutcome =
| "verified"
| "challenge_issued"
| "verification_failed"
| "connectivity_failed";
export type RuntimeVerificationEvidenceSummary = {
verified_at: string | null;
last_checked_at: string;
last_outcome: RuntimeVerificationOutcome;
binding_revision: number;
workspace_key_id: string;
workspace_identity_revision: number;
workspace_trust_generation: number;
runtime_public_key_fingerprint: string;
runtime_identity_revision: number;
};
export type WorkspaceRuntimeBindingSummary = {
state: WorkspaceRuntimeBindingState;
connection_state: RuntimeConnectionDisplayState;
revision: number;
workspace_key_id?: string | null;
workspace_key_generation?: number | null;
verification?: RuntimeVerificationEvidenceSummary | null;
};
export type RuntimeManagementSummary = {
built_in: boolean;
config_managed: boolean;
removable: boolean;
endpoint_configured: boolean;
token_ref_configured: boolean;
binding?: WorkspaceRuntimeBindingSummary | null;
};
export type WorkspaceRuntimeResource = {
management: RuntimeManagementSummary;
runtime_id: string;
label: string;
kind: string;
status: string;
source: RuntimeSourceSummary;
host_ids: Array<string>;
worker_creation_available: boolean;
os: string;
arch: string;
diagnostics: Array<Diagnostic>;
};
export type RuntimeTrustKeyStatus = "unconfigured" | "active" | "revoked";
export type RuntimeTrustKeyState = {
status: RuntimeTrustKeyStatus;
fingerprint?: string | null;
revision?: number | null;
created_at?: string | null;
updated_at?: string | null;
revoked_at?: string | null;
};
export type RuntimeTrustAuditAction =
| "created"
| "replaced"
| "reactivated"
| "revoked";
export type RuntimeTrustAuditEntry = {
action: RuntimeTrustAuditAction;
actor_account_id: string;
old_fingerprint?: string | null;
new_fingerprint?: string | null;
revision: number;
at: string;
};
export type WorkspaceRuntimeDetail = {
workspace_id: string;
runtime: WorkspaceRuntimeResource;
endpoint?: string | null;
trust_key: RuntimeTrustKeyState;
recent_audit: Array<RuntimeTrustAuditEntry>;
};
export type RuntimeTrustKeyRevealResponse = { public_key: string };
export type RevokeRuntimeTrustKeyRequest = { expected_revision: number };
export type RemoveRuntimeRequest = {
operation_id: string;
expected_binding_revision: number;
};
export type RuntimeRemovalOperationState =
| "pending"
| "cleanup_pending"
| "succeeded"
| "failed";
export type RuntimeRemovalOperationResponse = {
operation_id: string;
workspace_id: string;
runtime_id: string;
state: RuntimeRemovalOperationState;
binding_removed: boolean;
runtime_registration_removed: boolean | null;
failure_category?: string | null;
created_at: string;
updated_at: string;
completed_at?: string | null;
};
export type RuntimeTrustConflictKind = "stale_revision" | "fingerprint_in_use";
export type RuntimeTrustConflictResponse = {
error: RuntimeTrustConflictKind;
message: string;
current_revision?: number;
current_fingerprint?: string | null;
};
export type RuntimePublicIdentityBundle = {
identity_id: string;
public_key: string;
};
export type CreateRemoteRuntimeRequest = {
public_bundle: RuntimePublicIdentityBundle;
display_name?: string | null;
endpoint: string;
expected_revision?: number | null;
};
export type UpdateRemoteRuntimeRequest = {
display_name?: string | null;
endpoint: string;
};
export type RuntimeConnectionTestStatus = "compatible" | "failed";
export type RuntimeConnectionTestFailureKind =
| "authentication"
| "authorization"
| "network_unreachable"
| "timeout"
| "tls_or_transport"
| "malformed_response"
| "protocol_version_mismatch"
| "runtime_identity_mismatch"
| "configuration";
export type RuntimeConnectionTestResponse = {
workspace_id: string;
runtime_id: string;
binding_revision: number;
connection_state: RuntimeConnectionDisplayState;
verification: RuntimeVerificationEvidenceSummary | null;
checked_at: string;
status: RuntimeConnectionTestStatus;
failure_kind: RuntimeConnectionTestFailureKind | null;
expected_protocol_version: number;
actual_protocol_version: number | null;
diagnostics: Array<Diagnostic>;
};
+2 -2
View File
@@ -16,7 +16,7 @@ export type ApiResult<T> = {
export type { SkillCatalogResponse, SkillDetailResponse };
type JsonLoadPolicy = {
export type JsonLoadPolicy = {
diagnosticLabel: string;
maxResponseBytes: number;
};
@@ -133,7 +133,7 @@ export async function loadJson<T>(
}
}
async function readBoundedJson(
export async function readBoundedJson(
response: Response,
maxBytes: number,
): Promise<unknown> {
@@ -0,0 +1,102 @@
import type {
CreateWorkspaceRepositoryRequest,
CreateWorkspaceRepositoryResponse,
RepositoryDetailResponse,
RepositoryListResponse,
} from "$lib/generated/repository-api.ts";
import {
type ApiResult,
loadJson,
readBoundedJson,
workspaceApiPath,
} from "$lib/workspace/api/http.ts";
import {
parseCreateWorkspaceRepositoryResponse,
parseRepositoryApiError,
parseRepositoryDetailResponse,
parseRepositoryListResponse,
} from "$lib/workspace/api/workspace-model.ts";
export const REPOSITORY_API_MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
const REPOSITORY_API_LOAD_POLICY = {
diagnosticLabel: "Repository API",
maxResponseBytes: REPOSITORY_API_MAX_RESPONSE_BYTES,
} as const;
export function workspaceRepositoryListPath(workspaceId: string): string {
return workspaceApiPath(workspaceId, "/repositories");
}
export function workspaceRepositoryDetailPath(
workspaceId: string,
repositoryKey: string,
): string {
return workspaceApiPath(
workspaceId,
`/repositories/${encodeURIComponent(repositoryKey)}`,
);
}
export async function loadWorkspaceRepositoryList(
fetchFn: typeof fetch,
workspaceId: string,
): Promise<ApiResult<RepositoryListResponse>> {
return loadJson(
fetchFn,
workspaceRepositoryListPath(workspaceId),
undefined,
parseRepositoryListResponse,
REPOSITORY_API_LOAD_POLICY,
);
}
export async function loadWorkspaceRepositoryDetail(
fetchFn: typeof fetch,
workspaceId: string,
repositoryKey: string,
): Promise<ApiResult<RepositoryDetailResponse>> {
return loadJson(
fetchFn,
workspaceRepositoryDetailPath(workspaceId, repositoryKey),
undefined,
parseRepositoryDetailResponse,
REPOSITORY_API_LOAD_POLICY,
);
}
export async function createWorkspaceRepository(
fetchFn: typeof fetch,
workspaceId: string,
request: CreateWorkspaceRepositoryRequest,
): Promise<ApiResult<CreateWorkspaceRepositoryResponse>> {
const path = workspaceRepositoryListPath(workspaceId);
try {
const response = await fetchFn(path, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(request),
});
const payload = await readBoundedJson(
response,
REPOSITORY_API_MAX_RESPONSE_BYTES,
);
if (!response.ok) {
try {
const error = parseRepositoryApiError(payload);
return { data: null, error: error.message };
} catch {
return {
data: null,
error: `Repository API request failed with HTTP ${response.status}`,
};
}
}
return {
data: parseCreateWorkspaceRepositoryResponse(payload),
error: null,
};
} catch {
return { data: null, error: "Repository API response is invalid" };
}
}
@@ -3,7 +3,7 @@ import type {
RuntimeConnectionTestFailureKind,
RuntimeConnectionTestResponse,
RuntimeVerificationEvidenceSummary,
} from "$lib/generated/workspace-api";
} from "$lib/generated/legacy-server-api";
const RESPONSE_KEYS = [
"workspace_id",
@@ -24,7 +24,7 @@ import type {
WorkspaceRuntimeBindingSummary,
WorkspaceRuntimeDetail,
WorkspaceRuntimeResource,
} from "$lib/generated/workspace-api.ts";
} from "$lib/generated/legacy-server-api.ts";
import type { ListResponse } from "$lib/workspace/sidebar/types";
import { workspaceApiPath } from "./http.ts";
@@ -1,11 +1,11 @@
import {
parseRepositoryListResponse,
parseWorkspaceCatalogResponse,
parseWorkspaceCreateResponse,
type RepositorySummary,
type WorkspaceCreateResponse,
type WorkspaceSummary,
} from "$lib/workspace/api/workspace-model";
import { loadWorkspaceRepositoryList } from "$lib/workspace/api/repositories.ts";
export type WorkspaceCatalogRecord = WorkspaceSummary;
export type WorkspaceCatalogItem = WorkspaceCatalogRecord & {
@@ -48,12 +48,14 @@ export async function listWorkspaceRepositories(
fetcher: Fetch,
workspaceId: string,
): Promise<RepositorySummary[]> {
return parseRepositoryListResponse(
await fetchJson(
fetcher,
`/api/w/${encodeURIComponent(workspaceId)}/repositories`,
),
).items;
const result = await loadWorkspaceRepositoryList(fetcher, workspaceId);
if (result.data === null) {
throw new WorkspaceCatalogError(
null,
result.error ?? "Repository API request failed",
);
}
return result.data.items;
}
export async function loadWorkspaceCatalog(
@@ -1,19 +1,10 @@
import type { ApiResult } from "$lib/workspace/api/http";
import type {
Diagnostic,
GitCommitSummary,
GitRemoteSummary,
GitRepositorySummary,
RepositoryDetailResponse,
RepositoryDiagnostic,
RepositoryListResponse,
RepositoryLogResponse,
RepositorySource,
RepositorySourceKind,
RepositorySshConnectionProbeResponse,
RepositorySshConnectionTrustState,
RepositorySshHostKeyCandidate,
RepositorySummary,
WorkspaceAuthConfig,
WorkspaceCatalogListResponse,
WorkspaceCreateResponse,
@@ -29,18 +20,26 @@ import type {
WorkspaceRepositoryRecord,
WorkspaceResponse,
WorkspaceSummary,
} from "$lib/generated/workspace-api.ts";
} from "$lib/generated/legacy-server-api.ts";
import type {
CreateWorkspaceRepositoryResponse,
Diagnostic,
GitRemoteSummary,
GitRepositorySummary,
RepositoryApiError,
RepositoryDetailResponse,
RepositoryDiagnostic,
RepositoryListResponse,
RepositorySource,
RepositorySourceKind,
RepositorySummary,
} from "$lib/generated/repository-api.ts";
export type {
GitCommitSummary,
GitRemoteSummary,
GitRepositorySummary,
RepositoryDetailResponse,
RepositoryListResponse,
RepositoryLogResponse,
RepositorySshConnectionProbeResponse,
RepositorySshHostKeyCandidate,
RepositorySummary,
WorkspaceCatalogListResponse,
WorkspaceCreateResponse,
WorkspaceDeletionOperationResponse,
@@ -48,7 +47,19 @@ export type {
WorkspacePermissionSummary,
WorkspaceResponse,
WorkspaceSummary,
} from "$lib/generated/workspace-api.ts";
} from "$lib/generated/legacy-server-api.ts";
export type {
CreateWorkspaceRepositoryRequest,
CreateWorkspaceRepositoryResponse,
GitRemoteSummary,
GitRepositorySummary,
RepositoryApiError,
RepositoryDetailResponse,
RepositoryListResponse,
RepositorySource,
RepositorySourceKind,
RepositorySummary,
} from "$lib/generated/repository-api.ts";
type JsonObject = Record<string, unknown>;
@@ -62,6 +73,11 @@ const SOURCE_KINDS = new Set<RepositorySourceKind>([
const OBSERVED_STATUSES = new Set(["unverified", "ready", "invalid"]);
const DIAGNOSTIC_SEVERITIES = new Set(["info", "warning", "error"]);
export const REPOSITORY_API_LIMITS = {
maxStringCodeUnits: 65_536,
maxCollectionEntries: 4_096,
} as const;
function object(value: unknown, path: string): JsonObject {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new Error(`${path} must be an object`);
@@ -95,6 +111,34 @@ function nullableString(value: unknown, path: string): string | null {
return value === null ? null : string(value, path);
}
function repositoryString(value: unknown, path: string): string {
const parsed = string(value, path);
if (parsed.length > REPOSITORY_API_LIMITS.maxStringCodeUnits) {
throw new Error(`${path} exceeds the Repository API string limit`);
}
return parsed;
}
function nullableRepositoryString(value: unknown, path: string): string | null {
return value === null ? null : repositoryString(value, path);
}
function repositoryArray(value: unknown, path: string): unknown[] {
const parsed = array(value, path);
if (parsed.length > REPOSITORY_API_LIMITS.maxCollectionEntries) {
throw new Error(`${path} exceeds the Repository API collection limit`);
}
return parsed;
}
function repositorySourceRevision(value: unknown, path: string): number {
const parsed = integer(value, path);
if (parsed < 0) {
throw new Error(`${path} must be between 0 and Number.MAX_SAFE_INTEGER`);
}
return parsed;
}
function optionalNullableString(
value: unknown,
path: string,
@@ -102,6 +146,15 @@ function optionalNullableString(
return value === undefined ? undefined : nullableString(value, path);
}
function optionalNullableRepositoryString(
value: unknown,
path: string,
): string | null | undefined {
return value === undefined
? undefined
: nullableRepositoryString(value, path);
}
function exactKeys(
value: JsonObject,
keys: readonly string[],
@@ -128,6 +181,20 @@ function diagnostic(value: unknown, path: string): Diagnostic {
};
}
function repositoryApiDiagnostic(value: unknown, path: string): Diagnostic {
const item = object(value, path);
exactKeys(item, ["code", "severity", "message"], path);
const severity = repositoryString(item.severity, `${path}.severity`);
if (!DIAGNOSTIC_SEVERITIES.has(severity)) {
throw new Error(`${path}.severity is invalid`);
}
return {
code: repositoryString(item.code, `${path}.code`),
severity: severity as Diagnostic["severity"],
message: repositoryString(item.message, `${path}.message`),
};
}
function repositoryDiagnostic(
value: unknown,
path: string,
@@ -135,22 +202,22 @@ function repositoryDiagnostic(
const item = object(value, path);
exactKeys(item, ["severity", "code", "message"], path);
return {
severity: string(item.severity, `${path}.severity`),
code: string(item.code, `${path}.code`),
message: string(item.message, `${path}.message`),
severity: repositoryString(item.severity, `${path}.severity`),
code: repositoryString(item.code, `${path}.code`),
message: repositoryString(item.message, `${path}.message`),
};
}
function repositorySource(value: unknown, path: string): RepositorySource {
const source = object(value, path);
exactKeys(source, ["kind", "uri"], path);
const kind = string(source.kind, `${path}.kind`);
const kind = repositoryString(source.kind, `${path}.kind`);
if (!SOURCE_KINDS.has(kind as RepositorySourceKind)) {
throw new Error(`${path}.kind is invalid`);
}
return {
kind: kind as RepositorySourceKind,
uri: string(source.uri, `${path}.uri`),
uri: repositoryString(source.uri, `${path}.uri`),
};
}
@@ -158,8 +225,8 @@ function gitRemote(value: unknown, path: string): GitRemoteSummary {
const remote = object(value, path);
exactKeys(remote, ["name", "fetch_url"], path);
return {
name: string(remote.name, `${path}.name`),
fetch_url: string(remote.fetch_url, `${path}.fetch_url`),
name: repositoryString(remote.name, `${path}.name`),
fetch_url: repositoryString(remote.fetch_url, `${path}.fetch_url`),
};
}
@@ -167,13 +234,14 @@ function gitSummary(value: unknown, path: string): GitRepositorySummary {
const git = object(value, path);
exactKeys(git, ["status", "head", "branch", "dirty", "remotes"], path);
return {
status: string(git.status, `${path}.status`),
head: nullableString(git.head, `${path}.head`),
branch: nullableString(git.branch, `${path}.branch`),
status: repositoryString(git.status, `${path}.status`),
head: optionalNullableRepositoryString(git.head, `${path}.head`),
branch: optionalNullableRepositoryString(git.branch, `${path}.branch`),
dirty: boolean(git.dirty, `${path}.dirty`),
remotes: array(git.remotes, `${path}.remotes`).map((item, index) =>
gitRemote(item, `${path}.remotes[${index}]`)
),
remotes: repositoryArray(git.remotes, `${path}.remotes`).map((
item,
index,
) => gitRemote(item, `${path}.remotes[${index}]`)),
};
}
@@ -197,7 +265,7 @@ function repositorySummary(value: unknown, path: string): RepositorySummary {
],
path,
);
const observedStatus = string(
const observedStatus = repositoryString(
item.observed_status,
`${path}.observed_status`,
);
@@ -207,29 +275,39 @@ function repositorySummary(value: unknown, path: string): RepositorySummary {
const diagnostics =
item.diagnostics === undefined || item.diagnostics === null
? item.diagnostics
: array(item.diagnostics, `${path}.diagnostics`).map((entry, index) =>
repositoryDiagnostic(entry, `${path}.diagnostics[${index}]`)
);
: repositoryArray(item.diagnostics, `${path}.diagnostics`).map((
entry,
index,
) => repositoryDiagnostic(entry, `${path}.diagnostics[${index}]`));
return {
repository_key: string(item.repository_key, `${path}.repository_key`),
kind: string(item.kind, `${path}.kind`),
provider: string(item.provider, `${path}.provider`),
repository_key: repositoryString(
item.repository_key,
`${path}.repository_key`,
),
kind: repositoryString(item.kind, `${path}.kind`),
provider: repositoryString(item.provider, `${path}.provider`),
source: repositorySource(item.source, `${path}.source`),
source_revision: integer(item.source_revision, `${path}.source_revision`),
source_fingerprint: string(
source_revision: repositorySourceRevision(
item.source_revision,
`${path}.source_revision`,
),
source_fingerprint: repositoryString(
item.source_fingerprint,
`${path}.source_fingerprint`,
),
observed_status: observedStatus as RepositorySummary["observed_status"],
observed_at: optionalNullableString(
observed_at: optionalNullableRepositoryString(
item.observed_at,
`${path}.observed_at`,
),
default_selector: optionalNullableString(
default_selector: optionalNullableRepositoryString(
item.default_selector,
`${path}.default_selector`,
),
record_authority: string(item.record_authority, `${path}.record_authority`),
record_authority: repositoryString(
item.record_authority,
`${path}.record_authority`,
),
git: item.git === undefined || item.git === null
? item.git
: gitSummary(item.git, `${path}.git`),
@@ -287,7 +365,7 @@ function workspaceRepositoryRecord(
],
path,
);
const observedStatus = string(
const observedStatus = repositoryString(
item.observed_status,
`${path}.observed_status`,
);
@@ -301,8 +379,11 @@ function workspaceRepositoryRecord(
provider: nullableString(item.provider, `${path}.provider`),
source: repositorySource(item.source, `${path}.source`),
default_ref: nullableString(item.default_ref, `${path}.default_ref`),
source_revision: integer(item.source_revision, `${path}.source_revision`),
source_fingerprint: string(
source_revision: repositorySourceRevision(
item.source_revision,
`${path}.source_revision`,
),
source_fingerprint: repositoryString(
item.source_fingerprint,
`${path}.source_fingerprint`,
),
@@ -533,21 +614,28 @@ export function parseRepositoryListResponse(
"repository list response",
);
return {
workspace_id: string(
workspace_id: repositoryString(
response.workspace_id,
"repository list response.workspace_id",
),
items: array(response.items, "repository list response.items").map((
item,
index,
) => repositorySummary(item, `repository list response.items[${index}]`)),
source: string(response.source, "repository list response.source"),
diagnostics: array(
items: repositoryArray(response.items, "repository list response.items")
.map((
item,
index,
) => repositorySummary(item, `repository list response.items[${index}]`)),
source: repositoryString(
response.source,
"repository list response.source",
),
diagnostics: repositoryArray(
response.diagnostics,
"repository list response.diagnostics",
).map(
(item, index) =>
diagnostic(item, `repository list response.diagnostics[${index}]`),
repositoryApiDiagnostic(
item,
`repository list response.diagnostics[${index}]`,
),
),
};
}
@@ -578,12 +666,64 @@ export function parseRepositoryDetailResponse(
"repository detail response",
);
return {
workspace_id: string(
workspace_id: repositoryString(
response.workspace_id,
"repository detail response.workspace_id",
),
item: repositorySummary(response.item, "repository detail response.item"),
source: string(response.source, "repository detail response.source"),
source: repositoryString(
response.source,
"repository detail response.source",
),
};
}
export function parseCreateWorkspaceRepositoryResponse(
value: unknown,
): CreateWorkspaceRepositoryResponse {
const response = object(value, "repository create response");
exactKeys(
response,
["replayed", "repository_key", "workspace_id"],
"repository create response",
);
return {
replayed: boolean(response.replayed, "repository create response.replayed"),
repository_key: repositoryString(
response.repository_key,
"repository create response.repository_key",
),
workspace_id: repositoryString(
response.workspace_id,
"repository create response.workspace_id",
),
};
}
export function parseRepositoryApiError(value: unknown): RepositoryApiError {
const response = object(value, "repository error response");
exactKeys(
response,
["diagnostics", "error", "message"],
"repository error response",
);
return {
diagnostics: response.diagnostics === undefined
? undefined
: repositoryArray(
response.diagnostics,
"repository error response.diagnostics",
).map((item, index) =>
repositoryApiDiagnostic(
item,
`repository error response.diagnostics[${index}]`,
)
),
error: repositoryString(response.error, "repository error response.error"),
message: repositoryString(
response.message,
"repository error response.message",
),
};
}
@@ -133,7 +133,7 @@
{:else if item.kind === 'user'}
<div class="user-message">
<span class="user-prompt" aria-hidden="true">&gt;</span>
<div><RichMarkdown text={item.body || '—'} /></div>
<div><RichMarkdown text={item.body || '—'} streamId={item.id} /></div>
</div>
{:else if item.kind === 'activity'}
<p class="activity-summary">{item.body || '—'}</p>
@@ -142,7 +142,7 @@
{:else if item.kind === 'run_stats'}
<p class="run-stats">{item.body}</p>
{:else if shouldRenderMarkdown(item)}
<RichMarkdown text={item.body || '—'} />
<RichMarkdown text={item.body || '—'} streamId={item.id} />
{:else}
<p class="console-plain-text">{item.body || '—'}</p>
{/if}
@@ -0,0 +1,32 @@
<script lang="ts">
import type { Snippet } from "svelte";
let {
href = "",
title,
children,
}: {
href?: string;
title?: string;
children?: Snippet;
} = $props();
let safeHref = $derived(httpHref(href));
function httpHref(value: string): string | undefined {
try {
const protocol = new URL(value).protocol.toLowerCase();
return protocol === "http:" || protocol === "https:" ? value : undefined;
} catch {
return undefined;
}
}
</script>
{#if safeHref}
<a href={safeHref} {title} target="_blank" rel="noreferrer">
{@render children?.()}
</a>
{:else}
{@render children?.()}
{/if}
@@ -0,0 +1,209 @@
// @vitest-environment happy-dom
import { cleanup, render, waitFor } from "@testing-library/svelte";
import { afterEach, expect, test } from "vitest";
import RichMarkdown from "./RichMarkdown.svelte";
afterEach(cleanup);
test("keeps completed block nodes while a trailing inline token grows", async () => {
const initial = [
"# Stable heading",
"",
"This paragraph is already complete.",
"",
"- stable list item",
"- another item",
"",
"> stable quote",
"",
"```ts",
"const answer: number = 42;",
"```",
"",
"The final paragraph has **partial",
].join("\n");
const { container, rerender } = render(RichMarkdown, {
text: initial,
streamId: "message-1",
});
await waitFor(() => {
expect(container.querySelector("h1")?.textContent).toBe("Stable heading");
expect(container.querySelector("ul")).not.toBeNull();
expect(container.querySelector("blockquote")).not.toBeNull();
expect(container.querySelector("pre.shiki code span")).not.toBeNull();
});
const stableNodes = [
container.querySelector("h1"),
container.querySelector("p"),
container.querySelector("ul"),
container.querySelector("blockquote"),
container.querySelector("pre"),
];
await rerender({
text: `${initial} emphasis** and a [safe link](https://exam`,
streamId: "message-1",
});
await waitFor(() => {
expect(container.querySelector("strong")?.textContent).toBe(
"partial emphasis",
);
expect(container.textContent).toContain("[safe link](https://exam");
});
expect(container.querySelector('a[href="https://example.com"]')).toBeNull();
expect(container.querySelector("h1")).toBe(stableNodes[0]);
expect(container.querySelector("p")).toBe(stableNodes[1]);
expect(container.querySelector("ul")).toBe(stableNodes[2]);
expect(container.querySelector("blockquote")).toBe(stableNodes[3]);
expect(container.querySelector("pre")).toBe(stableNodes[4]);
await rerender({
text: `${initial} emphasis** and a [safe link](https://example.com).`,
streamId: "message-1",
});
await waitFor(() => {
expect(container.querySelector("strong")?.textContent).toBe(
"partial emphasis",
);
expect(container.querySelector('a[href="https://example.com"]')).not
.toBeNull();
});
expect(container.querySelector("h1")).toBe(stableNodes[0]);
expect(container.querySelector("p")).toBe(stableNodes[1]);
expect(container.querySelector("ul")).toBe(stableNodes[2]);
expect(container.querySelector("blockquote")).toBe(stableNodes[3]);
expect(container.querySelector("pre")).toBe(stableNodes[4]);
});
test("resets the DOM for non-prefix rewrites and stream identity changes", async () => {
const { container, rerender } = render(RichMarkdown, {
text: "# Original\n\nOriginal body.",
streamId: "message-1",
});
await waitFor(() => {
expect(container.querySelector("h1")?.textContent).toBe("Original");
});
await rerender({
text: "# Corrected\n\nRewritten body.",
streamId: "message-1",
});
await waitFor(() => {
expect(container.querySelector("h1")?.textContent).toBe("Corrected");
});
expect(container.textContent).not.toContain("Original body.");
await rerender({
text: "# Corrected\n\nRewritten body for a new message.",
streamId: "message-2",
});
await waitFor(() => {
expect(container.querySelector("p")?.textContent).toBe(
"Rewritten body for a new message.",
);
});
expect(container.querySelector("h1")?.textContent).toBe("Corrected");
expect(container.textContent).not.toContain("Original body.");
});
test("preserves line breaks and the HTTP(S)-only link boundary", async () => {
const source = [
"first line\nsecond line",
"",
"[https](https://example.com) [http](http://example.com)",
"[script](javascript:alert(1)) [data](data:text/html,hello)",
"[mail](mailto:test@example.com) [relative](/internal)",
"",
'<img src="x" onerror="alert(1)">',
].join("\n");
const { container } = render(RichMarkdown, {
text: source,
streamId: "message-security",
});
await waitFor(() => {
expect(container.querySelectorAll("a")).toHaveLength(2);
expect(container.querySelector("br")).not.toBeNull();
});
const links = container.querySelectorAll("a");
expect(links[0].getAttribute("href")).toBe("https://example.com");
expect(links[1].getAttribute("href")).toBe("http://example.com");
expect(links[0].getAttribute("target")).toBe("_blank");
expect(links[0].getAttribute("rel")).toBe("noreferrer");
expect(container.textContent).toContain("script");
expect(container.textContent).toContain("relative");
expect(container.querySelector("img")).toBeNull();
expect(container.querySelector("script")).toBeNull();
});
test("keeps prior content through an unclosed fence and unknown-language fallback", async () => {
const initial = [
"Stable paragraph.",
"",
"```not-a-real-language",
"raw <text>",
].join("\n");
const { container, rerender } = render(RichMarkdown, {
text: initial,
streamId: "message-fence",
});
await waitFor(() => {
expect(container.querySelector("pre.shiki-fallback code")?.textContent)
.toContain(
"raw <text>",
);
});
const stableParagraph = container.querySelector("p");
await rerender({
text: `${initial}\n\`\`\`\n\nAfter the fence.`,
streamId: "message-fence",
});
await waitFor(() => {
expect(container.querySelectorAll("p")).toHaveLength(2);
expect(container.querySelectorAll("p")[1]?.textContent).toBe(
"After the fence.",
);
});
expect(container.querySelector("p")).toBe(stableParagraph);
expect(container.querySelector("pre.shiki-fallback code")?.textContent)
.toContain(
"raw <text>",
);
});
test("reuses early DOM nodes across a long streaming append", async () => {
const firstBatch = Array.from(
{ length: 80 },
(_, index) => `Paragraph ${index}.`,
);
const { container, rerender } = render(RichMarkdown, {
text: firstBatch.join("\n\n"),
streamId: "message-long",
});
await waitFor(
() => expect(container.querySelectorAll("p")).toHaveLength(80),
{ timeout: 5_000 },
);
const first = container.querySelectorAll("p")[0];
const middle = container.querySelectorAll("p")[40];
const secondBatch = Array.from(
{ length: 40 },
(_, index) => `Paragraph ${index + 80}.`,
);
await rerender({
text: [...firstBatch, ...secondBatch].join("\n\n"),
streamId: "message-long",
});
await waitFor(
() => expect(container.querySelectorAll("p")).toHaveLength(120),
{ timeout: 5_000 },
);
expect(container.querySelectorAll("p")[0]).toBe(first);
expect(container.querySelectorAll("p")[40]).toBe(middle);
}, 10_000);
@@ -1,41 +1,55 @@
<script module lang="ts">
import {
createShikiHighlighter,
setShikiHighlighter,
} from "@humanspeak/svelte-markdown/extensions/shiki";
import bash from "shiki/langs/bash.mjs";
import javascript from "shiki/langs/javascript.mjs";
import json from "shiki/langs/json.mjs";
import python from "shiki/langs/python.mjs";
import rust from "shiki/langs/rust.mjs";
import typescript from "shiki/langs/typescript.mjs";
import kanagawaWave from "shiki/themes/kanagawa-wave.mjs";
setShikiHighlighter(
createShikiHighlighter({
themes: [kanagawaWave],
langs: [bash, javascript, json, python, rust, typescript],
}),
);
</script>
<script lang="ts">
import { markdownToHtml } from "$lib/workspace/console/markdown";
import SvelteMarkdown, {
buildUnsupportedHTML,
type Renderers,
type SvelteMarkdownProps,
} from "@humanspeak/svelte-markdown";
import { ShikiCode } from "@humanspeak/svelte-markdown/extensions/shiki";
import MarkdownLink from "$lib/workspace/console/MarkdownLink.svelte";
type Props = {
text: string;
streamId?: string | number;
class?: string;
};
let { text, class: className = "" }: Props = $props();
let html = $state("");
let rendering = $state(false);
let { text, streamId = "static", class: className = "" }: Props = $props();
async function render(value: string): Promise<void> {
const current = value;
rendering = true;
try {
const next = await markdownToHtml(current);
if (text === current) {
html = next;
}
} finally {
if (text === current) {
rendering = false;
}
}
}
const options = {
breaks: true,
gfm: true,
} satisfies NonNullable<SvelteMarkdownProps["options"]>;
$effect(() => {
void render(text);
});
const renderers = {
code: ShikiCode,
html: buildUnsupportedHTML(),
link: MarkdownLink,
} satisfies Partial<Renderers>;
</script>
<div class={`rich-markdown ${className}`} class:is-rendering={rendering}>
{#if html}
{@html html}
{:else}
<p>{text}</p>
{/if}
<div class={`rich-markdown ${className}`}>
<SvelteMarkdown source={text} {streamId} {options} {renderers} streaming />
</div>
<style>
@@ -90,7 +104,8 @@
color: var(--tui-cyan);
}
:global(.rich-markdown .shiki) {
:global(.rich-markdown .shiki),
:global(.rich-markdown .shiki-fallback) {
border: 1px solid var(--line);
border-radius: 0.65rem;
overflow: auto;
@@ -1,7 +1,14 @@
<script lang="ts">
import type { InFlightCompaction } from "$lib/generated/protocol.ts";
import type {
InFlightCompaction,
WorkerStateSnapshot,
} from "$lib/generated/protocol.ts";
import Spinner from "./Spinner.svelte";
import { formatRunElapsed, formatRunTokens } from "./run-status";
import {
formatRunElapsed,
formatRunTokens,
resolveCompactionStatusPresentation,
} from "./run-status";
type Props = {
startedAtMs: number | null;
@@ -9,12 +16,24 @@
uploadTokens: number;
outputTokens: number;
compaction?: InFlightCompaction | null;
workerState?: WorkerStateSnapshot | null;
};
let { startedAtMs, requests, uploadTokens, outputTokens, compaction = null }: Props =
$props();
let {
startedAtMs,
requests,
uploadTokens,
outputTokens,
compaction = null,
workerState = null,
}: Props = $props();
let nowMs = $state(Date.now());
const clockStartedAtMs = $derived(compaction?.started_at_ms ?? startedAtMs);
const compactionStatus = $derived(
resolveCompactionStatusPresentation(compaction, workerState),
);
const clockStartedAtMs = $derived(
compactionStatus?.progress.started_at_ms ?? startedAtMs,
);
$effect(() => {
clockStartedAtMs;
@@ -33,8 +52,8 @@
<div class="worker-run-status" role="status" aria-live="off">
<Spinner />
{#if compaction}
<span>Compacting · {compaction.phase}</span>
{#if compactionStatus}
<span>{compactionStatus.label}</span>
<span aria-hidden="true">|</span>
{/if}
<span>{elapsed}</span>
@@ -1,36 +0,0 @@
import { markdownToHtml } from "./markdown.ts";
declare const Deno: {
test(name: string, fn: () => void | Promise<void>): void;
};
function assert(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}
Deno.test("markdownToHtml escapes raw html and renders basic markdown", async () => {
const html = await markdownToHtml(
"# Title\n\nhello **world** `<x>`\n\n<script>bad()</script>",
);
assert(html.includes("<h1>Title</h1>"), "heading should render");
assert(html.includes("<strong>world</strong>"), "strong should render");
assert(
html.includes("&lt;script&gt;bad()&lt;/script&gt;"),
"raw html should be escaped",
);
assert(
!html.includes("<script>bad()</script>"),
"raw html must not pass through",
);
});
Deno.test("markdownToHtml renders fenced code through shiki", async () => {
const html = await markdownToHtml("```ts\nconst answer: number = 42;\n```");
assert(
html.includes("shiki"),
"highlighted code should include shiki markup",
);
assert(html.includes("answer"), "code content should be present");
});
@@ -1,225 +0,0 @@
import { codeToHtml } from "shiki";
const SHIKI_THEME = "kanagawa-wave";
export async function markdownToHtml(markdown: string): Promise<string> {
if (!markdown.trim()) {
return "";
}
const blocks = splitFencedCode(markdown);
const html: string[] = [];
let textBuffer: string[] = [];
async function flushText(): Promise<void> {
if (textBuffer.length === 0) {
return;
}
html.push(renderMarkdownText(textBuffer.join("\n")));
textBuffer = [];
}
for (const block of blocks) {
if (block.kind === "text") {
textBuffer.push(block.text);
continue;
}
await flushText();
html.push(await highlightCode(block.code, block.lang));
}
await flushText();
return html.join("\n");
}
type MarkdownBlock =
| { kind: "text"; text: string }
| { kind: "code"; lang: string; code: string };
function splitFencedCode(markdown: string): MarkdownBlock[] {
const lines = markdown.split(/\r?\n/);
const blocks: MarkdownBlock[] = [];
let text: string[] = [];
let code: string[] | null = null;
let lang = "text";
for (const line of lines) {
const fence = line.match(/^```\s*([^`]*)\s*$/);
if (fence) {
if (code) {
blocks.push({ kind: "text", text: text.join("\n") });
text = [];
blocks.push({ kind: "code", lang, code: code.join("\n") });
code = null;
lang = "text";
} else {
if (text.length > 0) {
blocks.push({ kind: "text", text: text.join("\n") });
text = [];
}
lang = normalizeLanguage(fence[1]);
code = [];
}
continue;
}
if (code) {
code.push(line);
} else {
text.push(line);
}
}
if (code) {
text.push("```" + (lang === "text" ? "" : lang));
text.push(...code);
}
if (text.length > 0) {
blocks.push({ kind: "text", text: text.join("\n") });
}
return blocks.filter((block) =>
block.kind === "code" || block.text.trim().length > 0
);
}
function renderMarkdownText(markdown: string): string {
const lines = markdown.split(/\r?\n/);
const html: string[] = [];
let paragraph: string[] = [];
let list: string[] = [];
let blockquote: string[] = [];
function flushParagraph(): void {
if (paragraph.length === 0) {
return;
}
html.push(`<p>${paragraph.map(renderInline).join("<br>")}</p>`);
paragraph = [];
}
function flushList(): void {
if (list.length === 0) {
return;
}
html.push(
`<ul>${
list.map((item) => `<li>${renderInline(item)}</li>`).join("")
}</ul>`,
);
list = [];
}
function flushBlockquote(): void {
if (blockquote.length === 0) {
return;
}
html.push(
`<blockquote>${blockquote.map(renderInline).join("<br>")}</blockquote>`,
);
blockquote = [];
}
function flushAll(): void {
flushParagraph();
flushList();
flushBlockquote();
}
for (const rawLine of lines) {
const line = rawLine.trimEnd();
if (!line.trim()) {
flushAll();
continue;
}
const heading = line.match(/^(#{1,4})\s+(.+)$/);
if (heading) {
flushAll();
const level = heading[1].length;
html.push(`<h${level}>${renderInline(heading[2])}</h${level}>`);
continue;
}
const listItem = line.match(/^\s*[-*]\s+(.+)$/);
if (listItem) {
flushParagraph();
flushBlockquote();
list.push(listItem[1]);
continue;
}
const quote = line.match(/^>\s?(.*)$/);
if (quote) {
flushParagraph();
flushList();
blockquote.push(quote[1]);
continue;
}
flushList();
flushBlockquote();
paragraph.push(line);
}
flushAll();
return html.join("\n");
}
function renderInline(value: string): string {
let escaped = escapeHtml(value);
escaped = escaped.replace(/`([^`]+)`/g, "<code>$1</code>");
escaped = escaped.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
escaped = escaped.replace(/\b(https?:\/\/[^\s<]+[^\s<.,;:)])/g, (url) => {
const safe = escapeAttribute(url);
return `<a href="${safe}" rel="noreferrer" target="_blank">${url}</a>`;
});
return escaped;
}
async function highlightCode(code: string, lang: string): Promise<string> {
try {
return await codeToHtml(code, {
lang: lang || "text",
theme: SHIKI_THEME,
});
} catch {
return `<pre class="shiki fallback"><code>${escapeHtml(code)}</code></pre>`;
}
}
function normalizeLanguage(value: string | undefined): string {
const raw = value?.trim().split(/\s+/, 1)[0] ?? "";
if (!raw) {
return "text";
}
switch (raw) {
case "sh":
case "shell":
case "zsh":
return "bash";
case "ts":
return "typescript";
case "js":
return "javascript";
default:
return raw;
}
}
function escapeHtml(value: string): string {
return value.replace(/[&<>"]/g, (char) => {
switch (char) {
case "&":
return "&amp;";
case "<":
return "&lt;";
case ">":
return "&gt;";
case '"':
return "&quot;";
default:
return char;
}
});
}
function escapeAttribute(value: string): string {
return escapeHtml(value).replace(/'/g, "&#39;");
}
@@ -5,6 +5,8 @@ import {
formatRunElapsed,
formatRunElapsedCompact,
formatRunTokens,
resolveCompactionStatusPresentation,
visibleCompactionProgress,
} from "./run-status.ts";
function assertEquals(actual: unknown, expected: unknown): void {
@@ -98,3 +100,57 @@ Deno.test("run status formatting matches the compact TUI shape", () => {
assertEquals(formatRunTokens(3_000), "3.0k");
assertEquals(formatRunTokens(999), "999");
});
Deno.test("WorkerRunStatus presentation follows compaction and busy state", () => {
const manual = {
phase: "preparing",
started_at_ms: 1_700_000_000_000,
trigger: "manual",
} as const;
const automatic = {
phase: "summarizing",
started_at_ms: 1_700_000_000_000,
trigger: "request_threshold",
} as const;
const maintenance = {
last_command_id: 7,
state: {
kind: "busy",
state: { kind: "maintenance", state: "compacting" },
},
} as const;
const run = {
last_command_id: 7,
state: {
kind: "busy",
state: { kind: "run", state: "running" },
},
} as const;
const idle = {
last_command_id: 7,
state: { kind: "idle" },
} as const;
assertEquals(visibleCompactionProgress(manual, maintenance), manual);
assertEquals(visibleCompactionProgress(automatic, run), automatic);
assertEquals(visibleCompactionProgress(manual, run), null);
assertEquals(visibleCompactionProgress(automatic, maintenance), null);
assertEquals(visibleCompactionProgress(manual, idle), null);
assertEquals(visibleCompactionProgress(manual, null), null);
assertEquals(visibleCompactionProgress(null, maintenance), null);
assertEquals(resolveCompactionStatusPresentation(manual, maintenance), {
progress: manual,
label: "Compacting · preparing",
});
assertEquals(resolveCompactionStatusPresentation(automatic, run), {
progress: automatic,
label: "Compacting · summarizing",
});
assertEquals(resolveCompactionStatusPresentation(manual, run), null);
assertEquals(
resolveCompactionStatusPresentation(automatic, maintenance),
null,
);
assertEquals(resolveCompactionStatusPresentation(manual, idle), null);
});
@@ -1,4 +1,8 @@
import type { Event as ProtocolEvent } from "$lib/generated/protocol";
import type {
Event as ProtocolEvent,
InFlightCompaction,
WorkerStateSnapshot,
} from "$lib/generated/protocol";
export type RunActivityStats = {
startedAtMs: number | null;
@@ -71,3 +75,35 @@ export function formatRunTokens(tokens: number): string {
if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}k`;
return String(tokens);
}
export type CompactionStatusPresentation = {
progress: InFlightCompaction;
label: string;
};
export function resolveCompactionStatusPresentation(
compaction: InFlightCompaction | null,
workerState: WorkerStateSnapshot | null,
): CompactionStatusPresentation | null {
const progress = visibleCompactionProgress(compaction, workerState);
return progress
? { progress, label: `Compacting · ${progress.phase}` }
: null;
}
export function visibleCompactionProgress(
compaction: InFlightCompaction | null,
workerState: WorkerStateSnapshot | null,
): InFlightCompaction | null {
if (!compaction || workerState?.state.kind !== "busy") {
return null;
}
const busyState = workerState.state.state;
const triggerMatches = compaction.trigger === "manual"
? busyState.kind === "maintenance" &&
busyState.state === "compacting"
: busyState.kind === "run";
return triggerMatches ? compaction : null;
}
@@ -574,7 +574,9 @@ Deno.test("Worker Console renders markdown only for message rows", async () => {
consoleLine.includes("font-size: var(--font-size-compact);") &&
consoleLine.includes("line-height: 1.1;") &&
consoleLine.includes("{:else if shouldRenderMarkdown(item)}") &&
consoleLine.includes("<RichMarkdown text={item.body || '—'} />") &&
consoleLine.includes(
"<RichMarkdown text={item.body || '—'} streamId={item.id} />",
) &&
!consoleLine.includes("{@html"),
"Console should keep markdown rendering to message bodies, safely project Bash ANSI, and render other tool text literally",
);
@@ -1246,3 +1248,29 @@ Deno.test("Web Console uses Notify while running and exposes durable pending con
"ordinary text must remain Submit instead of being implicitly converted to Notify",
);
});
Deno.test("Web Console gates compaction progress before rendering run status", async () => {
const consolePage = await Deno.readTextFile(
new URL(
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
import.meta.url,
),
);
const runStatus = await Deno.readTextFile(
new URL("./WorkerRunStatus.svelte", import.meta.url),
);
assert(
consolePage.includes("compaction={consoleProjection.compaction}") &&
consolePage.includes("workerState={consoleProjection.workerState}"),
"Console page should pass compaction metadata with authoritative worker state to the status component",
);
assert(
runStatus.includes("resolveCompactionStatusPresentation(") &&
runStatus.includes("{compactionStatus.label}") &&
runStatus.includes(
"compactionStatus?.progress.started_at_ms ?? startedAtMs",
),
"run status should render the guarded presentation and derive elapsed time from its authoritative start timestamp",
);
});
@@ -1,4 +1,4 @@
import type { WorkspaceRuntimeResource } from "$lib/generated/workspace-api";
import type { WorkspaceRuntimeResource } from "$lib/generated/legacy-server-api";
export type RepositorySshProbeSelection<T> = Readonly<{
changed: boolean;
@@ -0,0 +1,84 @@
import {
isCurrentWorkerSessionRequest,
workerSessionAction,
type WorkerSessionRequestIdentity,
workerSessionRequestInit,
} from "./session-observation";
import type { SessionSnapshot } from "$lib/generated/protocol";
declare const Deno: {
test(name: string, fn: () => Promise<void> | void): void;
};
function assert(condition: boolean, message: string): void {
if (!condition) throw new Error(message);
}
const snapshot: SessionSnapshot = {
pending_submissions: {
revision: 0,
notification_count: 0,
head_id: null,
submissions: [],
},
entries: [],
};
Deno.test("worker session request includes same-origin credentials", () => {
const controller = new AbortController();
const init = workerSessionRequestInit(controller.signal);
assert(
init.credentials === "same-origin",
"session observation must use the Workspace auth cookie boundary",
);
assert(
init.signal === controller.signal,
"request must retain its abort fence",
);
});
Deno.test("worker session observation selects live, retained, and unavailable behavior", () => {
assert(
workerSessionAction({ availability: "live_protocol" }).kind ===
"subscribe_live",
"live observation must subscribe",
);
const retained = workerSessionAction({
availability: "retained_snapshot",
snapshot,
});
assert(
retained.kind === "apply_retained" && retained.snapshot === snapshot,
"retained observation must apply its snapshot",
);
const unavailable = workerSessionAction({
availability: "unavailable",
message: "retention expired",
});
assert(
unavailable.kind === "show_unavailable" &&
unavailable.message === "retention expired",
"unavailable observation must stay recoverable and visible",
);
});
Deno.test("late worker session response cannot overwrite a newer selection", () => {
const request: WorkerSessionRequestIdentity = {
token: 7,
workspaceId: "workspace-a",
runtimeId: "runtime-a",
workerId: "worker-a",
};
assert(
isCurrentWorkerSessionRequest(request, request),
"matching request identity must remain current",
);
assert(
!isCurrentWorkerSessionRequest(request, {
...request,
token: 8,
workerId: "worker-b",
}),
"late response for an older token/worker must be fenced",
);
});
@@ -0,0 +1,54 @@
import type { SessionSnapshot } from "$lib/generated/protocol";
export type WorkerSessionObservation =
| { availability: "live_protocol" }
| { availability: "retained_snapshot"; snapshot: SessionSnapshot }
| { availability: "unavailable"; message: string };
export type WorkerSessionAction =
| { kind: "subscribe_live" }
| { kind: "apply_retained"; snapshot: SessionSnapshot }
| { kind: "show_unavailable"; message: string };
export interface WorkerSessionRequestIdentity {
token: number;
workspaceId: string;
runtimeId: string;
workerId: string;
}
export function workerSessionRequestInit(signal: AbortSignal): RequestInit {
return {
signal,
credentials: "same-origin",
headers: { accept: "application/json" },
};
}
export function workerSessionAction(
observation: WorkerSessionObservation,
): WorkerSessionAction {
switch (observation.availability) {
case "live_protocol":
return { kind: "subscribe_live" };
case "retained_snapshot":
return { kind: "apply_retained", snapshot: observation.snapshot };
case "unavailable":
return {
kind: "show_unavailable",
message: observation.message.slice(0, 512),
};
}
}
export function isCurrentWorkerSessionRequest(
request: WorkerSessionRequestIdentity,
current: WorkerSessionRequestIdentity,
): boolean {
return (
request.token === current.token &&
request.workspaceId === current.workspaceId &&
request.runtimeId === current.runtimeId &&
request.workerId === current.workerId
);
}
@@ -1,6 +1,6 @@
import type { Diagnostic as WorkspaceApiDiagnostic } from "$lib/generated/workspace-api";
import type { Diagnostic as ServerApiDiagnostic } from "$lib/generated/legacy-server-api";
export type Diagnostic = WorkspaceApiDiagnostic;
export type Diagnostic = ServerApiDiagnostic;
export type SettingsSectionId =
| "runtimes"
@@ -12,7 +12,7 @@ import type {
WorkspaceSigningIdentityPublic,
WorkspaceSigningIdentityResponse,
WorkspaceSigningIdentityState,
} from "$lib/generated/workspace-api";
} from "$lib/generated/legacy-server-api";
export class ProfileApiError extends Error {
constructor(
@@ -2,7 +2,7 @@ import type {
WorkspaceDeletionOperationResponse,
WorkspaceDeletionPreflightResponse,
WorkspaceDeletionRequest,
} from "$lib/generated/workspace-api";
} from "$lib/generated/legacy-server-api";
import { loadJson } from "$lib/workspace/api/http";
import {
parseWorkspaceDeletionOperationResponse,
@@ -5,6 +5,7 @@ import type {
WorkerLaunchOptionsResponse as SharedWorkerLaunchOptionsResponse,
WorkerLaunchProfileCandidate as SharedWorkerLaunchProfileCandidate,
WorkerLaunchRuntimeOption as SharedWorkerLaunchRuntimeOption,
WorkerLaunchWorkerSummary as WorkerSummary,
WorkingDirectoryRepositoryOption as SharedWorkingDirectoryRepositoryOption,
} from "$lib/generated/worker-launch-api";
import type {
@@ -112,6 +113,19 @@ export type Worker = {
export type WorkerOperationState = "accepted" | "unsupported" | "rejected";
/** Typed result of restore across Runtime, Workspace, Web, and TUI clients. */
export type WorkerRestoreState =
| "accepted"
| "rejected"
| "rolled_back"
| "reconciliation_required";
export type WorkerRestoreResult = {
state: WorkerRestoreState;
worker?: WorkerSummary | null;
diagnostics: Diagnostic[];
};
export type WorkerLaunchRuntimeOption = SharedWorkerLaunchRuntimeOption;
export type WorkerLaunchProfileCandidate = SharedWorkerLaunchProfileCandidate;
export type WorkingDirectoryRepositoryOption =
@@ -2,8 +2,10 @@ import type { WorkerStateSnapshot } from "$lib/generated/protocol";
export function liveWorkerState(worker: {
state: string;
availability?: "observed" | "unavailable";
worker_state?: WorkerStateSnapshot | null;
}): string {
if (worker.availability === "unavailable") return "unknown";
const state = worker.worker_state?.state;
if (!state) {
if (worker.state === "missing" || worker.state === "stopped") {
@@ -20,10 +20,12 @@ function worker(
workerId: string,
revision: number,
hasRunningInternalWorkers = false,
availability: 'observed' | 'unavailable' = 'observed',
): SubscriptionWorker {
return {
worker_id: workerId,
runtime_id: runtimeId,
availability,
subject_revision: revision,
state: 'idle',
has_running_internal_workers: hasRunningInternalWorkers,
@@ -42,12 +44,23 @@ Deno.test('Worker list state uses the authoritative live snapshot separately fro
};
assertEquals(liveWorkerState(active), 'paused');
const unavailable = worker('runtime-a', 'worker-2', 1);
const unavailable = worker('runtime-a', 'worker-2', 1, false, 'unavailable');
unavailable.worker_state = {
last_command_id: 2,
state: { kind: 'busy', state: { kind: 'run', state: 'running' } },
};
assertEquals(liveWorkerState(unavailable), 'unknown');
assertEquals(
liveWorkerState({ ...unavailable, state: 'missing' }),
liveWorkerState({
...unavailable,
availability: 'observed',
worker_state: null,
state: 'missing',
}),
'missing',
);
unavailable.availability = 'observed';
unavailable.worker_state = null;
unavailable.state = 'stopped';
assertEquals(liveWorkerState(unavailable), 'stopped');
});
@@ -100,7 +100,7 @@ function projectWorker(worker: SubscriptionWorker): SidebarWorker {
display_hint: 'Workspace-authorized Runtime Worker',
},
capabilities: {
can_stop: worker.state !== 'stopped',
can_stop: worker.availability !== 'unavailable' && worker.state !== 'stopped',
can_spawn_followup: false,
},
repository_key: worker.repository_key ?? null,
@@ -140,8 +140,7 @@
gap: var(--space-2);
min-width: 0;
}
.ticket-title,
.objective-title {
.ticket-title {
min-width: 0;
overflow: hidden;
color: var(--text-strong);
@@ -149,6 +148,14 @@
text-overflow: ellipsis;
white-space: nowrap;
}
.objective-title {
min-width: 0;
flex: 1 1 auto;
color: var(--text-strong);
font-weight: 700;
overflow-wrap: anywhere;
white-space: normal;
}
.state-pill {
flex: 0 0 auto;
color: var(--success);
@@ -157,8 +164,7 @@
letter-spacing: 0.05em;
text-transform: uppercase;
}
.ticket-summary,
.objective-summary {
.ticket-summary {
margin: 0;
color: var(--text-muted);
font-size: var(--font-size-compact);
@@ -189,6 +195,51 @@
white-space: normal;
}
}
.objective-title-row {
align-items: flex-start;
}
.objective-title-row.detail h3 {
min-width: 0;
margin: 0;
overflow-wrap: anywhere;
}
.objective-detail-card {
display: grid;
gap: var(--space-4);
}
.objective-detail-card > h2 {
margin: 0;
}
.objective-back-link {
width: fit-content;
color: var(--text-muted);
font-size: var(--font-size-compact);
}
.objective-detail-metadata {
padding-bottom: var(--space-4);
border-bottom: 1px solid var(--line);
}
.objective-detail-metadata dd {
overflow-wrap: anywhere;
}
.objective-body {
min-width: 0;
}
.objective-body > :first-child {
margin-top: 0;
}
.objective-body > :last-child {
margin-bottom: 0;
}
@media (max-width: 600px) {
.objective-title-row.detail {
flex-wrap: wrap;
}
.objective-detail-metadata > div {
grid-template-columns: minmax(0, 1fr);
gap: var(--space-1);
}
}
.runtime-heading,
.detail-heading {
display: flex;
@@ -21,7 +21,7 @@
let open = $state(false);
let pointerInside = false;
let focusInside = false;
let hoverTimer: number | undefined;
let hoverTimer: ReturnType<typeof globalThis.setTimeout> | undefined;
let rootElement: HTMLSpanElement | undefined;
let tooltipElement: HTMLSpanElement | undefined;
let tooltipLeft = $state(0);
@@ -1,16 +1,14 @@
import { error } from "@sveltejs/kit";
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
import {
parseRepositoryListResponse,
parseWorkspaceResponse,
} from "$lib/workspace/api/workspace-model";
import { loadWorkspaceRepositoryList } from "$lib/workspace/api/repositories";
import { parseWorkspaceResponse } from "$lib/workspace/api/workspace-model";
import type { LayoutLoad } from "./$types";
export const load: LayoutLoad = async ({ fetch, params }) => {
const workspaceId = params.workspaceId;
const [workspaceResult, repositoryResult] = await Promise.all([
loadJson<unknown>(fetch, workspaceApiPath(workspaceId, "/workspace")),
loadJson<unknown>(fetch, workspaceApiPath(workspaceId, "/repositories")),
loadWorkspaceRepositoryList(fetch, workspaceId),
]);
let workspace = null;
@@ -30,17 +28,8 @@ export const load: LayoutLoad = async ({ fetch, params }) => {
});
}
let repositories = null;
let repositoriesError = repositoryResult.error;
if (repositoryResult.data !== null) {
try {
repositories = parseRepositoryListResponse(repositoryResult.data);
} catch (cause) {
repositoriesError = cause instanceof Error
? cause.message
: "invalid repository list response";
}
}
const repositories = repositoryResult.data;
const repositoriesError = repositoryResult.error;
return {
workspace,
@@ -25,7 +25,6 @@
<strong class="objective-title">{objective.title}</strong>
<span class="state-pill">{objective.state}</span>
</div>
<p class="objective-summary">{objective.summary || 'No summary text is available.'}</p>
</div>
<div class="objective-meta" aria-label="Objective metadata">
<span>Updated {objective.updated_at ? formatDate(objective.updated_at) : 'unknown'}</span>
@@ -1,52 +1,28 @@
<script lang="ts">
import RichMarkdown from '$lib/workspace/console/RichMarkdown.svelte';
import { formatDate } from '$lib/workspace/api/http';
import { objectiveHref, ticketHref } from '$lib/workspace/resource-links';
import { ticketHref } from '$lib/workspace/resource-links';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
const objectivesHref = (workspaceId: string) =>
`/w/${encodeURIComponent(workspaceId)}/objectives`;
</script>
<svelte:head>
<title>{data.objective?.title ?? data.objectiveId} · Objective</title>
</svelte:head>
<section class="card">
<h2>Objectives</h2>
{#if data.objectives}
<div class="objective-list compact">
{#each data.objectives.items as objective (objective.id)}
<a class="objective-row" class:active={objective.id === data.objectiveId} href={objectiveHref(data.workspaceId, objective)}>
<div class="objective-main">
<div class="objective-title-row">
<strong class="objective-title">{objective.title}</strong>
<span class="state-pill">{objective.state}</span>
</div>
<p class="objective-summary">{objective.summary || 'No summary text is available.'}</p>
</div>
<div class="objective-meta" aria-label="Objective metadata">
<span>Updated {objective.updated_at ? formatDate(objective.updated_at) : 'unknown'}</span>
<code>{objective.resource_key}</code>
</div>
</a>
{/each}
</div>
{:else if data.objectivesError}
<p class="error">{data.objectivesError}</p>
{:else}
<p>Loading objectives…</p>
{/if}
</section>
<section class="card objective-detail-card">
<a class="objective-back-link" href={objectivesHref(data.workspaceId)}>← All Objectives</a>
<h2>Objective detail</h2>
{#if data.objective}
<div class="objective-title-row detail">
<div>
<h3>{data.objective.title}</h3>
</div>
<h3>{data.objective.title}</h3>
<span class="state-pill">{data.objective.state}</span>
</div>
<dl>
<dl class="objective-detail-metadata">
<div>
<dt>Created</dt>
<dd>{data.objective.created_at ? formatDate(data.objective.created_at) : 'unknown'}</dd>
@@ -72,7 +48,13 @@
</dd>
</div>
</dl>
<pre class="objective-body">{data.objective.body}</pre>
<div class="objective-body" aria-label="Objective body">
{#if data.objective.body}
<RichMarkdown text={data.objective.body} />
{:else}
<p class="section-note">No Objective body is available.</p>
{/if}
</div>
{#if data.objective.body_truncated}
<p class="error">Objective body was truncated by the Backend response limit.</p>
{/if}
@@ -4,22 +4,16 @@ import {
canonicalResourceReference,
resourceKey,
} from "$lib/workspace/resource-links";
import type {
ObjectiveDetail,
ObjectiveListResponse,
} from "$lib/workspace/sidebar/types";
import type { ObjectiveDetail } from "$lib/workspace/sidebar/types";
import type { PageLoad } from "./$types";
export const load: PageLoad = async ({ fetch, params }) => {
const apiPath = (path: string) => workspaceApiPath(params.workspaceId, path);
const objectiveId = resourceKey(params.objectiveId);
const [objectives, objective] = await Promise.all([
loadJson<ObjectiveListResponse>(fetch, apiPath("/objectives")),
loadJson<ObjectiveDetail>(
fetch,
apiPath(`/objectives/${encodeURIComponent(objectiveId)}`),
),
]);
const objective = await loadJson<ObjectiveDetail>(
fetch,
apiPath(`/objectives/${encodeURIComponent(objectiveId)}`),
);
if (objective.data) {
const canonical = canonicalResourceReference(
@@ -29,7 +23,9 @@ export const load: PageLoad = async ({ fetch, params }) => {
if (params.objectiveId !== canonical) {
redirect(
308,
`/w/${encodeURIComponent(params.workspaceId)}/objectives/${encodeURIComponent(canonical)}`,
`/w/${encodeURIComponent(params.workspaceId)}/objectives/${
encodeURIComponent(canonical)
}`,
);
}
}
@@ -37,8 +33,6 @@ export const load: PageLoad = async ({ fetch, params }) => {
return {
workspaceId: params.workspaceId,
objectiveId,
objectives: objectives.data,
objectivesError: objectives.error,
objective: objective.data,
objectiveError: objective.error,
};
@@ -3,7 +3,7 @@
ConfirmRepositorySshHostTrustRequest,
RepositorySshConnectionProbeRequest,
RepositorySshConnectionProbeResponse
} from '$lib/generated/workspace-api';
} from '$lib/generated/legacy-server-api';
import { parseRepositorySshHostTrust } from '$lib/workspace/api/repository-access';
import { formatDate, workspaceApiPath } from '$lib/workspace/api/http';
import { parseRepositorySshConnectionProbeResponse } from '$lib/workspace/api/workspace-model';
@@ -1,22 +1,14 @@
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
import { loadWorkspaceRepositoryDetail } from "$lib/workspace/api/repositories";
import { parseWorkspaceRuntimeList } from "$lib/workspace/api/runtime-management";
import {
parseRepositoryDetailResponse,
parseRepositoryLogResponse,
} from "$lib/workspace/api/workspace-model";
import { parseRepositoryLogResponse } from "$lib/workspace/api/workspace-model";
import type { PageLoad } from "./$types";
export const load: PageLoad = async ({ fetch, params }) => {
const workspaceId = params.workspaceId;
const repositoryKey = params.repositoryKey;
const [repositoryResult, logResult, runtimesResult] = await Promise.all([
loadJson<unknown>(
fetch,
workspaceApiPath(
workspaceId,
`/repositories/${encodeURIComponent(repositoryKey)}`,
),
),
loadWorkspaceRepositoryDetail(fetch, workspaceId, repositoryKey),
loadJson<unknown>(
fetch,
workspaceApiPath(
@@ -30,17 +22,8 @@ export const load: PageLoad = async ({ fetch, params }) => {
),
]);
let repository = null;
let repositoryError = repositoryResult.error;
if (repositoryResult.data !== null) {
try {
repository = parseRepositoryDetailResponse(repositoryResult.data);
} catch (cause) {
repositoryError = cause instanceof Error
? cause.message
: "invalid repository detail response";
}
}
const repository = repositoryResult.data;
const repositoryError = repositoryResult.error;
let log = null;
let logError = logResult.error;
@@ -6,7 +6,7 @@
WorkspaceDeletionRequest,
WorkspaceMetadataSettingsResponse,
WorkspaceSigningIdentityResponse,
} from '$lib/generated/workspace-api';
} from '$lib/generated/legacy-server-api';
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
import { disposeWorkspaceMultiplexer } from '$lib/workspace/multiplexer';
@@ -1,7 +1,7 @@
<script lang="ts">
import DiagnosticsList from "$lib/workspace/settings/DiagnosticsList.svelte";
import { settingsSectionHref } from "$lib/workspace/settings/model";
import type { ProfileSettingsResponse } from "$lib/generated/workspace-api";
import type { ProfileSettingsResponse } from "$lib/generated/legacy-server-api";
import { fetchProfileSettings } from "$lib/workspace/settings/profile-api";
import type { PageProps } from "./$types";
@@ -1,7 +1,11 @@
<script lang="ts">
import { invalidateAll } from '$app/navigation';
import { workspaceApiPath, workspaceRoute } from '$lib/workspace/api/http';
import type { RepositorySourceKind } from '$lib/generated/workspace-api';
import { workspaceRoute } from '$lib/workspace/api/http';
import { createWorkspaceRepository } from '$lib/workspace/api/repositories';
import type {
CreateWorkspaceRepositoryRequest,
RepositorySourceKind,
} from '$lib/generated/repository-api';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
@@ -22,38 +26,27 @@
let pending = $state(false);
let requestError = $state<string | null>(null);
async function responseError(response: Response): Promise<string> {
const payload = await response.json().catch(() => null) as
| { message?: string; error?: string }
| null;
return payload?.message ?? payload?.error ?? `Request failed (${response.status})`;
}
async function addRepository(event: SubmitEvent): Promise<void> {
event.preventDefault();
pending = true;
requestError = null;
try {
const response = await fetch(workspaceApiPath(data.workspaceId, '/repositories'), {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
repository_key: repositoryKey,
source,
default_ref: defaultRef || null,
}),
});
if (!response.ok) throw new Error(await responseError(response));
repositoryKey = '';
source = '';
defaultRef = '';
showAddRepository = false;
await invalidateAll();
} catch (error) {
requestError = error instanceof Error ? error.message : String(error);
} finally {
const request: CreateWorkspaceRepositoryRequest = {
repository_key: repositoryKey,
source,
default_ref: defaultRef || null,
};
const result = await createWorkspaceRepository(fetch, data.workspaceId, request);
if (result.data === null) {
requestError = result.error ?? 'Repository creation failed';
pending = false;
return;
}
repositoryKey = '';
source = '';
defaultRef = '';
showAddRepository = false;
await invalidateAll();
pending = false;
}
</script>
@@ -1,13 +1,10 @@
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
import { parseRepositoryListResponse } from "$lib/workspace/api/workspace-model";
import { loadWorkspaceRepositoryList } from "$lib/workspace/api/repositories";
import type { PageLoad } from "./$types";
export const load: PageLoad = async ({ fetch, params }) => {
const repositories = await loadJson(
const repositories = await loadWorkspaceRepositoryList(
fetch,
workspaceApiPath(params.workspaceId, "/repositories"),
undefined,
parseRepositoryListResponse,
params.workspaceId,
);
return {
workspaceId: params.workspaceId,
@@ -4,7 +4,7 @@
RuntimeConnectionTestResponse,
RuntimePublicIdentityBundle,
WorkspaceRuntimeResource,
} from '$lib/generated/workspace-api';
} from '$lib/generated/legacy-server-api';
import {
createRemoteRuntime,
previewRuntimePublicKeyFingerprint,
@@ -3,7 +3,7 @@
import type {
RevokeRuntimeTrustKeyRequest,
RuntimeTrustKeyStatus,
} from '$lib/generated/workspace-api';
} from '$lib/generated/legacy-server-api';
import {
createRemoteRuntime,
removeRemoteRuntime,
@@ -1,6 +1,6 @@
import { redirect } from "@sveltejs/kit";
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
import { parseRepositoryListApiResult } from "$lib/workspace/api/workspace-model";
import { loadWorkspaceRepositoryList } from "$lib/workspace/api/repositories";
import {
canonicalResourceReference,
resourceKey,
@@ -17,10 +17,7 @@ export const load = (async ({ fetch, params }) => {
);
const [ticket, repositoriesRaw, orchestrator] = await Promise.all([
loadJson<TicketDetail>(fetch, ticketPath),
loadJson<unknown>(
fetch,
workspaceApiPath(params.workspaceId, "/repositories"),
),
loadWorkspaceRepositoryList(fetch, params.workspaceId),
loadJson<WorkspaceOrchestratorStatus>(
fetch,
workspaceApiPath(params.workspaceId, "/orchestrator"),
@@ -40,7 +37,7 @@ export const load = (async ({ fetch, params }) => {
);
}
}
const repositories = parseRepositoryListApiResult(repositoriesRaw);
const repositories = repositoriesRaw;
return {
workspaceId: params.workspaceId,
@@ -52,6 +52,13 @@
import { pushWorkspaceAlert } from "$lib/workspace/alerts/store";
import { workspaceApiPath } from "$lib/workspace/api/http";
import { workspaceMultiplexer, type WorkspaceMultiplexerSubscription } from "$lib/workspace/multiplexer";
import {
isCurrentWorkerSessionRequest,
workerSessionAction,
workerSessionRequestInit,
type WorkerSessionObservation,
type WorkerSessionRequestIdentity,
} from "$lib/workspace/session-observation";
import type { Diagnostic, Worker } from "$lib/workspace/sidebar/types";
type Props = {
@@ -248,18 +255,6 @@
liveWorkerState ?? (worker?.state === "stopped" ? "stopped" : "loading"),
);
const workerRunning = $derived(workerState === "running");
const compactionProgress = $derived.by(() => {
const state = consoleProjection.workerState?.state;
if (!state || typeof state !== "object" || !("busy" in state)) return null;
const progress = consoleProjection.compaction;
if (!progress) return null;
const busy = state.busy;
if (!busy || typeof busy !== "object") return null;
const valid = progress.trigger === "manual"
? "maintenance" in busy && busy.maintenance === "compacting"
: "run" in busy;
return valid ? progress : null;
});
const workerPaused = $derived(workerState === "paused");
const composerEditable = $derived(protocolState === "open" && !sending);
const draftHasText = $derived(draft.content.trim().length > 0);
@@ -659,7 +654,6 @@
params: {
notification_request_id: crypto.randomUUID(),
message: request.content,
auto_run: true,
},
};
case "compact": {
@@ -944,10 +938,121 @@
token: number,
target: ConsoleTarget,
) {
if (!targetWorker || targetWorker.state === "stopped") {
protocolState = "closed";
return;
}
protocolState = "connecting";
const controller = new AbortController();
let closeLiveSubscription: (() => void) | undefined;
const requestIdentity: WorkerSessionRequestIdentity = {
token,
workspaceId: target.workspaceId,
runtimeId: target.runtimeId,
workerId: target.workerId,
};
void fetch(
workerApiPath(
`/runtimes/${encodeURIComponent(target.runtimeId)}/workers/${encodeURIComponent(target.workerId)}/session`,
),
workerSessionRequestInit(controller.signal),
)
.then(async (response) => {
if (!response.ok) {
throw new Error(`failed to observe Worker Session (${response.status})`);
}
return (await response.json()) as WorkerSessionObservation;
})
.then((observation) => {
const currentIdentity: WorkerSessionRequestIdentity = {
token: reloadToken,
workspaceId,
runtimeId,
workerId,
};
if (
!isCurrentWorkerSessionRequest(requestIdentity, currentIdentity) ||
controller.signal.aborted
)
return;
const action = workerSessionAction(observation);
if (action.kind === "show_unavailable") {
protocolState = "closed";
streamDiagnostics = [
...streamDiagnostics,
{
code: "retained_session_unavailable",
severity: "warning",
message: action.message,
},
];
return;
}
if (
action.kind === "subscribe_live" &&
targetWorker &&
targetWorker.state === "stopped"
) {
protocolState = "closed";
streamDiagnostics = [
...streamDiagnostics,
{
code: "worker_session_state_changed",
severity: "warning",
message:
"The Session is live but the Worker is stopped. Reload to refresh its state.",
},
];
return;
}
if (action.kind === "subscribe_live") {
closeLiveSubscription = connectLiveProtocolTransport(token, target);
return;
}
handleIncomingProtocolEvent({
event: "snapshot",
data: {
session: action.snapshot,
greeting: {
worker_name: "retained worker",
cwd: "",
provider: "retained session",
model: "",
scope_summary: "read-only retained session",
tools: [],
context_window: 0,
context_tokens: 0,
},
state: { last_command_id: 0, state: { kind: "idle" } },
in_flight: { responses: [], commands: [] },
internal_workers: [],
},
} as ProtocolEvent);
protocolState = "closed";
streamDiagnostics = [
...streamDiagnostics,
{
code: "retained_session_read_only",
severity: "info",
message: "Showing a read-only retained Session snapshot.",
},
];
})
.catch((error) => {
if (token !== reloadToken || controller.signal.aborted) return;
protocolState = "error";
streamDiagnostics = [
...streamDiagnostics,
{
code: "worker_session_observation_failed",
severity: "warning",
message: error instanceof Error ? error.message : String(error),
},
];
});
return () => {
controller.abort();
closeLiveSubscription?.();
};
}
function connectLiveProtocolTransport(token: number, target: ConsoleTarget) {
protocolState = "connecting";
const subscription = workspaceMultiplexer(target.workspaceId).subscribe(
{
@@ -1917,7 +2022,8 @@
requests={consoleProjection.runActivity.requests}
uploadTokens={consoleProjection.runActivity.uploadTokens}
outputTokens={consoleProjection.runActivity.outputTokens}
compaction={compactionProgress}
compaction={consoleProjection.compaction}
workerState={consoleProjection.workerState}
/>
{/if}
@@ -0,0 +1,65 @@
import {
assert,
assertFalse,
assertMatch,
assertStringIncludes,
} from "jsr:@std/assert";
const objectivesListSource = await Deno.readTextFile(
new URL(
"../src/routes/w/[workspaceId]/objectives/+page.svelte",
import.meta.url,
),
);
const objectiveDetailSource = await Deno.readTextFile(
new URL(
"../src/routes/w/[workspaceId]/objectives/[objectiveId]/+page.svelte",
import.meta.url,
),
);
const objectiveDetailLoaderSource = await Deno.readTextFile(
new URL(
"../src/routes/w/[workspaceId]/objectives/[objectiveId]/+page.ts",
import.meta.url,
),
);
const workspaceStyles = await Deno.readTextFile(
new URL("../src/lib/workspace/styles/workspace-pages.css", import.meta.url),
);
Deno.test("Objective list shows complete titles without body summaries", () => {
assertStringIncludes(objectivesListSource, "{objective.title}");
assertFalse(objectivesListSource.includes("objective.summary"));
assertMatch(
workspaceStyles,
/\.objective-title\s*\{[^}]*overflow-wrap:\s*anywhere;[^}]*white-space:\s*normal;/s,
);
});
Deno.test("Objective detail focuses on one Markdown-rendered Objective", () => {
assertStringIncludes(
objectiveDetailSource,
"<RichMarkdown text={data.objective.body} />",
);
assertFalse(objectiveDetailSource.includes('class="objective-list'));
assertFalse(objectiveDetailSource.includes("data.objectives"));
assertStringIncludes(objectiveDetailSource, "linked_ticket_summaries");
assertStringIncludes(objectiveDetailSource, "body_truncated");
});
Deno.test("Objective detail loader fetches only the selected Objective", () => {
assertStringIncludes(
objectiveDetailLoaderSource,
"`/objectives/${encodeURIComponent(objectiveId)}`",
);
assertFalse(
objectiveDetailLoaderSource.includes('apiPath("/objectives")'),
);
assertFalse(
objectiveDetailLoaderSource.includes("ObjectiveListResponse"),
);
assert(
objectiveDetailLoaderSource.includes("canonicalResourceReference"),
"canonical Objective redirects must remain intact",
);
});
+25 -15
View File
@@ -20,29 +20,39 @@ const loadSource = await Deno.readTextFile(
),
);
const apiSource = await Deno.readTextFile(
new URL("../../src/lib/workspace/api/repositories.ts", import.meta.url),
);
test("Repository settings use the scoped list and typed create collection", () => {
for (const token of [
"workspaceApiPath(params.workspaceId, \"/repositories\")",
"workspaceApiPath(data.workspaceId, '/repositories')",
"method: 'POST'",
"repository_key: repositoryKey",
"default_ref: defaultRef || null",
"await invalidateAll()",
]) {
const source = `${pageSource}\n${loadSource}\n${apiSource}`;
for (
const token of [
"loadWorkspaceRepositoryList",
"createWorkspaceRepository",
"workspaceRepositoryListPath(workspaceId)",
'method: "POST"',
"repository_key: repositoryKey",
"default_ref: defaultRef || null",
"await invalidateAll()",
]
) {
assert(
pageSource.includes(token) || loadSource.includes(token),
source.includes(token),
`Repository settings should include ${token}`,
);
}
});
test("Repository Add form keeps access secrets outside registration input", () => {
for (const forbidden of [
"private_key",
"passphrase",
"credential_id",
"host_trust_id",
]) {
for (
const forbidden of [
"private_key",
"passphrase",
"credential_id",
"host_trust_id",
]
) {
assert(
!pageSource.includes(forbidden),
`Repository registration must not accept ${forbidden}`,
@@ -20,7 +20,7 @@ const loaderSource = await Deno.readTextFile(
),
);
test("Repository Access Web code consumes workspace-api generated DTOs", () => {
test("Repository Access Web code consumes server-api legacy generated DTOs", () => {
assert(
source.includes("$lib/generated/repository-access-api"),
"mutation code should import generated request and response contracts",
@@ -1,5 +1,5 @@
import { assert, assertEquals } from "jsr:@std/assert";
import type { WorkspaceRuntimeResource } from "../src/lib/generated/workspace-api.ts";
import type { WorkspaceRuntimeResource } from "../src/lib/generated/legacy-server-api.ts";
import { parseRepositorySshConnectionProbeResponse } from "../src/lib/workspace/api/workspace-model.ts";
import {
changeRepositorySshProbeRuntime,
+139
View File
@@ -0,0 +1,139 @@
declare const Deno: {
test(name: string, fn: () => void | Promise<void>): void;
};
import {
createWorkspaceRepository,
loadWorkspaceRepositoryList,
REPOSITORY_API_MAX_RESPONSE_BYTES,
} from "../src/lib/workspace/api/repositories.ts";
const repository = {
repository_key: "main",
kind: "git",
provider: "git",
source: { kind: "local_path", uri: "/srv/main" },
source_revision: 1,
source_fingerprint: "sha256:main",
observed_status: "ready",
record_authority: "workspace-control-plane",
};
Deno.test("Repository API loaders parse unknown JSON through the strict generated contract", async () => {
const valid = await loadWorkspaceRepositoryList(
() =>
Promise.resolve(
Response.json({
workspace_id: "workspace-a",
items: [repository],
source: "workspace-control-plane",
diagnostics: [],
}),
),
"workspace-a",
);
if (valid.data?.items[0]?.repository_key !== "main") {
throw new Error(`unexpected load result: ${valid.error}`);
}
const invalid = await loadWorkspaceRepositoryList(
() =>
Promise.resolve(
Response.json({
workspace_id: "workspace-a",
items: [{ ...repository, observed_status: "future_status" }],
source: "workspace-control-plane",
diagnostics: [],
}),
),
"workspace-a",
);
if (
invalid.data !== null ||
!invalid.error?.includes("Repository API response is invalid")
) {
throw new Error("unknown enum values must fail closed in the loader");
}
});
Deno.test("Repository create client validates request, response, and typed API errors", async () => {
const success = await createWorkspaceRepository(
(_input, init) => {
const body = typeof init === "object" && init !== null && "body" in init
? init.body
: undefined;
const request: unknown = JSON.parse(String(body));
if (
typeof request !== "object" || request === null ||
!("repository_key" in request) || request.repository_key !== "main"
) {
throw new Error("typed create request was not serialized");
}
return Promise.resolve(
Response.json({
workspace_id: "workspace-a",
repository_key: "main",
replayed: false,
}),
);
},
"workspace-a",
{ repository_key: "main", source: "/srv/main", default_ref: null },
);
if (success.data?.repository_key !== "main" || success.data.replayed) {
throw new Error(`unexpected create result: ${success.error}`);
}
const rejected = await createWorkspaceRepository(
() =>
Promise.resolve(
Response.json(
{ error: "invalid_source", message: "source is invalid" },
{ status: 400 },
),
),
"workspace-a",
{ repository_key: "main", source: "bad", default_ref: null },
);
if (rejected.data !== null || rejected.error !== "source is invalid") {
throw new Error("typed RepositoryApiError message was not preserved");
}
const invalid = await createWorkspaceRepository(
() =>
Promise.resolve(
Response.json({
workspace_id: "workspace-a",
repository_key: "main",
replayed: null,
}),
),
"workspace-a",
{ repository_key: "main", source: "/srv/main", default_ref: null },
);
if (
invalid.data !== null ||
invalid.error !== "Repository API response is invalid"
) {
throw new Error("invalid create success payload must fail closed");
}
});
Deno.test("Repository API clients bound response bodies before parsing JSON", async () => {
const result = await loadWorkspaceRepositoryList(
() =>
Promise.resolve(
new Response("{}", {
headers: {
"content-length": String(REPOSITORY_API_MAX_RESPONSE_BYTES + 1),
},
}),
),
"workspace-a",
);
if (
result.data !== null || !result.error?.includes("exceeds its byte limit")
) {
throw new Error(`oversized response was not rejected: ${result.error}`);
}
});
+134 -2
View File
@@ -4,11 +4,14 @@ declare const Deno: {
};
import {
parseCreateWorkspaceRepositoryResponse,
parseRepositoryApiError,
parseRepositoryListApiResult,
parseRepositoryListResponse,
parseWorkspaceDeletionOperationResponse,
parseWorkspaceDeletionPreflightResponse,
parseWorkspaceResponse,
REPOSITORY_API_LIMITS,
} from "../src/lib/workspace/api/workspace-model.ts";
function assertThrows(operation: () => unknown, expected: string): void {
@@ -47,6 +50,40 @@ Deno.test("generated repository wrapper validates current Backend JSON", () => {
}
});
Deno.test("repository git optional nullable fields accept omission and null", () => {
const omitted = structuredClone(repositoryList) as Record<string, unknown>;
const omittedItems = omitted.items as Array<Record<string, unknown>>;
omittedItems[0].git = {
status: "available",
dirty: false,
remotes: [],
};
const omittedParsed = parseRepositoryListResponse(omitted);
if (
omittedParsed.items[0]?.git?.head !== undefined ||
omittedParsed.items[0]?.git?.branch !== undefined
) {
throw new Error("omitted git fields were not preserved");
}
const nullable = structuredClone(repositoryList) as Record<string, unknown>;
const nullableItems = nullable.items as Array<Record<string, unknown>>;
nullableItems[0].git = {
status: "available",
head: null,
branch: null,
dirty: false,
remotes: [],
};
const nullableParsed = parseRepositoryListResponse(nullable);
if (
nullableParsed.items[0]?.git?.head !== null ||
nullableParsed.items[0]?.git?.branch !== null
) {
throw new Error("null git fields were not preserved");
}
});
Deno.test("plain HTTP repository source kind fails closed at the JSON boundary", () => {
const stale = structuredClone(repositoryList) as Record<string, unknown>;
const items = stale.items as Array<Record<string, unknown>>;
@@ -71,6 +108,81 @@ Deno.test("stale repository aliases fail closed at the JSON boundary", () => {
);
});
Deno.test("repository source revisions enforce the OpenAPI integer range", () => {
const negative = structuredClone(repositoryList) as Record<string, unknown>;
const negativeItems = negative.items as Array<Record<string, unknown>>;
negativeItems[0].source_revision = -1;
assertThrows(
() => parseRepositoryListResponse(negative),
"must be between 0 and Number.MAX_SAFE_INTEGER",
);
const unsafe = structuredClone(repositoryList) as Record<string, unknown>;
const unsafeItems = unsafe.items as Array<Record<string, unknown>>;
unsafeItems[0].source_revision = Number.MAX_SAFE_INTEGER + 1;
assertThrows(
() => parseRepositoryListResponse(unsafe),
"must be a safe integer",
);
});
Deno.test("repository create/error responses use the generated strict contract", () => {
const created = parseCreateWorkspaceRepositoryResponse({
workspace_id: repositoryList.workspace_id,
repository_key: "main",
replayed: false,
});
if (created.replayed || created.repository_key !== "main") {
throw new Error("create response fields were not preserved");
}
const error = parseRepositoryApiError({
error: "invalid_request",
message: "source is invalid",
});
if (error.error !== "invalid_request") {
throw new Error("error response fields were not preserved");
}
assertThrows(
() =>
parseCreateWorkspaceRepositoryResponse({
workspace_id: repositoryList.workspace_id,
repository_key: "main",
replayed: null,
}),
".replayed must be a boolean",
);
assertThrows(
() =>
parseRepositoryApiError({
error: "invalid_request",
message: "source is invalid",
context: {},
}),
".context is not part",
);
});
Deno.test("repository responses retain bounded strings and collections", () => {
const oversizedString = structuredClone(repositoryList);
oversizedString.items[0].provider = "x".repeat(
REPOSITORY_API_LIMITS.maxStringCodeUnits + 1,
);
assertThrows(
() => parseRepositoryListResponse(oversizedString),
"string limit",
);
const oversizedCollection = structuredClone(repositoryList);
oversizedCollection.items = Array.from(
{ length: REPOSITORY_API_LIMITS.maxCollectionEntries + 1 },
() => repositoryList.items[0],
);
assertThrows(
() => parseRepositoryListResponse(oversizedCollection),
"collection limit",
);
});
Deno.test("repository API result converts stale payloads into bounded page errors", () => {
const result = parseRepositoryListApiResult({
data: {
@@ -227,7 +339,7 @@ Deno.test("Workspace settings exposes owner-gated typed destructive confirmation
});
Deno.test("Repository settings consume the validated shared wire shape", async () => {
const [loadSource, pageSource] = await Promise.all([
const [loadSource, pageSource, apiSource, modelSource] = await Promise.all([
Deno.readTextFile(
new URL(
"../src/routes/w/[workspaceId]/settings/repositories/+page.ts",
@@ -240,21 +352,41 @@ Deno.test("Repository settings consume the validated shared wire shape", async (
import.meta.url,
),
),
Deno.readTextFile(
new URL("../src/lib/workspace/api/repositories.ts", import.meta.url),
),
Deno.readTextFile(
new URL("../src/lib/workspace/api/workspace-model.ts", import.meta.url),
),
]);
const targetSources = `${loadSource}\n${pageSource}\n${apiSource}`;
for (
const token of [
"loadWorkspaceRepositoryList",
"createWorkspaceRepository",
"parseRepositoryListResponse",
"parseCreateWorkspaceRepositoryResponse",
"parseRepositoryApiError",
"repository.repository_key",
"repository.observed_status",
"sourceLabel(repository.source.kind)",
"supportsRepositoryAccess(repository.source.kind)",
]
) {
if (!loadSource.includes(token) && !pageSource.includes(token)) {
if (!targetSources.includes(token) && !modelSource.includes(token)) {
throw new Error(`Repository settings should include ${token}`);
}
}
if (
pageSource.includes("generated/legacy-server-api") ||
pageSource.includes("response.json()") ||
pageSource.includes(" as CreateWorkspaceRepository")
) {
throw new Error(
"Repository create UI must use the generated contract and strict API client",
);
}
for (const kind of ["ssh", "https"]) {
if (!pageSource.includes(`kind === '${kind}'`)) {
throw new Error(`Repository Access should support ${kind}`);
+2 -1
View File
@@ -1,9 +1,10 @@
import { svelteTesting } from "@testing-library/svelte/vite";
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
import { CODEMIRROR_VITE_DEDUPE } from "./src/lib/workspace/config-source/vite-dedupe";
export default defineConfig({
plugins: [sveltekit()],
plugins: [sveltekit(), svelteTesting()],
resolve: {
dedupe: CODEMIRROR_VITE_DEDUPE,
+11
View File
@@ -0,0 +1,11 @@
import { defineConfig, mergeConfig } from "vitest/config";
import viteConfig from "./vite.config.ts";
export default mergeConfig(
viteConfig,
defineConfig({
test: {
include: ["src/**/*.browser.spec.ts"],
},
}),
);