@chatcode/chatcode-cli 3.0.2 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.i18n.yaml +6 -6
  2. package/README.md +11 -1
  3. package/README.zh.md +11 -1
  4. package/lib/bin.js +11 -67
  5. package/lib/{command-errors-CIYn1nnW.js → command-errors-CBJRI2gy.js} +12 -12
  6. package/lib/{command-trees-8o99Bws7.js → command-trees-Dvq4C7a4.js} +2 -2
  7. package/lib/{component-identity-CnmIKHEF.js → component-identity-DPdJglJO.js} +3 -3
  8. package/lib/{dist-DxjlHHph.js → dist-w_sIm4VD.js} +203 -197
  9. package/lib/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -0
  10. package/lib/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -0
  11. package/lib/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -0
  12. package/lib/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -0
  13. package/lib/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -0
  14. package/lib/dsh-ecosystem-spec/protocols/tui-channel.js +166 -0
  15. package/lib/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -0
  16. package/lib/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -0
  17. package/lib/dsh-ecosystem-spec/registry/README.md +14 -0
  18. package/lib/dsh-ecosystem-spec/registry/contracts/decision-events-v1alpha1.json +13 -0
  19. package/lib/dsh-ecosystem-spec/registry/contracts/scene-v1alpha1.json +13 -0
  20. package/lib/dsh-ecosystem-spec/registry/contracts/settings-section-v1alpha1.json +13 -0
  21. package/lib/dsh-ecosystem-spec/registry/contracts/tui-channel-v1alpha1.json +30 -0
  22. package/lib/dsh-ecosystem-spec/registry/contracts/workspace-provider-v1alpha1.json +6 -0
  23. package/lib/dsh-ecosystem-spec/registry/host-descriptor.tui.example.json +56 -0
  24. package/lib/dsh-ecosystem-spec/registry/permissions-0.1.json +13 -0
  25. package/lib/dsh-ecosystem-spec/registry/registry-0.15.json +149 -0
  26. package/lib/dsh-ecosystem-spec/schemas/conformance-claim.schema.json +15 -0
  27. package/lib/dsh-ecosystem-spec/schemas/effect-ledger-record.schema.json +16 -0
  28. package/lib/dsh-ecosystem-spec/schemas/host-descriptor.schema.json +26 -0
  29. package/lib/{dump-config-BSTrhTT_.js → dump-config-CauO75nE.js} +1 -1
  30. package/lib/dump-config-CijLv_kn.js +2 -0
  31. package/lib/{dump-config-schema-Cz4A13GB.js → dump-config-schema-CObLWWaW.js} +2 -2
  32. package/lib/{extension-events-DciGPKJa.js → extension-events-Be9LWvHD.js} +20 -20
  33. package/lib/{home-migration-DLaSvmfn.js → home-migration-BjDmRwBB.js} +23 -4
  34. package/lib/{host-access-CmD27uhQ.js → host-access-DeCjoRQP.js} +1 -1
  35. package/lib/{keymap-CFChEJ2W.js → keymap-CRUxM_qn.js} +2 -2
  36. package/lib/{plugin-jvd9PCVt.js → plugin-CDFSDE_y.js} +1491 -743
  37. package/lib/{plugin-DYHtreEd.js → plugin-Cn-hyYY8.js} +1 -1
  38. package/lib/{profile-boot-Blq7uqhx.js → profile-boot-SJGbpLgN.js} +13 -1
  39. package/lib/profile-boot.js +1 -1
  40. package/lib/{scenes-ai_8sHDs.js → scenes-D6jV1EEi.js} +3 -3
  41. package/lib/{settings-sections-QQ1-2lhL.js → settings-sections-DJQRNBo9.js} +3 -3
  42. package/lib/startup-diagnostics-C_hRCp-q.js +91 -0
  43. package/lib/{themes-DTzG_9I_.js → themes-BCly4_y9.js} +12 -12
  44. package/lib/tui-compat-command-trees.js +1 -1
  45. package/lib/tui-compat-extensions.js +4 -4
  46. package/lib/tui-compat-plugin-host.js +5 -5
  47. package/lib/tui-compat-scenes.js +1 -1
  48. package/lib/tui-compat-settings-sections.js +1 -1
  49. package/lib/tui-compat-working-activity.js +1 -1
  50. package/lib/tui-compat-workspaces.js +1 -1
  51. package/lib/tui-compat.js +41 -9
  52. package/lib/{tuiDisplayPrefs-BCHRRcfe.js → tuiDisplayPrefs-Dm_fULiR.js} +44 -2
  53. package/lib/types/home-migration.d.ts +8 -0
  54. package/lib/types/startup-diagnostics.d.ts +13 -0
  55. package/lib/types/tui-compat.d.ts +2 -0
  56. package/lib/{workspaces-CznCgVHv.js → workspaces-Bb3kCAFb.js} +2 -2
  57. package/package.json +40 -34
  58. package/runtime/manifest.json +2 -6
  59. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-account/lib/client.js +2 -2
  60. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-settings-general/lib/client.js +1 -1
  61. package/runtime/node_modules/@deepseek-ai/dsh-client-ui-sidebar/lib/client.js +1 -1
  62. package/runtime/node_modules/@deepseek-ai/dsh-compaction-basic/lib/index.js +84 -8
  63. package/runtime/node_modules/@deepseek-ai/dsh-compaction-basic/lib/types/region.d.ts +9 -0
  64. package/runtime/node_modules/@deepseek-ai/dsh-compaction-basic/lib/types/summarizer.d.ts +7 -0
  65. package/runtime/node_modules/@deepseek-ai/dsh-session-log-export/lib/client.js +3 -8
  66. package/runtime/node_modules/@deepseek-ai/dsh-session-log-export/lib/types/client/HeaderAction.d.ts +1 -1
  67. package/runtime/node_modules/@deepseek-ai/dsh-skill/lib/types/index.d.ts +1 -1
  68. package/runtime/node_modules/@deepseek-ai/dsh-skill-filesystem/lib/index.js +7 -1
  69. package/runtime/node_modules/@deepseek-ai/dsh-web-app/presets/cordis.patch.yml +2 -1
  70. package/runtime/node_modules/@deepseek-ai/dsh-web-app/presets/ptc.patch.yml +2 -1
  71. package/runtime/node_modules/@deepseek-ai/dsh-web-app/presets/standard.patch.yml +2 -1
  72. package/lib/dump-config-DBW7YLx-.js +0 -2
  73. package/runtime/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.md +0 -9
  74. package/runtime/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/bin/system.node +0 -0
  75. package/runtime/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/package.json +0 -31
  76. package/runtime/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/prebuilds.json +0 -6
  77. package/runtime/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.md +0 -9
  78. package/runtime/node_modules/@deepseek-ai/node-addon-system-darwin-x64/package.json +0 -31
  79. package/runtime/node_modules/@deepseek-ai/node-addon-system-darwin-x64/prebuilds.json +0 -6
  80. package/runtime/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.md +0 -11
  81. package/runtime/node_modules/@deepseek-ai/node-addon-system-linux-arm64/package.json +0 -31
  82. package/runtime/node_modules/@deepseek-ai/node-addon-system-linux-arm64/prebuilds.json +0 -24
  83. package/runtime/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.md +0 -11
  84. package/runtime/node_modules/@deepseek-ai/node-addon-system-linux-x64/package.json +0 -31
  85. package/runtime/node_modules/@deepseek-ai/node-addon-system-linux-x64/prebuilds.json +0 -24
