From 61a1a4d36810bd695cbd593ca353c58fd2714a38 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 12 Jun 2026 14:36:41 +0800 Subject: [PATCH] =?UTF-8?q?Web=E4=BC=98=E5=8C=96=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/acp/handler.go | 1 + internal/transport/http/middleware/auth.go | 18 +- .../transport/http/middleware/auth_test.go | 15 ++ web/package.json | 6 +- web/pnpm-lock.yaml | 227 ++++++++++++------ web/src/api/acp.ts | 23 +- .../components/acp/AgentConfigFilesPanel.vue | 110 ++------- web/src/composables/useAcpStream.test.ts | 7 +- web/src/composables/useAlert.ts | 18 ++ web/src/composables/useConfirm.ts | 30 +++ web/src/stores/workspaces.ts | 7 +- web/src/utils/date.ts | 7 + web/src/views/HomeView.vue | 65 ++--- web/src/views/acp/AcpAdminSessionsView.vue | 72 ++---- web/src/views/acp/AcpSessionDetailView.vue | 51 ++-- web/src/views/acp/AcpSessionListView.vue | 88 +++---- .../views/admin/AgentKindAdminListView.vue | 80 +++--- web/src/views/admin/AgentKindEditView.vue | 25 +- web/src/views/admin/UsageView.vue | 29 +-- web/src/views/chat/ChatDetailView.vue | 52 +--- .../chat/components/AttachmentUploader.vue | 31 +-- .../views/projects/RequirementDetailView.vue | 102 ++------ .../projects/components/ArtifactPanel.vue | 80 ++---- .../components/RequirementAcpPanel.vue | 43 +--- .../components/RequirementChatPanel.vue | 42 +--- .../components/RequirementHistoryPanel.vue | 130 +++------- .../components/RequirementSummaryPanel.vue | 50 +--- .../projects/components/SaveArtifactModal.vue | 29 +-- .../components/CommitHistoryTab.vue | 28 +-- web/tsconfig.json | 1 + web/vite.config.ts | 2 +- 31 files changed, 551 insertions(+), 918 deletions(-) create mode 100644 web/src/composables/useAlert.ts create mode 100644 web/src/composables/useConfirm.ts create mode 100644 web/src/utils/date.ts diff --git a/internal/acp/handler.go b/internal/acp/handler.go index dd28c15..b9d11f9 100644 --- a/internal/acp/handler.go +++ b/internal/acp/handler.go @@ -548,6 +548,7 @@ func (h *Handler) sessionWS(w http.ResponseWriter, r *http.Request) { conn, err := ws.Accept(w, r, &ws.AcceptOptions{ InsecureSkipVerify: false, + Subprotocols: []string{"acp-v1"}, }) if err != nil { return diff --git a/internal/transport/http/middleware/auth.go b/internal/transport/http/middleware/auth.go index 020acf2..281acd7 100644 --- a/internal/transport/http/middleware/auth.go +++ b/internal/transport/http/middleware/auth.go @@ -100,16 +100,26 @@ func UserIDFromContext(ctx context.Context) (uuid.UUID, bool) { // because RFC 6750 specifies the literal "Bearer" scheme name and being // strict here keeps the parsing surface small. // -// As a fallback, ?token= query parameter is accepted for endpoints that -// cannot send custom headers (notably browser-native WebSocket connections). -// Callers must keep token lifetimes short so the URL-borne token is not a -// long-lived secret should it be logged by intermediaries. +// For browser-native WebSocket connections, custom HTTP headers cannot be +// sent during the handshake. Two fallbacks are supported, in order of +// preference: +// 1. Sec-WebSocket-Protocol: the client sends a subprotocol entry +// "acw-token.". The token is extracted from the header, which is +// not logged by proxies/servers as readily as a URL query parameter. +// 2. ?token= query parameter (legacy): kept for backward compatibility. func extractBearer(r *http.Request) string { h := r.Header.Get("Authorization") const prefix = "Bearer " if strings.HasPrefix(h, prefix) { return strings.TrimSpace(h[len(prefix):]) } + const wsTokenPrefix = "acw-token." + for _, p := range strings.Split(r.Header.Get("Sec-WebSocket-Protocol"), ",") { + p = strings.TrimSpace(p) + if strings.HasPrefix(p, wsTokenPrefix) { + return p[len(wsTokenPrefix):] + } + } if q := r.URL.Query().Get("token"); q != "" { return q } diff --git a/internal/transport/http/middleware/auth_test.go b/internal/transport/http/middleware/auth_test.go index 59f5e6d..7afbad2 100644 --- a/internal/transport/http/middleware/auth_test.go +++ b/internal/transport/http/middleware/auth_test.go @@ -77,3 +77,18 @@ func TestAuth_BadTokenReturns401(t *testing.T) { h.ServeHTTP(rec, req) require.Equal(t, http.StatusUnauthorized, rec.Code) } + +func TestAuth_WebSocketTokenFromSubprotocol(t *testing.T) { + uid := uuid.New() + mw := Auth(fakeAuth{user: uid}, AuthOptions{}) + h := mw(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + got, ok := UserIDFromContext(r.Context()) + require.True(t, ok) + require.Equal(t, uid, got) + })) + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set("Sec-WebSocket-Protocol", "acp-v1, acw-token.ws-tok") + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + require.Equal(t, http.StatusOK, rec.Code) +} diff --git a/web/package.json b/web/package.json index e4a1b43..e84aa20 100644 --- a/web/package.json +++ b/web/package.json @@ -12,7 +12,8 @@ "test:watch": "vitest", "lint": "eslint .", "format": "prettier -w .", - "typecheck": "vue-tsc --noEmit" + "typecheck": "vue-tsc --noEmit", + "analyze": "vite-bundle-visualizer" }, "keywords": [], "author": "", @@ -34,16 +35,15 @@ "@types/node": "^25.6.0", "@vitejs/plugin-vue": "^6.0.6", "@vue/test-utils": "^2.4.9", - "autoprefixer": "^10.5.0", "eslint": "^10.2.1", "eslint-plugin-vue": "^10.9.0", "happy-dom": "^20.9.0", - "postcss": "^8.5.12", "prettier": "^3.8.3", "tailwindcss": "^4.2.4", "typescript": "^6.0.3", "typescript-eslint": "^8.59.1", "vite": "^8.0.10", + "vite-bundle-visualizer": "^1.2.1", "vitest": "^4.1.5", "vue-eslint-parser": "^10.4.0", "vue-tsc": "^3.2.7" diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index fc83163..4398c23 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -48,9 +48,6 @@ importers: '@vue/test-utils': specifier: ^2.4.9 version: 2.4.9(@vue/compiler-dom@3.5.33)(@vue/server-renderer@3.5.33(vue@3.5.33(typescript@6.0.3)))(vue@3.5.33(typescript@6.0.3)) - autoprefixer: - specifier: ^10.5.0 - version: 10.5.0(postcss@8.5.12) eslint: specifier: ^10.2.1 version: 10.2.1(jiti@2.6.1) @@ -60,9 +57,6 @@ importers: happy-dom: specifier: ^20.9.0 version: 20.9.0 - postcss: - specifier: ^8.5.12 - version: 8.5.12 prettier: specifier: ^3.8.3 version: 3.8.3 @@ -78,6 +72,9 @@ importers: vite: specifier: ^8.0.10 version: 8.0.10(@types/node@25.6.0)(jiti@2.6.1)(yaml@2.8.3) + vite-bundle-visualizer: + specifier: ^1.2.1 + version: 1.2.1 vitest: specifier: ^4.1.5 version: 4.1.5(@types/node@25.6.0)(happy-dom@20.9.0)(vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1)(yaml@2.8.3)) @@ -701,13 +698,6 @@ packages: async-validator@4.2.5: resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} - autoprefixer@10.5.0: - resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -715,11 +705,6 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.10.24: - resolution: {integrity: sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==} - engines: {node: '>=6.0.0'} - hasBin: true - birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} @@ -733,13 +718,9 @@ packages: resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - caniuse-lite@1.0.30001791: - resolution: {integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} @@ -749,6 +730,10 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -814,6 +799,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -826,9 +815,6 @@ packages: engines: {node: '>=14'} hasBin: true - electron-to-chromium@1.5.344: - resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -967,14 +953,15 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - fraction.js@5.3.4: - resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -1006,6 +993,13 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + import-from-esm@1.3.4: + resolution: {integrity: sha512-7EyUlPFC0HOlBDpUFGfYstsU7XHxZJKAAMzCT8wZ0hMW7b+hG51LIKTDcsgtz8Pu6YC0HqRVbX+rVUtsGMUKvg==} + engines: {node: '>=16.20'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1013,6 +1007,11 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1029,6 +1028,10 @@ packages: resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} engines: {node: '>=18'} + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1220,9 +1223,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-releases@2.0.38: - resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} - nopt@7.2.1: resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1234,6 +1234,10 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1299,9 +1303,6 @@ packages: resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.12: resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} engines: {node: ^10 || ^12 || >=14} @@ -1333,6 +1334,10 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} @@ -1341,6 +1346,19 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rollup-plugin-visualizer@5.14.0: + resolution: {integrity: sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + rolldown: 1.x + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rolldown: + optional: true + rollup: + optional: true + scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} @@ -1375,6 +1393,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + speakingurl@14.0.1: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} @@ -1427,6 +1449,10 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} + treemate@0.3.11: resolution: {integrity: sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==} @@ -1472,12 +1498,6 @@ packages: resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} engines: {node: ^20.19.0 || >=22.12.0} - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -1489,6 +1509,11 @@ packages: peerDependencies: vue: ^3.0.11 + vite-bundle-visualizer@1.2.1: + resolution: {integrity: sha512-cwz/Pg6+95YbgIDp+RPwEToc4TKxfsFWSG/tsl2DSZd9YZicUag1tQXjJ5xcL7ydvEoaC2FOZeaXOU60t9BRXw==} + engines: {node: ^18.19.0 || >=20.6.0} + hasBin: true + vite@8.0.10: resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1669,11 +1694,23 @@ packages: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yaml@2.8.3: resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} engines: {node: '>= 14.6'} hasBin: true + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2296,21 +2333,10 @@ snapshots: async-validator@4.2.5: {} - autoprefixer@10.5.0(postcss@8.5.12): - dependencies: - browserslist: 4.28.2 - caniuse-lite: 1.0.30001791 - fraction.js: 5.3.4 - picocolors: 1.1.1 - postcss: 8.5.12 - postcss-value-parser: 4.2.0 - balanced-match@1.0.2: {} balanced-match@4.0.4: {} - baseline-browser-mapping@2.10.24: {} - birpc@2.9.0: {} boolbase@1.0.0: {} @@ -2323,15 +2349,7 @@ snapshots: dependencies: balanced-match: 4.0.4 - browserslist@4.28.2: - dependencies: - baseline-browser-mapping: 2.10.24 - caniuse-lite: 1.0.30001791 - electron-to-chromium: 1.5.344 - node-releases: 2.0.38 - update-browserslist-db: 1.2.3(browserslist@4.28.2) - - caniuse-lite@1.0.30001791: {} + cac@6.7.14: {} chai@6.2.2: {} @@ -2339,6 +2357,12 @@ snapshots: dependencies: readdirp: 5.0.0 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -2391,6 +2415,8 @@ snapshots: deep-is@0.1.4: {} + define-lazy-prop@2.0.0: {} + detect-libc@2.1.2: {} eastasianwidth@0.2.0: {} @@ -2402,8 +2428,6 @@ snapshots: minimatch: 9.0.9 semver: 7.7.4 - electron-to-chromium@1.5.344: {} - emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -2545,11 +2569,11 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fraction.js@5.3.4: {} - fsevents@2.3.3: optional: true + get-caller-file@2.0.5: {} + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -2585,10 +2609,21 @@ snapshots: ignore@7.0.5: {} + import-from-esm@1.3.4: + dependencies: + debug: 4.4.3 + import-meta-resolve: 4.2.0 + transitivePeerDependencies: + - supports-color + + import-meta-resolve@4.2.0: {} + imurmurhash@0.1.4: {} ini@1.3.8: {} + is-docker@2.2.1: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -2599,6 +2634,10 @@ snapshots: is-what@5.5.0: {} + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + isexe@2.0.0: {} jackspeak@3.4.3: @@ -2775,8 +2814,6 @@ snapshots: natural-compare@1.4.0: {} - node-releases@2.0.38: {} - nopt@7.2.1: dependencies: abbrev: 2.0.0 @@ -2787,6 +2824,12 @@ snapshots: obug@2.1.1: {} + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2851,8 +2894,6 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-value-parser@4.2.0: {} - postcss@8.5.12: dependencies: nanoid: 3.3.11 @@ -2873,6 +2914,8 @@ snapshots: readdirp@5.0.0: {} + require-directory@2.1.1: {} + rfdc@1.4.1: {} rolldown@1.0.0-rc.17: @@ -2896,6 +2939,13 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + rollup-plugin-visualizer@5.14.0: + dependencies: + open: 8.4.2 + picomatch: 4.0.4 + source-map: 0.7.6 + yargs: 17.7.2 + scule@1.3.0: {} seemly@0.3.10: {} @@ -2916,6 +2966,8 @@ snapshots: source-map-js@1.2.1: {} + source-map@0.7.6: {} + speakingurl@14.0.1: {} stackback@0.0.2: {} @@ -2961,6 +3013,8 @@ snapshots: tinyrainbow@3.1.0: {} + tmp@0.2.7: {} + treemate@0.3.11: {} ts-api-utils@2.5.0(typescript@6.0.3): @@ -3004,12 +3058,6 @@ snapshots: picomatch: 4.0.4 webpack-virtual-modules: 0.6.2 - update-browserslist-db@1.2.3(browserslist@4.28.2): - dependencies: - browserslist: 4.28.2 - escalade: 3.2.0 - picocolors: 1.1.1 - uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -3021,6 +3069,17 @@ snapshots: evtd: 0.2.4 vue: 3.5.33(typescript@6.0.3) + vite-bundle-visualizer@1.2.1: + dependencies: + cac: 6.7.14 + import-from-esm: 1.3.4 + rollup-plugin-visualizer: 5.14.0 + tmp: 0.2.7 + transitivePeerDependencies: + - rolldown + - rollup + - supports-color + vite@8.0.10(@types/node@25.6.0)(jiti@2.6.1)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 @@ -3165,6 +3224,20 @@ snapshots: xml-name-validator@4.0.0: {} + y18n@5.0.8: {} + yaml@2.8.3: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} diff --git a/web/src/api/acp.ts b/web/src/api/acp.ts index 3fc90a4..b0c95cb 100644 --- a/web/src/api/acp.ts +++ b/web/src/api/acp.ts @@ -146,10 +146,8 @@ const enc = (v: string) => encodeURIComponent(v) export const acpApi = { agentKinds: { - list: () => - request<(AgentKindAdmin | AgentKindPublic)[]>('/api/v1/acp/agent-kinds'), - get: (id: string) => - request(`/api/v1/acp/agent-kinds/${enc(id)}`), + list: () => request<(AgentKindAdmin | AgentKindPublic)[]>('/api/v1/acp/agent-kinds'), + get: (id: string) => request(`/api/v1/acp/agent-kinds/${enc(id)}`), create: (req: CreateAgentKindReq) => request('/api/v1/acp/agent-kinds', { method: 'POST', body: req }), update: (id: string, req: UpdateAgentKindReq) => @@ -180,8 +178,7 @@ export const acpApi = { const qs = q.toString() return request('/api/v1/acp/sessions' + (qs ? '?' + qs : '')) }, - get: (id: string) => - request(`/api/v1/acp/sessions/${enc(id)}`), + get: (id: string) => request(`/api/v1/acp/sessions/${enc(id)}`), create: (req: CreateSessionReq) => request('/api/v1/acp/sessions', { method: 'POST', body: req }), terminate: (id: string) => @@ -205,13 +202,15 @@ export const acpApi = { // openSessionWS opens a native WebSocket to /sessions/{id}/ws. // // Auth: browser WebSocket cannot send custom headers, so the token is sent -// via the ?token= query parameter. The backend auth middleware accepts it -// as a fallback to Authorization. Keep token lifetimes short. +// via the Sec-WebSocket-Protocol handshake as "acw-token.". This is +// safer than a URL query parameter (which may be logged by proxies). The +// application subprotocol "acp-v1" is also advertised and selected by the +// server when supported. export function openSessionWS(sessionId: string, since: number, token: string): WebSocket { const proto = window.location.protocol === 'https:' ? 'wss' : 'ws' const host = window.location.host - const url = - `${proto}://${host}/api/v1/acp/sessions/${enc(sessionId)}` + - `/ws?since=${since}&token=${enc(token)}` - return new WebSocket(url) + const url = `${proto}://${host}/api/v1/acp/sessions/${enc(sessionId)}/ws?since=${since}` + const protocols = ['acp-v1'] + if (token) protocols.push(`acw-token.${token}`) + return new WebSocket(url, protocols) } diff --git a/web/src/components/acp/AgentConfigFilesPanel.vue b/web/src/components/acp/AgentConfigFilesPanel.vue index 935c534..3579e0c 100644 --- a/web/src/components/acp/AgentConfigFilesPanel.vue +++ b/web/src/components/acp/AgentConfigFilesPanel.vue @@ -13,6 +13,7 @@ import { ref, computed, onMounted, watch } from 'vue' import { NInput, NButton, NSelect, NSpin } from 'naive-ui' import { parse as parseToml, stringify as stringifyToml } from 'smol-toml' import { acpApi, type AgentConfigFile, type AgentClientType } from '@/api/acp' +import { useConfirm } from '@/composables/useConfirm' const props = defineProps<{ kindId: string @@ -22,6 +23,7 @@ const props = defineProps<{ const files = ref([]) const loading = ref(false) const error = ref('') +const confirm = useConfirm() // ===== 客户端规范配置文件 ===== @@ -270,7 +272,8 @@ async function upsert(relPath: string, content: string) { } async function removeFile(relPath: string) { - if (!confirm(`删除配置文件 ${relPath}?`)) return + const ok = await confirm(`删除配置文件 ${relPath}?`) + if (!ok) return error.value = '' try { await acpApi.configFiles.remove(props.kindId, relPath) @@ -299,9 +302,7 @@ watch(() => props.clientType, loadForm)