feat: merge develop into hare/develop
This commit is contained in:
@@ -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",
|
||||
|
||||
Generated
+398
-23
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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>, };
|
||||
@@ -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>, };
|
||||
|
||||
@@ -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; };
|
||||
@@ -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>, };
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
@@ -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">></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("<script>bad()</script>"),
|
||||
"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 "&";
|
||||
case "<":
|
||||
return "<";
|
||||
case ">":
|
||||
return ">";
|
||||
case '"':
|
||||
return """;
|
||||
default:
|
||||
return char;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function escapeAttribute(value: string): string {
|
||||
return escapeHtml(value).replace(/'/g, "'");
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
+124
-18
@@ -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",
|
||||
);
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
});
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"],
|
||||
},
|
||||
}),
|
||||
);
|
||||
Reference in New Issue
Block a user