diff --git a/.agents/docs/notifications.md b/.agents/docs/notifications.md new file mode 100644 index 0000000..6777083 --- /dev/null +++ b/.agents/docs/notifications.md @@ -0,0 +1,27 @@ +# Operation notifications + +Provider requests use the Host `ctx.notifications.show()` service. Source icon/name, +card interactions, queue, expiry and suppression rules belong to Host. +Declare `notifications` in injection requirements. Field validation and persistent +business state stay beside the relevant control or object. Do not introduce a +custom Toast, page-wide operation error, or raw exception message. + +The [Host guide](https://github.com/cordisx/cordisx/blob/3cfe370eb7abf33e16686fbd82659cd441247fbd/.agents/docs/notifications.md) +owns usage and interaction guidance; the +[Protocol contract](https://github.com/cordisx/cordisx-protocol/blob/f46dd21e15a949a26f05f89bf11dea339fc60c02/.agents/docs/notifications-v1.md) +owns the public API. Source merge and package publication are separate stages. + +## Development dependencies + +`package.json` and `package-lock.json` pin formally merged Host and Protocol +commits. Install through the repository's normal dependency flow: + +```sh +npm ci +npm run check +``` + +Keep the shared Protocol override so public branded types resolve to one copy. +A missing notification service requires a supported Host upgrade; do not recreate +notification UI as an older-Host fallback. The former local candidate SDK archive +is no longer an installation prerequisite. diff --git a/.gitignore b/.gitignore index 1eae0cf..edfe37a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ dist/ node_modules/ + +.cache/sdk/ diff --git a/AGENTS.md b/AGENTS.md index c1cebed..a81460e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,3 +12,16 @@ - Read the organization [CSS ownership and maintenance rule](https://github.com/cordisx/cordisxmono/blob/main/.agents/rules/css.md) before changing CSS or a style-bearing DOM contract. + +## Operation notifications + +Use the public `ctx.notifications.show()` service for operation feedback and +require `notifications` in plugin injection. Do not create a custom Toast, +manually positioned alert, or page-wide success/error paragraph. Keep field +validation and durable business state beside the relevant object. Use stable +semantic `kind` values, localized safe text, and notification rules owned by Host; +never expose raw exceptions or notify on every polling attempt. +See the [Host notification guide](https://github.com/cordisx/cordisx/blob/3cfe370eb7abf33e16686fbd82659cd441247fbd/.agents/docs/notifications.md) +for the interaction contract and older-Host capability boundary. + +Dependency setup: [notification migration](./.agents/docs/notifications.md). diff --git a/README.md b/README.md index 1d1f93e..1cb4517 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,7 @@ Desktop. The package remains `private`; use the explicit local source distributi [HISTORY.md](HISTORY.md) records the filtered owner history from the original Host-owned implementation. This repository is licensed under [AGPL-3.0-or-later](LICENSE). + +## Notification feedback + +See [operation notifications and development dependencies](./.agents/docs/notifications.md). diff --git a/package-lock.json b/package-lock.json index a296010..4a2def6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "AGPL-3.0-or-later", "devDependencies": { "@cordisx/eslint-config": "github:cordisx/cordisxmono#c63c2e8c2ba7e11502934a52ad2ce3734e804cdc", - "@cordisx/protocol": "github:cordisx/cordisx-protocol#c2f6f8e4bf4a638bf4627c9c567792f2fedbcfd6", + "@cordisx/protocol": "github:cordisx/cordisx-protocol#f46dd21e15a949a26f05f89bf11dea339fc60c02", "@deepseek-ai/cordis": "4.0.1", "@deepseek-ai/schemastery": "3.18.1", "@projectwallace/stylelint-plugin": "0.7.0", @@ -18,7 +18,7 @@ "@vitejs/plugin-react": "6.1.0", "ajv": "8.20.0", "ajv-formats": "3.0.1", - "cordisx": "github:cordisx/cordisx#152d55aa26527b23c5e70a752fb3f6562e944afc", + "cordisx": "github:cordisx/cordisx#3cfe370eb7abf33e16686fbd82659cd441247fbd", "dprint": "0.57.1", "esbuild": "0.28.2", "eslint": "9.39.4", @@ -129,16 +129,6 @@ "@keyv/serialize": "^1.1.1" } }, - "node_modules/@cordisx/channel": { - "version": "0.1.0", - "resolved": "git+ssh://git@github.com/cordisx/plugin-channel.git#4cee12e3a92eeed557bc9de8cc4792710918327a", - "integrity": "sha512-uErxyAHsoKSQKASW8DZM/V6cQt6rPZ8jjE4vzQ8H1rFGIhuB8mE4Qy8PAnw7Y1sFafMsB+ENVqJDrLD5CwiGTA==", - "dev": true, - "license": "AGPL-3.0-or-later", - "engines": { - "node": ">=22.19" - } - }, "node_modules/@cordisx/eslint-config": { "version": "0.1.0", "resolved": "git+ssh://git@github.com/cordisx/cordisxmono.git#c63c2e8c2ba7e11502934a52ad2ce3734e804cdc", @@ -153,8 +143,8 @@ }, "node_modules/@cordisx/protocol": { "version": "0.1.0-alpha.0", - "resolved": "git+ssh://git@github.com/cordisx/cordisx-protocol.git#c2f6f8e4bf4a638bf4627c9c567792f2fedbcfd6", - "integrity": "sha512-m+/bdnP/tx0RbzISwUOfOcRVbjuarHTyNdHuX+IsrAETRmTwBTAhJI8S5Irx/2H+YZUrsU23JcUxMZCfcjFwxQ==", + "resolved": "git+ssh://git@github.com/cordisx/cordisx-protocol.git#f46dd21e15a949a26f05f89bf11dea339fc60c02", + "integrity": "sha512-o/YzvFs5gkrYsK0vijubLN/go2/oL04sixPz/Ks7N0eMLEHUL7qxD56Yf1ltW5dN9qfBIpgkHuiXgkSgOjj1bw==", "dev": true, "license": "AGPL-3.0-or-later", "engines": { @@ -2708,10 +2698,12 @@ "node_modules/cordisx": { "name": "cordisx-monorepo", "version": "0.1.0-beta.2", - "resolved": "git+ssh://git@github.com/cordisx/cordisx.git#152d55aa26527b23c5e70a752fb3f6562e944afc", - "integrity": "sha512-xMLYyYDpH1nN22T7jLllh83s+hbvFpfv3h2aR8R2p2DG9+bKCnA1RFfw0W6c/h5QztGWQuIRrx0bg+gadcIbYg==", + "resolved": "git+ssh://git@github.com/cordisx/cordisx.git#3cfe370eb7abf33e16686fbd82659cd441247fbd", + "integrity": "sha512-k6IhNsliOoWBDU0PuaQJUWO/llUdJAlrcdV9ErjyjSK+kYK0MJzO9UWWSnTc3bJGwLJ/Osv1cNo+tGV7BPwhcA==", "bundleDependencies": [ - "@cordisx/schemastery-ui" + "@cordisx/schemastery-ui", + "@cordisx/channel", + "@cordisx/plugin-cli-proxy-api" ], "dev": true, "license": "AGPL-3.0-or-later", @@ -2720,7 +2712,8 @@ ], "dependencies": { "@cordisx/channel": "github:cordisx/plugin-channel#4cee12e3a92eeed557bc9de8cc4792710918327a", - "@cordisx/protocol": "github:cordisx/cordisx-protocol#c2f6f8e4bf4a638bf4627c9c567792f2fedbcfd6", + "@cordisx/plugin-cli-proxy-api": "github:cordisx/plugin-cli-proxy-api#1428ee205aab31df2779398cc8491879b303d1d0", + "@cordisx/protocol": "github:cordisx/cordisx-protocol#f46dd21e15a949a26f05f89bf11dea339fc60c02", "@cordisx/schemastery-ui": "0.1.0-beta.2", "@deepseek-ai/cordis": "4.0.1", "@deepseek-ai/schemastery": "^3.18.1", @@ -2774,13 +2767,16 @@ "name": "cordisx", "version": "0.1.0-beta.2", "bundleDependencies": [ - "@cordisx/schemastery-ui" + "@cordisx/schemastery-ui", + "@cordisx/channel", + "@cordisx/plugin-cli-proxy-api" ], "extraneous": true, "license": "AGPL-3.0-or-later", "dependencies": { - "@cordisx/channel": "github:cordisx/plugin-channel#4cee12e3a92eeed557bc9de8cc4792710918327a", - "@cordisx/protocol": "github:cordisx/cordisx-protocol#c2f6f8e4bf4a638bf4627c9c567792f2fedbcfd6", + "@cordisx/channel": "0.1.0", + "@cordisx/plugin-cli-proxy-api": "0.1.0", + "@cordisx/protocol": "github:cordisx/cordisx-protocol#f46dd21e15a949a26f05f89bf11dea339fc60c02", "@cordisx/schemastery-ui": "0.1.0-beta.2", "@deepseek-ai/cordis": "4.0.1", "@deepseek-ai/schemastery": "^3.18.1", @@ -2821,7 +2817,6 @@ }, "devDependencies": { "@cordisx/channel-runtime": "file:../channel-runtime", - "@cordisx/protocol": "github:cordisx/cordisx-protocol#c2f6f8e4bf4a638bf4627c9c567792f2fedbcfd6", "@types/node": "^22.18.1", "@types/react": "19.2.18", "@types/react-dom": "19.2.5", diff --git a/package.json b/package.json index 7aec840..828c822 100644 --- a/package.json +++ b/package.json @@ -38,13 +38,13 @@ }, "devDependencies": { "@cordisx/eslint-config": "github:cordisx/cordisxmono#c63c2e8c2ba7e11502934a52ad2ce3734e804cdc", - "@cordisx/protocol": "github:cordisx/cordisx-protocol#c2f6f8e4bf4a638bf4627c9c567792f2fedbcfd6", + "@cordisx/protocol": "github:cordisx/cordisx-protocol#f46dd21e15a949a26f05f89bf11dea339fc60c02", "@deepseek-ai/cordis": "4.0.1", "@deepseek-ai/schemastery": "3.18.1", "@projectwallace/stylelint-plugin": "0.7.0", "@typescript-eslint/parser": "8.69.0", "@vitejs/plugin-react": "6.1.0", - "cordisx": "github:cordisx/cordisx#152d55aa26527b23c5e70a752fb3f6562e944afc", + "cordisx": "github:cordisx/cordisx#3cfe370eb7abf33e16686fbd82659cd441247fbd", "dprint": "0.57.1", "eslint": "9.39.4", "stylelint": "17.15.0", @@ -64,5 +64,8 @@ "bugs": { "url": "https://github.com/cordisx/plugin-cli-proxy-api/issues" }, - "homepage": "https://github.com/cordisx/plugin-cli-proxy-api#readme" + "homepage": "https://github.com/cordisx/plugin-cli-proxy-api#readme", + "overrides": { + "@cordisx/protocol": "$@cordisx/protocol" + } } diff --git a/src/index.ts b/src/index.ts index 6569d2c..e48642e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ import { createProviderFleetPage } from './provider-fleet-page.js' import { CLI_PROXY_ICON } from './icon.js' export const name = 'cli-proxy-api' -export const inject = ['i18n', 'slots', 'pages', 'routes', 'platform'] +export const inject = ['i18n', 'slots', 'pages', 'routes', 'platform', 'notifications'] export const icon = CLI_PROXY_ICON const capabilities = [ @@ -76,7 +76,7 @@ interface Messages { 'state.empty': undefined 'state.no-models': undefined 'state.select-session': undefined - 'state.error': { readonly message: string } + 'state.error': undefined 'session.provider': { readonly provider: string } 'session.model': { readonly model: string } 'permission.models.read': undefined @@ -185,7 +185,7 @@ export function apply(ctx: Context, config: Config = Config({})): void { 'state.empty': 'No matching sessions.', 'state.no-models': 'No provider models are available.', 'state.select-session': 'Select a provider session to inspect its content.', - 'state.error': 'Provider request failed: {message}', + 'state.error': 'Provider request failed. Check the connection and try again.', 'session.provider': 'Provider {provider}', 'session.model': 'Model {model}', 'permission.models.read': 'List models from configured external providers', @@ -230,7 +230,7 @@ export function apply(ctx: Context, config: Config = Config({})): void { 'state.empty': '没有匹配的会话。', 'state.no-models': '没有可用的 Provider 模型。', 'state.select-session': '选择一个 Provider 会话以查看内容。', - 'state.error': 'Provider 请求失败:{message}', + 'state.error': 'Provider 请求失败,请检查连接后重试', 'session.provider': 'Provider {provider}', 'session.model': '模型 {model}', 'permission.models.read': '读取已配置外部 Provider 的模型', diff --git a/src/provider-fleet-page.tsx b/src/provider-fleet-page.tsx index 5b0137a..a708607 100644 --- a/src/provider-fleet-page.tsx +++ b/src/provider-fleet-page.tsx @@ -37,7 +37,7 @@ interface Messages { 'state.empty': undefined 'state.no-models': undefined 'state.select-session': undefined - 'state.error': { readonly message: string } + 'state.error': undefined 'session.provider': { readonly provider: string } 'session.model': { readonly model: string } } @@ -78,8 +78,10 @@ export function createProviderFleetPage(ctx: Context, config: Config) { const [steer, setSteer] = useState('') const [status, setStatus] = useState('') - const showError = (error: { readonly message: string }) => - setStatus(props.t('state.error', { message: error.message })) + const showError = (_error: { readonly message: string }) => { + setStatus('') + ctx.notifications.show({ kind: 'provider.request-failed', type: 'error', message: props.t('state.error') }) + } const providerIds = provider === '' ? configuredProviders ?? [...new Set(models.map(item => item.ref.providerId))].sort() : [provider] diff --git a/test/manifest.mjs b/test/manifest.mjs index 5d20a80..d2f37d8 100644 --- a/test/manifest.mjs +++ b/test/manifest.mjs @@ -42,7 +42,7 @@ test('exports the CLIProxy renderer manifest through public Host contracts', () 'turns.control', ], ) - assert.deepEqual(plugin.inject, ['i18n', 'slots', 'pages', 'routes', 'platform']) + assert.deepEqual(plugin.inject, ['i18n', 'slots', 'pages', 'routes', 'platform', 'notifications']) }) test('registers one public page, route, and navigation contribution', () => {