@@ -0,0 +1,13 @@
1
+ import type { ApiReference, ProtocolCatalog, ProtocolDefinition } from '@dsh-std/core'
2
+ export { registerTuiContributionExtensions } from './tui-contributions.js'
3
+ export * from './tui-channel.js'
4
+
5
+ export const DECISION_EVENTS: Readonly<ApiReference & {
6
+ apiVersion: 'tui.dsh/v1alpha1'
7
+ kind: 'DecisionEvents'
8
+ }>
9
+
10
+ export const decisionEventsDefinition: ProtocolDefinition
11
+ export const profileDefinitions: readonly ProtocolDefinition[]
12
+
13
+ export function registerProfileProtocols(catalog: ProtocolCatalog): () => void
@@ -0,0 +1,59 @@
1
+ /** dsh-TUI private protocol definitions evaluated by @dsh-std/core. */
2
+
3
+ export { registerTuiContributionExtensions } from './tui-contributions.js'
4
+ export * from './tui-channel.js'
5
+ import { tuiChannelDefinition } from './tui-channel.js'
6
+
7
+ export const DECISION_EVENTS = Object.freeze({
8
+ apiVersion: 'tui.dsh/v1alpha1',
9
+ kind: 'DecisionEvents',
10
+ })
11
+
12
+ function featureSupport(value, label) {
13
+ if (value === undefined) return Object.freeze({ features: Object.freeze([]) })
14
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
15
+ throw new TypeError(`${label} support spec must be an object`)
16
+ }
17
+ const unknown = Object.keys(value).filter(key => key !== 'features' && !key.startsWith('x-'))
18
+ if (unknown.length > 0) throw new TypeError(`${label} support spec contains unknown field ${JSON.stringify(unknown[0])}`)
19
+ if (value.features !== undefined && (!Array.isArray(value.features)
20
+ || value.features.some(feature => typeof feature !== 'string' || feature.trim() === ''))) {
21
+ throw new TypeError(`${label} support spec.features must be an array of non-empty strings`)
22
+ }
23
+ return Object.freeze({ features: Object.freeze([...(value.features ?? [])].sort()) })
24
+ }
25
+
26
+ function providerDefinition(reference, label) {
27
+ return Object.freeze({
28
+ ...reference,
29
+ validateRequirement(value) {
30
+ if (value !== undefined) throw new TypeError(`${label} requirement does not accept spec in manifest v0.15`)
31
+ return undefined
32
+ },
33
+ validateSupport(value) { return featureSupport(value, label) },
34
+ negotiate(input) {
35
+ const providers = [...new Set(input.supports.map(row => row.participant))].sort()
36
+ const issues = input.requirements.flatMap(row => providers.length === 0
37
+ ? [{
38
+ code: row.requirement.optional === true ? 'optional-support-missing' : 'required-support-missing',
39
+ severity: row.requirement.optional === true ? 'warning' : 'error',
40
+ participant: row.participant,
41
+ message: `${label} has no provider in this negotiation scope`,
42
+ }]
43
+ : [])
44
+ return { agreement: Object.freeze({ providers: Object.freeze(providers) }), issues: Object.freeze(issues) }
45
+ },
46
+ })
47
+ }
48
+
49
+ export const decisionEventsDefinition = providerDefinition(DECISION_EVENTS, 'DecisionEvents')
50
+
51
+ export const profileDefinitions = Object.freeze([
52
+ decisionEventsDefinition,
53
+ tuiChannelDefinition,
54
+ ])
55
+
56
+ export function registerProfileProtocols(catalog) {
57
+ const disposers = profileDefinitions.map(definition => catalog.register(definition))
58
+ return () => { for (const dispose of disposers.reverse()) dispose() }
59
+ }
@@ -0,0 +1,15 @@
1
+ export const TUI_CHANNEL_HTTP_API_VERSION: 'tui.dsh/v1alpha1'
2
+ export const TUI_CHANNEL_HTTP_KIND: 'ChannelEndpoint'
3
+ export const TUI_CHANNEL_HTTP_HEALTH_PATH: '/dsh-tui/v1/health'
4
+ export const TUI_CHANNEL_HTTP_DESCRIPTOR_PATH: '/dsh-tui/v1/endpoint'
5
+ export const TUI_CHANNEL_HTTP_WEBSOCKET_PATH: '/dsh-tui/v1/connection'
6
+
7
+ export interface TuiChannelEndpointDescriptor {
8
+ readonly apiVersion: typeof TUI_CHANNEL_HTTP_API_VERSION
9
+ readonly kind: typeof TUI_CHANNEL_HTTP_KIND
10
+ readonly origin: string
11
+ readonly connection: typeof TUI_CHANNEL_HTTP_WEBSOCKET_PATH
12
+ }
13
+
14
+ export function createTuiChannelEndpointDescriptor(origin: string): TuiChannelEndpointDescriptor
15
+ export function validateTuiChannelEndpointDescriptor(value: unknown): TuiChannelEndpointDescriptor
@@ -0,0 +1,32 @@
1
+ export const TUI_CHANNEL_HTTP_API_VERSION = 'tui.dsh/v1alpha1'
2
+ export const TUI_CHANNEL_HTTP_KIND = 'ChannelEndpoint'
3
+ export const TUI_CHANNEL_HTTP_HEALTH_PATH = '/dsh-tui/v1/health'
4
+ export const TUI_CHANNEL_HTTP_DESCRIPTOR_PATH = '/dsh-tui/v1/endpoint'
5
+ export const TUI_CHANNEL_HTTP_WEBSOCKET_PATH = '/dsh-tui/v1/connection'
6
+
7
+ export function createTuiChannelEndpointDescriptor(origin) {
8
+ const normalized = new URL(origin)
9
+ if (normalized.protocol !== 'http:' && normalized.protocol !== 'https:') {
10
+ throw new TypeError('TUI Channel endpoint origin must use http or https')
11
+ }
12
+ normalized.pathname = '/'
13
+ normalized.search = ''
14
+ normalized.hash = ''
15
+ return Object.freeze({
16
+ apiVersion: TUI_CHANNEL_HTTP_API_VERSION,
17
+ kind: TUI_CHANNEL_HTTP_KIND,
18
+ origin: normalized.toString().replace(/\/$/u, ''),
19
+ connection: TUI_CHANNEL_HTTP_WEBSOCKET_PATH,
20
+ })
21
+ }
22
+
23
+ export function validateTuiChannelEndpointDescriptor(value) {
24
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) throw new TypeError('TUI Channel endpoint descriptor must be an object')
25
+ if (value.apiVersion !== TUI_CHANNEL_HTTP_API_VERSION || value.kind !== TUI_CHANNEL_HTTP_KIND) {
26
+ throw new TypeError('TUI Channel endpoint descriptor version or kind is unsupported')
27
+ }
28
+ if (typeof value.origin !== 'string') throw new TypeError('TUI Channel endpoint origin must be a string')
29
+ createTuiChannelEndpointDescriptor(value.origin)
30
+ if (value.connection !== TUI_CHANNEL_HTTP_WEBSOCKET_PATH) throw new TypeError('TUI Channel endpoint connection path is unsupported')
31
+ return value
32
+ }
@@ -0,0 +1,59 @@
1
+ import type { ApiReference, ProtocolDefinition } from '@dsh-std/core'
2
+
3
+ export type JsonValue = null | boolean | number | string | readonly JsonValue[] | { readonly [key: string]: JsonValue }
4
+
5
+ export const TUI_CHANNEL: Readonly<ApiReference & {
6
+ apiVersion: 'tui.dsh/v1alpha1'
7
+ kind: 'Channel'
8
+ }>
9
+ export const TUI_CHANNEL_WIRE_REVISION: 6
10
+ export const TUI_CHANNEL_FEATURES: readonly string[]
11
+
12
+ export interface TuiChannelRequirementSpec {
13
+ readonly wireRevision: number
14
+ readonly features: readonly string[]
15
+ }
16
+ export interface TuiChannelSupportSpec extends TuiChannelRequirementSpec {}
17
+
18
+ export interface TuiChannelSnapshot {
19
+ readonly wireRevision: number
20
+ readonly channelId: string
21
+ readonly version: number
22
+ readonly state: Readonly<Record<string, JsonValue>>
23
+ }
24
+
25
+ export interface TuiChannelWorkspaceFlowChoice {
26
+ readonly id: string
27
+ readonly label: string
28
+ readonly description?: string
29
+ readonly badge?: string
30
+ readonly action: string
31
+ readonly input?: {
32
+ readonly action: string
33
+ readonly initialValue?: string
34
+ readonly placeholder?: string
35
+ }
36
+ }
37
+
38
+ export type TuiChannelWorkspaceFlowResult =
39
+ | { readonly kind: 'target'; readonly target: import('./tui-contributions.js').WorkspaceTarget }
40
+ | { readonly kind: 'choices'; readonly title: string; readonly choices: readonly TuiChannelWorkspaceFlowChoice[] }
41
+
42
+ export type TuiChannelInput =
43
+ | { readonly operation: 'open'; readonly value: { readonly workspace?: string; readonly sessionId?: string; readonly options?: JsonValue } }
44
+ | { readonly operation: 'subscribe'; readonly value: { readonly channelId: string; readonly afterVersion: number } }
45
+ | { readonly operation: 'invoke'; readonly value: { readonly channelId: string; readonly method: string; readonly arguments: readonly JsonValue[] } }
46
+ | { readonly operation: 'close'; readonly value: { readonly channelId: string } }
47
+
48
+ export interface TuiChannelInvokeOutput {
49
+ readonly value: JsonValue
50
+ readonly valueDefined?: boolean
51
+ readonly snapshot?: TuiChannelSnapshot
52
+ }
53
+
54
+ export const tuiChannelDefinition: ProtocolDefinition
55
+ export function validateTuiChannelRequirement(value: unknown): TuiChannelRequirementSpec
56
+ export function validateTuiChannelSupport(value: unknown): TuiChannelSupportSpec
57
+ export function validateTuiChannelInput(operation: string, value: unknown): object
58
+ export function validateTuiChannelSnapshot(value: unknown): TuiChannelSnapshot
59
+ export function validateTuiChannelOutput(operation: string, value: unknown): object
@@ -0,0 +1,166 @@
1
+ import { defineCapabilityProtocol } from '@dsh-std/connection'
2
+
3
+ export const TUI_CHANNEL = Object.freeze({
4
+ apiVersion: 'tui.dsh/v1alpha1',
5
+ kind: 'Channel',
6
+ })
7
+
8
+ export const TUI_CHANNEL_WIRE_REVISION = 6
9
+
10
+ export const TUI_CHANNEL_FEATURES = Object.freeze([
11
+ 'commands',
12
+ 'credentials',
13
+ 'diagnostics',
14
+ 'files',
15
+ 'models',
16
+ 'modes',
17
+ 'presets',
18
+ 'presentation',
19
+ 'provider-setup',
20
+ 'scenes',
21
+ 'session-history',
22
+ 'session-input',
23
+ 'session-lifecycle',
24
+ 'session-state',
25
+ 'settings',
26
+ 'skills',
27
+ 'subagents',
28
+ 'trace',
29
+ 'workspaces',
30
+ ])
31
+
32
+ export const tuiChannelDefinition = defineCapabilityProtocol({
33
+ ...TUI_CHANNEL,
34
+ validateRequirement: validateTuiChannelRequirement,
35
+ validateSupport: validateTuiChannelSupport,
36
+ })
37
+
38
+ export function validateTuiChannelRequirement(value) {
39
+ const spec = exactRecord(value, ['wireRevision', 'features'], 'Channel requirement')
40
+ const wireRevision = positiveInteger(spec.wireRevision, 'Channel requirement.wireRevision')
41
+ const features = featureList(spec.features, 'Channel requirement.features')
42
+ return Object.freeze({ wireRevision, features })
43
+ }
44
+
45
+ export function validateTuiChannelSupport(value) {
46
+ const spec = exactRecord(value, ['wireRevision', 'features'], 'Channel support')
47
+ const wireRevision = positiveInteger(spec.wireRevision, 'Channel support.wireRevision')
48
+ const features = featureList(spec.features, 'Channel support.features')
49
+ return Object.freeze({ wireRevision, features })
50
+ }
51
+
52
+ export function validateTuiChannelInput(operation, value) {
53
+ switch (operation) {
54
+ case 'open': {
55
+ const input = exactRecord(value, ['workspace', 'sessionId', 'options'], 'Channel.open input')
56
+ optionalString(input.workspace, 'Channel.open input.workspace')
57
+ optionalString(input.sessionId, 'Channel.open input.sessionId')
58
+ if (input.options !== undefined) jsonValue(input.options, 'Channel.open input.options', new Set())
59
+ return input
60
+ }
61
+ case 'subscribe': {
62
+ const input = exactRecord(value, ['channelId', 'afterVersion'], 'Channel.subscribe input')
63
+ nonEmpty(input.channelId, 'Channel.subscribe input.channelId')
64
+ nonNegativeInteger(input.afterVersion, 'Channel.subscribe input.afterVersion')
65
+ return input
66
+ }
67
+ case 'invoke': {
68
+ const input = exactRecord(value, ['channelId', 'method', 'arguments'], 'Channel.invoke input')
69
+ nonEmpty(input.channelId, 'Channel.invoke input.channelId')
70
+ nonEmpty(input.method, 'Channel.invoke input.method')
71
+ if (!Array.isArray(input.arguments)) throw new TypeError('Channel.invoke input.arguments must be an array')
72
+ jsonValue(input.arguments, 'Channel.invoke input.arguments', new Set())
73
+ return input
74
+ }
75
+ case 'close': {
76
+ const input = exactRecord(value, ['channelId'], 'Channel.close input')
77
+ nonEmpty(input.channelId, 'Channel.close input.channelId')
78
+ return input
79
+ }
80
+ default:
81
+ throw new TypeError(`Channel operation ${JSON.stringify(operation)} is not defined`)
82
+ }
83
+ }
84
+
85
+ export function validateTuiChannelSnapshot(value) {
86
+ const snapshot = exactRecord(value, ['wireRevision', 'channelId', 'version', 'state'], 'Channel snapshot')
87
+ positiveInteger(snapshot.wireRevision, 'Channel snapshot.wireRevision')
88
+ nonEmpty(snapshot.channelId, 'Channel snapshot.channelId')
89
+ nonNegativeInteger(snapshot.version, 'Channel snapshot.version')
90
+ const state = exactRecord(snapshot.state, Object.keys(snapshot.state ?? {}), 'Channel snapshot.state')
91
+ jsonValue(state, 'Channel snapshot.state', new Set())
92
+ return snapshot
93
+ }
94
+
95
+ export function validateTuiChannelOutput(operation, value) {
96
+ if (operation === 'open' || operation === 'subscribe') return validateTuiChannelSnapshot(value)
97
+ if (operation === 'invoke') {
98
+ const output = exactRecord(value, ['value', 'valueDefined', 'snapshot'], 'Channel.invoke output')
99
+ jsonValue(output.value, 'Channel.invoke output.value', new Set())
100
+ if (output.valueDefined !== undefined && typeof output.valueDefined !== 'boolean') {
101
+ throw new TypeError('Channel.invoke output.valueDefined must be boolean')
102
+ }
103
+ if (output.snapshot !== undefined) validateTuiChannelSnapshot(output.snapshot)
104
+ return output
105
+ }
106
+ if (operation === 'close') {
107
+ const output = exactRecord(value, ['closed'], 'Channel.close output')
108
+ if (output.closed !== true) throw new TypeError('Channel.close output.closed must be true')
109
+ return output
110
+ }
111
+ throw new TypeError(`Channel operation ${JSON.stringify(operation)} is not defined`)
112
+ }
113
+
114
+ function exactRecord(value, allowed, label) {
115
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) throw new TypeError(`${label} must be an object`)
116
+ const unknown = Object.keys(value).filter(key => !allowed.includes(key) && !key.startsWith('x-'))
117
+ if (unknown.length > 0) throw new TypeError(`${label} contains unknown field ${JSON.stringify(unknown[0])}`)
118
+ return value
119
+ }
120
+
121
+ function featureList(value, label) {
122
+ if (!Array.isArray(value) || value.length === 0) throw new TypeError(`${label} must be a non-empty array`)
123
+ const features = value.map((feature, index) => {
124
+ nonEmpty(feature, `${label}[${index}]`)
125
+ return feature
126
+ })
127
+ if (new Set(features).size !== features.length) throw new TypeError(`${label} contains duplicates`)
128
+ return Object.freeze([...features].sort())
129
+ }
130
+
131
+ function positiveInteger(value, label) {
132
+ if (!Number.isSafeInteger(value) || value < 1) throw new TypeError(`${label} must be a positive integer`)
133
+ return value
134
+ }
135
+
136
+ function nonNegativeInteger(value, label) {
137
+ if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${label} must be a non-negative integer`)
138
+ return value
139
+ }
140
+
141
+ function nonEmpty(value, label) {
142
+ if (typeof value !== 'string' || value.trim() === '') throw new TypeError(`${label} must be a non-empty string`)
143
+ }
144
+
145
+ function optionalString(value, label) {
146
+ if (value !== undefined) nonEmpty(value, label)
147
+ }
148
+
149
+ function jsonValue(value, label, ancestors) {
150
+ if (value === null || typeof value === 'boolean' || typeof value === 'string') return
151
+ if (typeof value === 'number') {
152
+ if (!Number.isFinite(value)) throw new TypeError(`${label} must contain only finite numbers`)
153
+ return
154
+ }
155
+ if (typeof value !== 'object') throw new TypeError(`${label} must be a JSON value`)
156
+ if (ancestors.has(value)) throw new TypeError(`${label} must not contain cycles`)
157
+ ancestors.add(value)
158
+ if (Array.isArray(value)) {
159
+ for (const [index, item] of value.entries()) jsonValue(item, `${label}[${index}]`, ancestors)
160
+ } else {
161
+ const prototype = Object.getPrototypeOf(value)
162
+ if (prototype !== Object.prototype && prototype !== null) throw new TypeError(`${label} must contain only plain objects`)
163
+ for (const [key, item] of Object.entries(value)) jsonValue(item, `${label}.${key}`, ancestors)
164
+ }
165
+ ancestors.delete(value)
166
+ }
@@ -0,0 +1,42 @@
1
+ import type { ApiReference } from '@dsh-std/core'
2
+ import type { ManifestDefinitionCatalog, ManifestObjectDefinition } from '@dsh-std/manifest'
3
+
4
+ export const API_VERSION: 'tui.dsh/v1alpha1'
5
+ export const SETTINGS_SECTION: Readonly<ApiReference & { kind: 'SettingsSection' }>
6
+ export const SCENE: Readonly<ApiReference & { kind: 'Scene' }>
7
+
8
+ export type LocalizedText = Readonly<Record<string, string>>
9
+ export type SettingsFieldKind = 'text' | 'number' | 'boolean' | 'select'
10
+ export interface SettingsFieldOption { readonly value: string; readonly label: string; readonly titles?: LocalizedText }
11
+ export interface SettingsFieldSpec {
12
+ readonly path: readonly string[]
13
+ readonly label: string
14
+ readonly titles?: LocalizedText
15
+ readonly hint?: string
16
+ readonly hintTitles?: LocalizedText
17
+ readonly kind: SettingsFieldKind
18
+ readonly options?: readonly SettingsFieldOption[]
19
+ readonly placeholder?: string
20
+ readonly secretRef?: string
21
+ }
22
+ export interface SettingsSectionSpec {
23
+ readonly namespace: string
24
+ readonly title: string
25
+ readonly titles?: LocalizedText
26
+ readonly fields: readonly SettingsFieldSpec[]
27
+ }
28
+ export interface SettingsSectionHandler {}
29
+
30
+ export interface SceneSpec { readonly title?: string; readonly titles?: LocalizedText }
31
+ export interface SceneHandler<Props = unknown, Result = unknown> {
32
+ readonly component: (props: Props) => Result
33
+ }
34
+
35
+ export const settingsSectionExtensionDefinition: ManifestObjectDefinition
36
+ export const sceneExtensionDefinition: ManifestObjectDefinition
37
+ export const contributionExtensionDefinitions: readonly ManifestObjectDefinition[]
38
+ export function validateSettingsSectionSpec(value: unknown): Readonly<SettingsSectionSpec>
39
+ export function validateSceneSpec(value: unknown): Readonly<SceneSpec>
40
+ export function registerTuiContributionExtensions(catalog: ManifestDefinitionCatalog): () => void
41
+ export function assertSettingsSectionHandler(value: unknown): asserts value is SettingsSectionHandler
42
+ export function assertSceneHandler(value: unknown): asserts value is SceneHandler
@@ -0,0 +1,143 @@
1
+ /** dsh-TUI private, lifecycle-owned manifest contribution definitions. */
2
+
3
+ export const API_VERSION = 'tui.dsh/v1alpha1'
4
+
5
+ export const SETTINGS_SECTION = Object.freeze({
6
+ apiVersion: API_VERSION,
7
+ kind: 'SettingsSection',
8
+ })
9
+
10
+ export const SCENE = Object.freeze({
11
+ apiVersion: API_VERSION,
12
+ kind: 'Scene',
13
+ })
14
+
15
+ export const settingsSectionExtensionDefinition = Object.freeze({
16
+ ...SETTINGS_SECTION,
17
+ validateMetadata(metadata) {
18
+ contributionName(metadata?.name, 'SettingsSection metadata.name')
19
+ },
20
+ validateSpec: validateSettingsSectionSpec,
21
+ })
22
+
23
+ export function validateSettingsSectionSpec(value) {
24
+ const spec = exactRecord(value, ['namespace', 'title', 'titles', 'fields'], 'SettingsSection content')
25
+ contributionName(spec.namespace, 'SettingsSection content.namespace')
26
+ nonEmpty(spec.title, 'SettingsSection content.title')
27
+ localized(spec.titles, 'SettingsSection content.titles')
28
+ if (!Array.isArray(spec.fields)) throw new TypeError('SettingsSection content.fields must be an array')
29
+ return Object.freeze({
30
+ namespace: spec.namespace,
31
+ title: spec.title,
32
+ ...(spec.titles === undefined ? {} : { titles: freezeRecord(spec.titles) }),
33
+ fields: Object.freeze(spec.fields.map((field, index) => validateSettingsField(field, index))),
34
+ })
35
+ }
36
+
37
+ export const sceneExtensionDefinition = Object.freeze({
38
+ ...SCENE,
39
+ validateMetadata(metadata) {
40
+ contributionName(metadata?.name, 'Scene metadata.name')
41
+ },
42
+ validateSpec: validateSceneSpec,
43
+ })
44
+
45
+ export function validateSceneSpec(value) {
46
+ const spec = exactRecord(value, ['title', 'titles'], 'Scene content')
47
+ if (spec.title !== undefined) nonEmpty(spec.title, 'Scene content.title')
48
+ localized(spec.titles, 'Scene content.titles')
49
+ return Object.freeze({
50
+ ...(spec.title === undefined ? {} : { title: spec.title }),
51
+ ...(spec.titles === undefined ? {} : { titles: freezeRecord(spec.titles) }),
52
+ })
53
+ }
54
+
55
+ export const contributionExtensionDefinitions = Object.freeze([
56
+ settingsSectionExtensionDefinition,
57
+ sceneExtensionDefinition,
58
+ ])
59
+
60
+ export function registerTuiContributionExtensions(catalog) {
61
+ const disposers = contributionExtensionDefinitions.map(definition => catalog.registerExtension(definition))
62
+ return () => { for (const dispose of disposers.reverse()) dispose() }
63
+ }
64
+
65
+ export function assertSettingsSectionHandler(value) {
66
+ runtimeObject(value, 'SettingsSection handler')
67
+ }
68
+
69
+ export function assertSceneHandler(value) {
70
+ const handler = runtimeObject(value, 'Scene handler')
71
+ if (typeof handler.component !== 'function') throw new TypeError('Scene handler.component must be a function')
72
+ }
73
+
74
+ function validateSettingsField(value, index) {
75
+ const label = `SettingsSection spec.fields[${index}]`
76
+ const field = exactRecord(value, [
77
+ 'path', 'label', 'titles', 'hint', 'hintTitles', 'kind', 'options', 'placeholder', 'secretRef',
78
+ ], label)
79
+ if (!Array.isArray(field.path) || field.path.length === 0
80
+ || field.path.some(segment => typeof segment !== 'string' || segment.length === 0)) {
81
+ throw new TypeError(`${label}.path must be a non-empty string array`)
82
+ }
83
+ nonEmpty(field.label, `${label}.label`)
84
+ localized(field.titles, `${label}.titles`)
85
+ if (field.hint !== undefined) nonEmpty(field.hint, `${label}.hint`)
86
+ localized(field.hintTitles, `${label}.hintTitles`)
87
+ if (!['text', 'number', 'boolean', 'select'].includes(field.kind)) {
88
+ throw new TypeError(`${label}.kind is invalid`)
89
+ }
90
+ if (field.placeholder !== undefined && typeof field.placeholder !== 'string') {
91
+ throw new TypeError(`${label}.placeholder must be a string`)
92
+ }
93
+ if (field.secretRef !== undefined) nonEmpty(field.secretRef, `${label}.secretRef`)
94
+ if (field.options !== undefined) {
95
+ if (!Array.isArray(field.options)) throw new TypeError(`${label}.options must be an array`)
96
+ for (const [optionIndex, valueOption] of field.options.entries()) {
97
+ const option = exactRecord(valueOption, ['value', 'label', 'titles'], `${label}.options[${optionIndex}]`)
98
+ nonEmpty(option.value, `${label}.options[${optionIndex}].value`)
99
+ nonEmpty(option.label, `${label}.options[${optionIndex}].label`)
100
+ localized(option.titles, `${label}.options[${optionIndex}].titles`)
101
+ }
102
+ }
103
+ return Object.freeze(structuredClone(field))
104
+ }
105
+
106
+ function exactRecord(value, allowed, label) {
107
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
108
+ throw new TypeError(`${label} must be an object`)
109
+ }
110
+ const unknown = Object.keys(value).filter(key => !allowed.includes(key) && !key.startsWith('x-'))
111
+ if (unknown.length > 0) throw new TypeError(`${label} contains unknown field ${JSON.stringify(unknown[0])}`)
112
+ return value
113
+ }
114
+
115
+ function runtimeObject(value, label) {
116
+ if ((typeof value !== 'object' && typeof value !== 'function') || value === null || Array.isArray(value)) {
117
+ throw new TypeError(`${label} must be an object`)
118
+ }
119
+ return value
120
+ }
121
+
122
+ function contributionName(value, label) {
123
+ if (typeof value !== 'string' || !/^[a-z][a-z0-9_-]*$/u.test(value)) {
124
+ throw new TypeError(`${label} is invalid`)
125
+ }
126
+ }
127
+
128
+ function nonEmpty(value, label) {
129
+ if (typeof value !== 'string' || value.trim() === '') throw new TypeError(`${label} must be a non-empty string`)
130
+ }
131
+
132
+ function localized(value, label) {
133
+ if (value === undefined) return
134
+ const record = exactRecord(value, Object.keys(value), label)
135
+ for (const [locale, text] of Object.entries(record)) {
136
+ if (locale.trim() === '') throw new TypeError(`${label} contains an empty locale`)
137
+ nonEmpty(text, `${label}.${locale}`)
138
+ }
139
+ }
140
+
141
+ function freezeRecord(value) {
142
+ return Object.freeze({ ...value })
143
+ }
@@ -0,0 +1,14 @@
1
+ # dsh-TUI Admission Protocol Profile
2
+
3
+ [`registry-0.15.json`](registry-0.15.json) 不是全局协议注册表。它描述本 TUI admission profile 使用的 definition 集合:
4
+
5
+ - `imports` 引用固定 dsh-std revision 已提供的 definitions;
6
+ - `definitions` 只收录 dsh-TUI 私有 definitions。
7
+
8
+ 导入条目不复制 dsh-std 的 schema 或 contract 正文。Community v0.15 的 `Command`、`LocalStorage`、`MessageObserver`、Presentation 与 Workspace 定义分别由 `@dsh-std/command`、`@dsh-std/storage`、`@dsh-std/messages`、`@dsh-std/presentation` 与 `@dsh-std/workspace` 注册。
9
+
10
+ 本地 definition 的 contract profile 使用 SHA-256 固定。修改 profile 内容必须更新 digest,并按兼容性决定保留坐标或发布新 `apiVersion`。digest 只证明字节一致,不证明发布者身份。
11
+
12
+ `tui.dsh/*` 是 dsh-TUI 私有 namespace。私有 definition 与公共 definition 一样注册到 dsh-std `ProtocolCatalog`;目录收录本身不产生 live support。
13
+
14
+ 权限目录仍是 TUI authorization policy 的输入。permission grant 与 protocol support 分开判断,安装 definition 不自动授权操作。
@@ -0,0 +1,13 @@
1
+ {
2
+ "name":"tui.decision-events","version":"v1alpha1","kind":"capability",
3
+ "coordinates":{"apiVersion":"tui.dsh/v1alpha1","kind":"DecisionEvents"},
4
+ "caller":"authorized TUI decision-point dispatcher",
5
+ "permissions":["session.input.intercept","session.rewind.intercept","session.switch.intercept","session.compact.intercept"],
6
+ "operations":[{"name":"decide","input":{"event":"private decision envelope"},"output":{"decision":"event-point-defined decision or no-opinion"}}],
7
+ "errors":["INTERCEPT_PERMISSION_NOT_GRANTED","DECISION_STALE","DECISION_BUDGET_EXCEEDED","INVALID_DECISION"],
8
+ "concurrency":"one decision chain is serialized; independent decision points may run concurrently",
9
+ "timeout":"the dispatcher applies an explicit per-handler and total decision budget",
10
+ "cleanup":"subscriptions are owned by an activation scope and removed on deactivate or grant revocation",
11
+ "privacyClass":"sensitive",
12
+ "securityBoundary":false
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name":"tui.scene","version":"v1alpha1","kind":"extension",
3
+ "coordinates":{"apiVersion":"tui.dsh/v1alpha1","kind":"Scene"},
4
+ "caller":"trusted in-process dsh-TUI scene shell",
5
+ "permissions":[],
6
+ "operations":["open","close","render"],
7
+ "errors":["SCENE_INVALID","SCENE_CONFLICT","RENDERER_INCOMPATIBLE"],
8
+ "concurrency":"one scene may own the full-screen surface at a time",
9
+ "timeout":"scene operations are local; domain calls made by a scene retain their own deadlines",
10
+ "cleanup":"an open scene closes when its owning facet deactivates",
11
+ "privacyClass":"sensitive",
12
+ "securityBoundary":false
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name":"tui.settings-section","version":"v1alpha1","kind":"extension",
3
+ "coordinates":{"apiVersion":"tui.dsh/v1alpha1","kind":"SettingsSection"},
4
+ "caller":"dsh-TUI settings contribution binder",
5
+ "permissions":[],
6
+ "operations":["render"],
7
+ "errors":["SECTION_INVALID","NAMESPACE_CONFLICT","NAMESPACE_UNAVAILABLE"],
8
+ "concurrency":"one active section owns each settings namespace",
9
+ "timeout":"not applicable to declarative rendering; writes use the settings protocol deadline",
10
+ "cleanup":"removing a section removes only UI metadata and preserves settings and credentials",
11
+ "privacyClass":"public",
12
+ "securityBoundary":false
13
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "tui.channel",
3
+ "version": "v1alpha1",
4
+ "coordinates": {
5
+ "apiVersion": "tui.dsh/v1alpha1",
6
+ "kind": "Channel"
7
+ },
8
+ "kind": "capability",
9
+ "caller": "a negotiated TUI endpoint",
10
+ "permissions": [],
11
+ "operations": [
12
+ {"name": "open", "input": "workspace/session selector", "output": "complete channel snapshot"},
13
+ {"name": "subscribe", "input": "channel id and last observed version", "output": "snapshot progress stream"},
14
+ {"name": "invoke", "input": "channel method and JSON arguments", "output": "method progress, JSON result and optional snapshot"},
15
+ {"name": "close", "input": "channel id", "output": "closed acknowledgement"}
16
+ ],
17
+ "wireRevision": 6,
18
+ "features": [
19
+ "commands", "credentials", "diagnostics", "files", "models", "modes",
20
+ "presets", "presentation", "provider-setup", "scenes", "session-history", "session-input",
21
+ "session-lifecycle", "session-state", "settings", "skills", "subagents",
22
+ "trace", "workspaces"
23
+ ],
24
+ "errors": ["CHANNEL_NOT_FOUND", "FEATURE_UNAVAILABLE", "INVALID_ARGUMENT", "PERMISSION_DENIED", "REVISION_MISMATCH", "PROVIDER_FAILURE"],
25
+ "concurrency": "state mutations are serialized per channel; independent read operations may run concurrently",
26
+ "timeout": "the caller supplies connection invocation cancellation; subscriptions remain open until cancelled or closed",
27
+ "cleanup": "connection close or facet deactivate releases subscriptions, uploads and channel attachments",
28
+ "privacyClass": "sensitive",
29
+ "securityBoundary": false
30
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "status": "superseded",
3
+ "coordinates": {"apiVersion": "workspace.dsh/v1alpha1", "kind": "WorkspaceProvider"},
4
+ "package": "@dsh-std/workspace",
5
+ "proposal": "vendor/dsh-std/docs/proposals/workspace.zh.md"
6
+ }