@deepseek-harness-tui/dsh-tui 0.8.1 → 0.8.2

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 (240) hide show
  1. package/README.md +22 -13
  2. package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -0
  3. package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -0
  4. package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -0
  5. package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -0
  6. package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -0
  7. package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -0
  8. package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -0
  9. package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -0
  10. package/dsh-ecosystem-spec/registry/README.md +14 -0
  11. package/dsh-ecosystem-spec/registry/contracts/scene-v1alpha1.json +13 -0
  12. package/dsh-ecosystem-spec/registry/contracts/settings-section-v1alpha1.json +13 -0
  13. package/dsh-ecosystem-spec/registry/contracts/tui-channel-v1alpha1.json +30 -0
  14. package/dsh-ecosystem-spec/registry/contracts/workspace-provider-v1alpha1.json +6 -0
  15. package/{ecosystem-spec → dsh-ecosystem-spec}/registry/permissions-0.1.json +1 -1
  16. package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -0
  17. package/lib/types/command-trees.d.ts +2 -1
  18. package/lib/types/command-trees.d.ts.map +1 -1
  19. package/lib/types/command-trees.js +1 -1
  20. package/lib/types/components/LoadedContextPanel.d.ts +10 -5
  21. package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
  22. package/lib/types/components/LoadedContextPanel.js +18 -7
  23. package/lib/types/components/LogoV2.d.ts.map +1 -1
  24. package/lib/types/components/LogoV2.js +16 -7
  25. package/lib/types/components/Markdown.d.ts +13 -1
  26. package/lib/types/components/Markdown.d.ts.map +1 -1
  27. package/lib/types/components/Markdown.js +15 -1
  28. package/lib/types/components/MessageList.d.ts +3 -1
  29. package/lib/types/components/MessageList.d.ts.map +1 -1
  30. package/lib/types/components/MessageList.js +90 -9
  31. package/lib/types/components/PromptInput.d.ts.map +1 -1
  32. package/lib/types/components/PromptInput.js +26 -12
  33. package/lib/types/components/StreamingMarkdown.d.ts +2 -8
  34. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  35. package/lib/types/components/StreamingMarkdown.js +51 -9
  36. package/lib/types/components/activityFrames.d.ts +1 -1
  37. package/lib/types/components/activityFrames.d.ts.map +1 -1
  38. package/lib/types/components/activityFrames.js +4 -1
  39. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +5 -1
  40. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  41. package/lib/types/components/messages/AssistantThinkingMessage.js +26 -3
  42. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  43. package/lib/types/components/messages/AssistantToolUseMessage.js +12 -1
  44. package/lib/types/dsh-adapter/channel.d.ts +31 -4
  45. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  46. package/lib/types/dsh-adapter/channel.js +267 -102
  47. package/lib/types/dsh-adapter/childStderr.d.ts.map +1 -1
  48. package/lib/types/dsh-adapter/childStderr.js +10 -0
  49. package/lib/types/dsh-adapter/command-attribution.d.ts.map +1 -1
  50. package/lib/types/dsh-adapter/command-attribution.js +2 -2
  51. package/lib/types/dsh-adapter/command-trees.d.ts +7 -1
  52. package/lib/types/dsh-adapter/command-trees.d.ts.map +1 -1
  53. package/lib/types/dsh-adapter/command-trees.js +70 -20
  54. package/lib/types/dsh-adapter/component-identity.d.ts +5 -0
  55. package/lib/types/dsh-adapter/component-identity.d.ts.map +1 -1
  56. package/lib/types/dsh-adapter/component-identity.js +14 -14
  57. package/lib/types/dsh-adapter/decision-guard.d.ts.map +1 -1
  58. package/lib/types/dsh-adapter/decision-guard.js +10 -16
  59. package/lib/types/dsh-adapter/dialogs.d.ts +2 -3
  60. package/lib/types/dsh-adapter/dialogs.d.ts.map +1 -1
  61. package/lib/types/dsh-adapter/dialogs.js +114 -60
  62. package/lib/types/dsh-adapter/effect-ledger.d.ts +0 -8
  63. package/lib/types/dsh-adapter/effect-ledger.d.ts.map +1 -1
  64. package/lib/types/dsh-adapter/effect-ledger.js +73 -53
  65. package/lib/types/dsh-adapter/extension-events.d.ts.map +1 -1
  66. package/lib/types/dsh-adapter/extension-events.js +39 -1
  67. package/lib/types/dsh-adapter/grants.d.ts.map +1 -1
  68. package/lib/types/dsh-adapter/grants.js +43 -14
  69. package/lib/types/dsh-adapter/host-access.d.ts +37 -0
  70. package/lib/types/dsh-adapter/host-access.d.ts.map +1 -0
  71. package/lib/types/dsh-adapter/host-access.js +652 -0
  72. package/lib/types/dsh-adapter/host-descriptor.d.ts +2 -2
  73. package/lib/types/dsh-adapter/host-descriptor.d.ts.map +1 -1
  74. package/lib/types/dsh-adapter/host-descriptor.js +31 -8
  75. package/lib/types/dsh-adapter/index.d.ts +4 -0
  76. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  77. package/lib/types/dsh-adapter/index.js +1 -0
  78. package/lib/types/dsh-adapter/message-observer.d.ts +10 -15
  79. package/lib/types/dsh-adapter/message-observer.d.ts.map +1 -1
  80. package/lib/types/dsh-adapter/message-observer.js +114 -70
  81. package/lib/types/dsh-adapter/plugin-host.d.ts +39 -6
  82. package/lib/types/dsh-adapter/plugin-host.d.ts.map +1 -1
  83. package/lib/types/dsh-adapter/plugin-host.js +139 -44
  84. package/lib/types/dsh-adapter/plugin-storage.d.ts +0 -5
  85. package/lib/types/dsh-adapter/plugin-storage.d.ts.map +1 -1
  86. package/lib/types/dsh-adapter/plugin-storage.js +48 -36
  87. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  88. package/lib/types/dsh-adapter/plugin.js +80 -16
  89. package/lib/types/dsh-adapter/promptDebug.d.ts +0 -6
  90. package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -1
  91. package/lib/types/dsh-adapter/promptDebug.js +13 -4
  92. package/lib/types/dsh-adapter/renderers.d.ts +6 -14
  93. package/lib/types/dsh-adapter/renderers.d.ts.map +1 -1
  94. package/lib/types/dsh-adapter/renderers.js +81 -46
  95. package/lib/types/dsh-adapter/scenes.d.ts +8 -4
  96. package/lib/types/dsh-adapter/scenes.d.ts.map +1 -1
  97. package/lib/types/dsh-adapter/scenes.js +136 -37
  98. package/lib/types/dsh-adapter/settings-sections.d.ts +7 -3
  99. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
  100. package/lib/types/dsh-adapter/settings-sections.js +106 -26
  101. package/lib/types/dsh-adapter/shortcuts.d.ts +8 -10
  102. package/lib/types/dsh-adapter/shortcuts.d.ts.map +1 -1
  103. package/lib/types/dsh-adapter/shortcuts.js +108 -36
  104. package/lib/types/dsh-adapter/status.d.ts +6 -7
  105. package/lib/types/dsh-adapter/status.d.ts.map +1 -1
  106. package/lib/types/dsh-adapter/status.js +88 -22
  107. package/lib/types/dsh-adapter/workspaces.d.ts +16 -5
  108. package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -1
  109. package/lib/types/dsh-adapter/workspaces.js +287 -170
  110. package/lib/types/extensions.d.ts +10 -5
  111. package/lib/types/extensions.d.ts.map +1 -1
  112. package/lib/types/extensions.js +11 -5
  113. package/lib/types/force-production-react.d.ts +1 -0
  114. package/lib/types/force-production-react.d.ts.map +1 -0
  115. package/lib/types/force-production-react.js +12 -0
  116. package/lib/types/i18n.d.ts +14 -6
  117. package/lib/types/i18n.d.ts.map +1 -1
  118. package/lib/types/i18n.js +4 -2
  119. package/lib/types/index.d.ts +1 -0
  120. package/lib/types/index.d.ts.map +1 -1
  121. package/lib/types/index.js +1 -0
  122. package/lib/types/ink/components/App.d.ts +2 -0
  123. package/lib/types/ink/components/App.d.ts.map +1 -1
  124. package/lib/types/ink/components/App.js +4 -0
  125. package/lib/types/ink/ink.d.ts +20 -3
  126. package/lib/types/ink/ink.d.ts.map +1 -1
  127. package/lib/types/ink/ink.js +57 -5
  128. package/lib/types/ink/log-update.d.ts.map +1 -1
  129. package/lib/types/ink/log-update.js +128 -9
  130. package/lib/types/ink/output.d.ts +28 -2
  131. package/lib/types/ink/output.d.ts.map +1 -1
  132. package/lib/types/ink/output.js +158 -12
  133. package/lib/types/ink/screen.d.ts +39 -0
  134. package/lib/types/ink/screen.d.ts.map +1 -1
  135. package/lib/types/ink/screen.js +117 -0
  136. package/lib/types/ink/terminal.d.ts.map +1 -1
  137. package/lib/types/ink/terminal.js +10 -2
  138. package/lib/types/ink/wrapAnsi.d.ts +1 -1
  139. package/lib/types/ink/wrapAnsi.d.ts.map +1 -1
  140. package/lib/types/ink/wrapAnsi.js +29 -2
  141. package/lib/types/plugin-host.d.ts +13 -11
  142. package/lib/types/plugin-host.d.ts.map +1 -1
  143. package/lib/types/plugin-host.js +16 -16
  144. package/lib/types/plugin-spec/registry.d.ts +2 -2
  145. package/lib/types/plugin-spec/registry.d.ts.map +1 -1
  146. package/lib/types/plugin-spec/registry.js +113 -18
  147. package/lib/types/plugin-spec/tui-extension.d.ts.map +1 -1
  148. package/lib/types/plugin-spec/tui-extension.js +2 -0
  149. package/lib/types/plugin-spec/validate.d.ts.map +1 -1
  150. package/lib/types/plugin-spec/validate.js +20 -1
  151. package/lib/types/scenes.d.ts +2 -1
  152. package/lib/types/scenes.d.ts.map +1 -1
  153. package/lib/types/scenes.js +1 -1
  154. package/lib/types/screens/Chat.d.ts +3 -3
  155. package/lib/types/screens/Chat.d.ts.map +1 -1
  156. package/lib/types/screens/Chat.js +38 -17
  157. package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
  158. package/lib/types/screens/SessionBrowser.js +10 -3
  159. package/lib/types/sessions/view.d.ts +3 -2
  160. package/lib/types/sessions/view.d.ts.map +1 -1
  161. package/lib/types/sessions/view.js +18 -0
  162. package/lib/types/settings-sections.d.ts +2 -1
  163. package/lib/types/settings-sections.d.ts.map +1 -1
  164. package/lib/types/settings-sections.js +1 -1
  165. package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
  166. package/lib/types/utils/execFileNoThrow.js +3 -0
  167. package/lib/types/workspaces.d.ts +2 -1
  168. package/lib/types/workspaces.d.ts.map +1 -1
  169. package/lib/types/workspaces.js +1 -1
  170. package/node_modules/@dsh-std/command/CHANGELOG.md +8 -0
  171. package/node_modules/@dsh-std/command/package.json +1 -1
  172. package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
  173. package/node_modules/@dsh-std/connection/lib/memory.d.ts +1 -1
  174. package/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -1
  175. package/node_modules/@dsh-std/connection/lib/memory.js +1 -0
  176. package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  177. package/node_modules/@dsh-std/connection/package.json +1 -1
  178. package/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  179. package/node_modules/@dsh-std/core/package.json +1 -1
  180. package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -0
  181. package/node_modules/@dsh-std/manifest/package.json +1 -1
  182. package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -0
  183. package/node_modules/@dsh-std/messages/package.json +1 -1
  184. package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -0
  185. package/node_modules/@dsh-std/presentation/README.md +2 -1
  186. package/node_modules/@dsh-std/presentation/README.zh.md +2 -1
  187. package/node_modules/@dsh-std/presentation/lib/callback.d.ts +2 -0
  188. package/node_modules/@dsh-std/presentation/lib/callback.js +179 -0
  189. package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -0
  190. package/node_modules/@dsh-std/presentation/lib/index.d.ts +44 -1
  191. package/node_modules/@dsh-std/presentation/lib/index.d.ts.map +1 -1
  192. package/node_modules/@dsh-std/presentation/lib/index.js +6 -3
  193. package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  194. package/node_modules/@dsh-std/presentation/package.json +7 -3
  195. package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -0
  196. package/node_modules/@dsh-std/storage/package.json +1 -1
  197. package/package.json +10 -5
  198. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
  199. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.d.ts +1 -1
  200. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -1
  201. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js +1 -0
  202. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  203. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +1 -1
  204. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  205. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +1 -1
  206. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -0
  207. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +1 -1
  208. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -0
  209. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +2 -1
  210. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +2 -1
  211. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.d.ts +2 -0
  212. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js +179 -0
  213. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -0
  214. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.d.ts +44 -1
  215. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.d.ts.map +1 -1
  216. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js +6 -3
  217. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  218. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +7 -3
  219. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  220. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +1 -1
  221. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  222. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +1 -1
  223. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  224. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +1 -1
  225. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
  226. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.d.ts +1 -1
  227. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -1
  228. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js +1 -0
  229. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  230. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +1 -1
  231. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  232. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +1 -1
  233. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  234. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +1 -1
  235. package/ecosystem-spec/registry/registry-0.15.json +0 -57
  236. /package/{ecosystem-spec → dsh-ecosystem-spec}/registry/contracts/decision-events-v1alpha1.json +0 -0
  237. /package/{ecosystem-spec → dsh-ecosystem-spec}/registry/host-descriptor.tui.example.json +0 -0
  238. /package/{ecosystem-spec → dsh-ecosystem-spec}/schemas/conformance-claim.schema.json +0 -0
  239. /package/{ecosystem-spec → dsh-ecosystem-spec}/schemas/effect-ledger-record.schema.json +0 -0
  240. /package/{ecosystem-spec → dsh-ecosystem-spec}/schemas/host-descriptor.schema.json +0 -0
@@ -32,7 +32,7 @@
32
32
  * per-contract at descriptor build time; boot must not die on drifted
33
33
  * vendored data).
34
34
  */
35
- import { Service } from '@deepseek-ai/cordis';
35
+ import { Context, Service } from '@deepseek-ai/cordis';
36
36
  import { randomUUID } from 'node:crypto';
37
37
  import { parseManifest, projectManifest } from '@dsh-std/manifest';
38
38
  import { loadSpecData, verifyContractProfiles, verifyRegistry } from '../plugin-spec/registry.js';
@@ -46,18 +46,23 @@ import { TuiMessageObserverRuntime } from './message-observer.js';
46
46
  import { stampCommandOwner, unstampCommandOwner } from './command-attribution.js';
47
47
  import { hasCommandErrorCode, mapCommandError } from './command-errors.js';
48
48
  import { installDecisionGuard, decisionHandlerMetadataOf, registerDecisionHandler, withDecisionRegistration, } from './decision-guard.js';
49
- import { bindComponentIdentity, declaresCommand, requiresDecisionEvents, requireComponentIdentity, } from './component-identity.js';
49
+ import { bindComponentIdentity, declaresCommand, requiresDecisionEvents, requiresContract, requireComponentIdentity, } from './component-identity.js';
50
+ import { activationContext, activationFiber, assertCallerContext, bindCallerEffect, compositionRoot, concreteService } from './host-access.js';
50
51
  /** `ctx.tuiPluginHost` — plugin-interop anchor (generation, grants, descriptor). */
51
52
  export class TuiPluginHostRuntime extends Service {
52
53
  /** Runtime generation id (C-050): fresh per activation of this row. */
53
- generationId = randomUUID();
54
+ get generationId() { return hostStateFor(this).generationId; }
54
55
  /** Live scoped grant store; every operation observes the current file. */
55
- grants;
56
- descriptorBuild;
57
- descriptorCommandsMounted;
56
+ get grants() { return hostStateFor(this).grants; }
58
57
  constructor(ctx) {
59
58
  super(ctx, 'tuiPluginHost');
60
- this.grants = readGrantStore();
59
+ hostStates.set(this, {
60
+ hostContext: compositionRoot(ctx),
61
+ generationId: randomUUID(),
62
+ grants: Object.freeze(readGrantStore()),
63
+ descriptorBuild: undefined,
64
+ descriptorTopology: undefined,
65
+ });
61
66
  // The host row may be mounted without the extensions row or a channel;
62
67
  // decision registration must remain mediated in that degraded topology.
63
68
  installDecisionGuard(ctx, this.grants);
@@ -79,30 +84,63 @@ export class TuiPluginHostRuntime extends Service {
79
84
  return this.build();
80
85
  }
81
86
  build() {
82
- // C-010 honesty: advertise Command only when the commands service is
83
- // actually mounted — a bare/embedded context without dsh-commands must
84
- // not negotiate `compatible` for a capability plugins will never get.
85
- // Probed on every descriptor request: the service can mount or unmount
86
- // after the first lazy build.
87
- const commandsMounted = this.ctx.get('commands') !== undefined;
88
- if (this.descriptorBuild === undefined || this.descriptorCommandsMounted !== commandsMounted) {
89
- const supported = HOST_SUPPORTED_CONTRACTS.filter(contract => contract.kind !== 'Command' || commandsMounted);
90
- this.descriptorBuild = buildHostDescriptor({ generationId: this.generationId, supported });
91
- this.descriptorCommandsMounted = commandsMounted;
87
+ const state = hostStateFor(this);
88
+ const host = state.hostContext;
89
+ // C-010 honesty: advertise a runtime capability only while its service is
90
+ // actually mounted. Probed on every descriptor request because sibling
91
+ // rows can mount after this service, and embedders can unload them later.
92
+ const commandsMounted = host.get('commands') !== undefined;
93
+ const storageMounted = host.get('tuiPluginStorage') !== undefined;
94
+ const observerMounted = host.get('tuiMessageObserver') !== undefined;
95
+ const topology = `${Number(commandsMounted)}:${Number(storageMounted)}:${Number(observerMounted)}`;
96
+ if (state.descriptorBuild === undefined || state.descriptorTopology !== topology) {
97
+ const supported = HOST_SUPPORTED_CONTRACTS.filter(contract => {
98
+ if (contract.kind === 'Command')
99
+ return commandsMounted;
100
+ if (contract.kind === 'LocalStorage')
101
+ return storageMounted;
102
+ if (contract.kind === 'MessageObserver')
103
+ return observerMounted;
104
+ return true;
105
+ });
106
+ state.descriptorBuild = buildHostDescriptor({ generationId: state.generationId, supported });
107
+ state.descriptorTopology = topology;
92
108
  if (!commandsMounted) {
93
- this.ctx.logger.warn('dsh-tui: host descriptor: commands.dsh/v1alpha1#Command excluded — the commands service is not mounted on this context');
109
+ host.logger.warn('dsh-tui: host descriptor: commands.dsh/v1alpha1#Command excluded — the commands service is not mounted on this context');
94
110
  }
95
- for (const warning of this.descriptorBuild.warnings) {
96
- this.ctx.logger.warn(`dsh-tui: host descriptor: ${warning}`);
111
+ if (!storageMounted) {
112
+ host.logger.warn('dsh-tui: host descriptor: storage.dsh/v1alpha1#LocalStorage excluded — the tuiPluginStorage service is not mounted on this context');
113
+ }
114
+ if (!observerMounted) {
115
+ host.logger.warn('dsh-tui: host descriptor: messages.dsh/v1alpha1#MessageObserver excluded — the tuiMessageObserver service is not mounted on this context');
116
+ }
117
+ for (const warning of state.descriptorBuild.warnings) {
118
+ host.logger.warn(`dsh-tui: host descriptor: ${warning}`);
97
119
  }
98
120
  }
99
- return this.descriptorBuild;
121
+ return state.descriptorBuild;
100
122
  }
101
123
  /**
102
124
  * Parse, project, validate, negotiate, and bind one activation's Component
103
125
  * identity before any mediated runtime capability can be used.
104
126
  */
105
127
  admit(pluginCtx, source, options = {}) {
128
+ // Admission binds an untrusted manifest to a privileged activation. A
129
+ // plugin-facing service proxy must not be able to choose another
130
+ // component's id and inherit its grants, so the loader uses the
131
+ // host-only accessor below instead of this public compatibility method.
132
+ void pluginCtx;
133
+ void source;
134
+ void options;
135
+ throw new Error('dsh-tui: admission is host-owned; the loader must use its admission capability');
136
+ }
137
+ /** @internal Host loader entry; the unexported token prevents proxy calls. */
138
+ admitInternal(pluginCtx, source, options = {}, token) {
139
+ if (token !== HOST_ADMISSION_TOKEN) {
140
+ throw new Error('dsh-tui: admission capability is host-owned');
141
+ }
142
+ const host = hostContextFor(this);
143
+ const caller = assertActivationContext(host, pluginCtx);
106
144
  const manifest = parseManifest(source, { source: options.source });
107
145
  // The host owns the activation instance identity. Embedders may provide a
108
146
  // pre-issued id when policy has an activation-scoped grant; otherwise a
@@ -111,6 +149,10 @@ export class TuiPluginHostRuntime extends Service {
111
149
  const data = loadSpecData();
112
150
  if (data === undefined)
113
151
  throw new Error('dsh-tui: admission profile is unavailable');
152
+ const specViolations = [...verifyRegistry(data), ...verifyContractProfiles(data)];
153
+ if (specViolations.length > 0) {
154
+ throw new Error(`dsh-tui: admission profile self-check failed: ${specViolations.join(' | ')}`);
155
+ }
114
156
  const index = createContractIndex(data.registry, data.permissions);
115
157
  validatePlugin(index, manifest);
116
158
  const projection = projectManifest(manifest);
@@ -118,13 +160,13 @@ export class TuiPluginHostRuntime extends Service {
118
160
  .map(request => ({
119
161
  name: request.name,
120
162
  scope: request.scope,
121
- granted: this.grants.allows({ componentId: manifest.id, activationId }, request.name, request.scope),
163
+ granted: hostStateFor(this).grants.allows({ componentId: manifest.id, activationId }, request.name, request.scope),
122
164
  }));
123
165
  const decision = negotiate(index, manifest, this.hostDescriptor(), grants);
124
166
  if (decision.decision !== 'compatible' && decision.decision !== 'compatible_degraded') {
125
167
  throw new Error(`dsh-tui: Component ${manifest.id} admission ${decision.decision}: ${'reasonCode' in decision ? decision.reasonCode : 'incompatible'}`);
126
168
  }
127
- return bindComponentIdentity(pluginCtx, manifest, projection, activationId);
169
+ return bindComponentIdentity(caller, manifest, projection, activationId);
128
170
  }
129
171
  /**
130
172
  * Host-mediated DecisionEvents activation surface. A plugin cannot use a
@@ -134,13 +176,16 @@ export class TuiPluginHostRuntime extends Service {
134
176
  * the activation so deactivation cannot leave an effect behind.
135
177
  */
136
178
  subscribeDecision(pluginCtx, event, listener, options = {}) {
137
- const identity = requireComponentIdentity(pluginCtx);
179
+ const host = hostContextFor(this);
180
+ const caller = assertActivationContext(host, pluginCtx);
181
+ assertCallerContext(this.ctx, caller, 'DecisionEvents.subscribe', this);
182
+ const identity = requireComponentIdentity(caller);
138
183
  if (!requiresDecisionEvents(identity)) {
139
184
  throw new Error(`dsh-tui: Component "${identity.componentId}" must require tui.dsh/v1alpha1#DecisionEvents before subscribing`);
140
185
  }
141
186
  const previousMetadata = decisionHandlerMetadataOf(listener);
142
- const release = withDecisionRegistration(pluginCtx, () => registerDecisionHandler(pluginCtx, identity, event, listener, options, () => {
143
- this.ctx.get('tuiEffectLedger')?.record({ operation: 'release', resource: { kind: 'decision-handler', id: `${identity.componentId}:${event}` }, result: 'applied' }, pluginCtx);
187
+ const release = withDecisionRegistration(caller, () => registerDecisionHandler(caller, identity, event, listener, options, () => {
188
+ host.get('tuiEffectLedger')?.record({ operation: 'release', resource: { kind: 'decision-handler', id: `${identity.componentId}:${event}` }, result: 'applied' }, caller);
144
189
  }));
145
190
  // A missing live grant is represented by a no-op disposer rather than an
146
191
  // exception. Do not write a successful bind record for that path; the
@@ -151,26 +196,32 @@ export class TuiPluginHostRuntime extends Service {
151
196
  && metadata.activationId === identity.activationId
152
197
  && metadata.event === event;
153
198
  if (!registered) {
154
- this.ctx.get('tuiEffectLedger')?.record({
199
+ host.get('tuiEffectLedger')?.record({
155
200
  operation: 'bind',
156
201
  resource: { kind: 'permission', id: `${event}` },
157
202
  result: 'failed',
158
203
  errorCode: 'PERMISSION_NOT_GRANTED',
159
- }, pluginCtx);
204
+ }, caller);
160
205
  return () => false;
161
206
  }
162
- this.ctx.get('tuiEffectLedger')?.record({ operation: 'bind', resource: { kind: 'decision-handler', id: `${identity.componentId}:${event}` }, result: 'applied' }, pluginCtx);
207
+ host.get('tuiEffectLedger')?.record({ operation: 'bind', resource: { kind: 'decision-handler', id: `${identity.componentId}:${event}` }, result: 'applied' }, caller);
163
208
  return release;
164
209
  }
165
210
  registerCommand(pluginCtx, contributionOrDefinition, explicitDefinition) {
211
+ const host = hostContextFor(this);
212
+ const caller = assertActivationContext(host, pluginCtx);
213
+ assertCallerContext(this.ctx, caller, 'commands.register', this);
166
214
  // Resolve through the registrant context so dsh-commands attaches the
167
215
  // registration to that agent's scoped layer rather than the host-global
168
216
  // layer. Its traceable service proxy carries this context into register().
169
- const commands = pluginCtx.get('commands');
217
+ const commands = caller.get('commands');
170
218
  if (commands === undefined) {
171
219
  throw new Error('dsh-tui: registerCommand unavailable — the commands service is not mounted on this context');
172
220
  }
173
- const identity = requireComponentIdentity(pluginCtx);
221
+ const identity = requireComponentIdentity(caller);
222
+ if (!requiresContract(identity, 'commands.dsh/v1alpha1', 'Command')) {
223
+ throw new Error(`dsh-tui: Component "${identity.componentId}" must require commands.dsh/v1alpha1#Command before registering commands`);
224
+ }
174
225
  const definition = typeof contributionOrDefinition === 'string' ? explicitDefinition : contributionOrDefinition;
175
226
  if (definition === undefined)
176
227
  throw new TypeError('dsh-tui: registerCommand requires a command definition');
@@ -201,33 +252,28 @@ export class TuiPluginHostRuntime extends Service {
201
252
  }
202
253
  catch (error) {
203
254
  const mapped = mapCommandError(error);
204
- this.ctx.get('tuiEffectLedger')?.record({
255
+ host.get('tuiEffectLedger')?.record({
205
256
  operation: 'create',
206
257
  resource: { kind: 'command', id: contributionId },
207
258
  result: 'failed',
208
259
  errorCode: hasCommandErrorCode(mapped, 'DUPLICATE_CONTRIBUTION_ID') ? 'DUPLICATE_CONTRIBUTION_ID' : 'COMMAND_FAILED',
209
- }, pluginCtx);
260
+ }, caller);
210
261
  throw mapped;
211
262
  }
212
- stampCommandOwner(this.ctx, attributedDefinition, identity, contributionId);
213
- this.ctx.get('tuiEffectLedger')?.record({ operation: 'create', resource: { kind: 'command', id: contributionId }, result: 'applied' }, pluginCtx);
263
+ stampCommandOwner(host, attributedDefinition, identity, contributionId);
264
+ host.get('tuiEffectLedger')?.record({ operation: 'create', resource: { kind: 'command', id: contributionId }, result: 'applied' }, caller);
214
265
  let released = false;
215
266
  const release = () => {
216
267
  if (released)
217
268
  return;
218
269
  released = true;
219
- unstampCommandOwner(this.ctx, attributedDefinition, identity.activationId);
220
- this.ctx.get('tuiEffectLedger')?.record({ operation: 'release', resource: { kind: 'command', id: contributionId }, result: 'applied' }, pluginCtx);
270
+ unstampCommandOwner(host, attributedDefinition, identity.activationId);
271
+ host.get('tuiEffectLedger')?.record({ operation: 'release', resource: { kind: 'command', id: contributionId }, result: 'applied' }, caller);
221
272
  };
222
273
  // commands.register() already belongs to pluginCtx and therefore removes
223
274
  // the definition on fiber teardown. Keep the attribution in that same
224
275
  // lifecycle even when the caller never invokes our returned wrapper.
225
- try {
226
- pluginCtx.effect(() => release);
227
- }
228
- catch {
229
- // Degraded contexts retain the wrapper-only cleanup path below.
230
- }
276
+ bindCallerEffect(caller, release);
231
277
  let disposed = false;
232
278
  return () => {
233
279
  if (disposed)
@@ -249,10 +295,59 @@ export class TuiPluginHostRuntime extends Service {
249
295
  selfCheck() {
250
296
  const data = loadSpecData();
251
297
  if (data === undefined)
252
- return ['vendored spec data unavailable (ecosystem-spec/)'];
298
+ return ['vendored spec data unavailable (dsh-ecosystem-spec/)'];
253
299
  return [...verifyRegistry(data), ...verifyContractProfiles(data)];
254
300
  }
255
301
  }
302
+ const HOST_ADMISSION_TOKEN = Symbol('dsh-tui.host-admission');
303
+ const hostStates = new WeakMap();
304
+ function hostStateFor(runtime) {
305
+ const state = hostStates.get(concreteService(runtime));
306
+ if (state === undefined)
307
+ throw new Error('tuiPluginHost host state is unavailable');
308
+ return state;
309
+ }
310
+ function hostContextFor(runtime) {
311
+ return hostStateFor(runtime).hostContext;
312
+ }
313
+ export function getHostAdmission(runtime) {
314
+ if (runtime === undefined)
315
+ return undefined;
316
+ try {
317
+ const concrete = concreteService(runtime);
318
+ return {
319
+ admit: (pluginCtx, source, options = {}) => concrete.admitInternal(pluginCtx, source, options, HOST_ADMISSION_TOKEN),
320
+ };
321
+ }
322
+ catch {
323
+ return undefined;
324
+ }
325
+ }
326
+ /** A mediated capability must be bound to a live non-root activation in the
327
+ * same Cordis composition as the host row. In particular, accepting
328
+ * `ctx.root` would leave identity, commands, or handlers alive after the
329
+ * plugin fiber unloads. */
330
+ function assertActivationContext(hostCtx, pluginCtx) {
331
+ try {
332
+ const root = compositionRoot(hostCtx);
333
+ const caller = Context.is(pluginCtx) ? activationContext(pluginCtx) : undefined;
334
+ const rootFiber = activationFiber(root);
335
+ const callerFiber = caller === undefined ? undefined : activationFiber(caller);
336
+ if (caller === undefined
337
+ || compositionRoot(caller) !== root
338
+ || caller === root
339
+ || callerFiber === undefined
340
+ || callerFiber === rootFiber) {
341
+ throw new Error('dsh-tui: mediated capability requires a non-root activation context from the host composition');
342
+ }
343
+ return caller;
344
+ }
345
+ catch (error) {
346
+ if (error instanceof Error && error.message.startsWith('dsh-tui:'))
347
+ throw error;
348
+ throw new Error('dsh-tui: mediated capability requires a live activation context');
349
+ }
350
+ }
256
351
  export const name = 'dsh-tui-plugin-host';
257
352
  export function apply(ctx) {
258
353
  // The plugin-host service first — the contract surfaces mounted below
@@ -85,11 +85,6 @@ export declare function storageFileName(plugin: string): string;
85
85
  * (normal path) or a private read otherwise (bare mounts in tests).
86
86
  */
87
87
  export declare class TuiPluginStorageRuntime extends Service {
88
- private readonly grantsOption;
89
- private readonly fallbackGrants;
90
- private readonly ledgerOption;
91
- private readonly namespaces;
92
- private readonly dir;
93
88
  constructor(ctx: Context, options?: {
94
89
  dir?: string;
95
90
  grants?: GrantStore;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-storage.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/plugin-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAKH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAWtD,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAEhE,8CAA8C;AAC9C,eAAO,MAAM,gBAAgB,MAAM,CAAA;AACnC,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,QAAa,CAAA;AAC3C,mCAAmC;AACnC,eAAO,MAAM,sBAAsB,MAAM,CAAA;AAEzC,MAAM,MAAM,sBAAsB,GAC9B,wBAAwB,GACxB,aAAa,GACb,eAAe,GACf,gBAAgB,GAChB,qBAAqB,CAAA;AAEzB,iEAAiE;AACjE,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAA;gBACzB,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM;CAK1D;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC,CAAA;IAC/D,GAAG,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC,CAAA;IACtE,MAAM,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CAC9D;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,gBAAgB,EAAE,uBAAuB,CAAA;KAC1C;CACF;AAED,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,mBAAmB,CAAA;AAElD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGtD;AAgID;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,OAAO;IAClD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwB;IACrD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAY;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoC;IACjE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoC;IAC/D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;gBAEhB,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,UAAU,CAAC;QAAC,MAAM,CAAC,EAAE,sBAAsB,CAAA;KAAO;IAQ9G;;;gDAG4C;IAC5C,OAAO,CAAC,MAAM;IAId,2EAA2E;IAC3E,OAAO,CAAC,MAAM;IAId;;;;;OAKG;IACH,IAAI,CAAC,SAAS,EAAE,OAAO,GAAG,gBAAgB;CA2K3C"}
1
+ {"version":3,"file":"plugin-storage.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/plugin-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAKH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAYtD,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAEhE,8CAA8C;AAC9C,eAAO,MAAM,gBAAgB,MAAM,CAAA;AACnC,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,QAAa,CAAA;AAC3C,mCAAmC;AACnC,eAAO,MAAM,sBAAsB,MAAM,CAAA;AAEzC,MAAM,MAAM,sBAAsB,GAC9B,wBAAwB,GACxB,aAAa,GACb,eAAe,GACf,gBAAgB,GAChB,qBAAqB,CAAA;AAEzB,iEAAiE;AACjE,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAA;gBACzB,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM;CAK1D;AAmBD,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC,CAAA;IAC/D,GAAG,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC,CAAA;IACtE,MAAM,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CAC9D;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,gBAAgB,EAAE,uBAAuB,CAAA;KAC1C;CACF;AAED,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,mBAAmB,CAAA;AAElD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGtD;AAgID;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,OAAO;gBACtC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,UAAU,CAAC;QAAC,MAAM,CAAC,EAAE,sBAAsB,CAAA;KAAO;IAY9G;;;gDAG4C;IAC5C,OAAO,CAAC,MAAM;IAKd,2EAA2E;IAC3E,OAAO,CAAC,MAAM;IAKd;;;;;OAKG;IACH,IAAI,CAAC,SAAS,EAAE,OAAO,GAAG,gBAAgB;CAiL3C"}
@@ -38,7 +38,8 @@ import { writeFileAtomic, withFileLock } from '@deepseek-ai/dsh-atomic-write';
38
38
  import { Service } from '@deepseek-ai/cordis';
39
39
  import { validateDeleteInput, validateGetInput, validateSetInput, validateGetOutput, validateSetOutput, validateDeleteOutput, } from '@dsh-std/storage';
40
40
  import { DATA_DIR } from '../utils/paths.js';
41
- import { declaresPermission, requireComponentIdentity } from './component-identity.js';
41
+ import { activationContext, assertCallerContext, bindCallerEffect, compositionRoot, concreteService } from './host-access.js';
42
+ import { declaresPermission, requireComponentIdentity, requiresContract } from './component-identity.js';
42
43
  import { readGrantStore } from './grants.js';
43
44
  /** Quota: max distinct keys per namespace. */
44
45
  export const STORAGE_MAX_KEYS = 256;
@@ -55,6 +56,13 @@ export class PluginStorageError extends Error {
55
56
  this.code = code;
56
57
  }
57
58
  }
59
+ const storageStates = new WeakMap();
60
+ function storageStateFor(runtime) {
61
+ const state = storageStates.get(concreteService(runtime));
62
+ if (state === undefined)
63
+ throw new Error('tuiPluginStorage host state is unavailable');
64
+ return state;
65
+ }
58
66
  /** Namespace directory under the data dir. */
59
67
  export const PLUGIN_STORAGE_DIR = 'plugin-storage';
60
68
  /**
@@ -203,28 +211,29 @@ function isJsonValue(value, seen) {
203
211
  * (normal path) or a private read otherwise (bare mounts in tests).
204
212
  */
205
213
  export class TuiPluginStorageRuntime extends Service {
206
- grantsOption;
207
- fallbackGrants;
208
- ledgerOption;
209
- namespaces = new Map();
210
- dir;
211
214
  constructor(ctx, options = {}) {
212
215
  super(ctx, 'tuiPluginStorage');
213
- this.grantsOption = options.grants;
214
- this.fallbackGrants = readGrantStore();
215
- this.ledgerOption = options.ledger;
216
- this.dir = options.dir ?? join(DATA_DIR, PLUGIN_STORAGE_DIR);
216
+ storageStates.set(this, {
217
+ hostContext: compositionRoot(ctx),
218
+ grantsOption: options.grants,
219
+ fallbackGrants: readGrantStore(),
220
+ ledgerOption: options.ledger,
221
+ namespaces: new Map(),
222
+ dir: options.dir ?? join(DATA_DIR, PLUGIN_STORAGE_DIR),
223
+ });
217
224
  }
218
225
  /** Grants: the plugin-host row's store when mounted, else a private read.
219
226
  * Resolved PER CALL — sibling services mounted later by the same apply()
220
227
  * are not visible to constructors (cordis), so a constructor-time probe
221
228
  * would silently stick to the fallback. */
222
229
  grants() {
223
- return this.grantsOption ?? this.ctx.get('tuiPluginHost')?.grants ?? this.fallbackGrants;
230
+ const state = storageStateFor(this);
231
+ return state.grantsOption ?? state.hostContext.get('tuiPluginHost')?.grants ?? state.fallbackGrants;
224
232
  }
225
233
  /** Optional observability; a bare mount (tests) simply records nothing. */
226
234
  ledger() {
227
- return this.ledgerOption ?? this.ctx.get('tuiEffectLedger');
235
+ const state = storageStateFor(this);
236
+ return state.ledgerOption ?? state.hostContext.get('tuiEffectLedger');
228
237
  }
229
238
  /**
230
239
  * Open the caller's private namespace. Identity = the PASSED context's
@@ -233,40 +242,43 @@ export class TuiPluginStorageRuntime extends Service {
233
242
  * disposer); data is retained (contract cleanup rule).
234
243
  */
235
244
  open(pluginCtx) {
236
- const identity = requireComponentIdentity(pluginCtx);
245
+ const caller = activationContext(pluginCtx);
246
+ if (caller === undefined)
247
+ throw new PluginStorageError('STORAGE_UNAVAILABLE', 'storage.local.open requires a live activation context');
248
+ assertCallerContext(this.ctx, caller, 'storage.local.open', this);
249
+ const state = storageStateFor(this);
250
+ const identity = requireComponentIdentity(caller);
251
+ if (!requiresContract(identity, 'storage.dsh/v1alpha1', 'LocalStorage')) {
252
+ throw new PluginStorageError('PERMISSION_NOT_GRANTED', 'storage.local requires the storage.dsh/v1alpha1#LocalStorage contract in the admitted manifest');
253
+ }
237
254
  const plugin = identity.componentId;
238
- let state = this.namespaces.get(plugin);
239
- if (state === undefined) {
240
- state = { chain: Promise.resolve() };
241
- this.namespaces.set(plugin, state);
255
+ let namespace = state.namespaces.get(plugin);
256
+ if (namespace === undefined) {
257
+ namespace = { chain: Promise.resolve() };
258
+ state.namespaces.set(plugin, namespace);
242
259
  }
243
- this.ledger()?.record({ operation: 'create', resource: { kind: 'storage-namespace', id: plugin }, result: 'applied' }, pluginCtx);
260
+ this.ledger()?.record({ operation: 'create', resource: { kind: 'storage-namespace', id: plugin }, result: 'applied' }, caller);
244
261
  // `closed` is PER HANDLE, not per namespace: unloading one fiber must not
245
262
  // kill another handle opened on the same namespace. Close on unload;
246
- // idempotent (a double-close stays harmless by design); degraded contexts
247
- // without `effect` simply never auto-close.
263
+ // idempotent (a double-close stays harmless by design).
248
264
  let closed = false;
249
- try {
250
- pluginCtx.effect(() => () => {
251
- if (closed)
252
- return;
253
- closed = true;
254
- this.ledger()?.record({ operation: 'release', resource: { kind: 'storage-namespace', id: plugin }, result: 'applied' }, pluginCtx);
255
- });
256
- }
257
- catch {
258
- // Degraded context: the handle lives until process end.
259
- }
260
- const file = join(this.dir, `${storageFileName(plugin)}.json`);
265
+ const close = () => {
266
+ if (closed)
267
+ return;
268
+ closed = true;
269
+ this.ledger()?.record({ operation: 'release', resource: { kind: 'storage-namespace', id: plugin }, result: 'applied' }, caller);
270
+ };
271
+ bindCallerEffect(caller, close);
272
+ const file = join(state.dir, `${storageFileName(plugin)}.json`);
261
273
  const grants = () => this.grants();
262
274
  const ledger = () => this.ledger();
263
275
  const enqueue = (operation) => {
264
276
  if (closed) {
265
277
  return Promise.reject(new PluginStorageError('STORAGE_UNAVAILABLE', `storage namespace "${plugin}" handle is closed`));
266
278
  }
267
- const run = state.chain.then(operation);
279
+ const run = namespace.chain.then(operation);
268
280
  // Keep the chain alive after a failure without unhandled rejections.
269
- state.chain = run.catch(() => { });
281
+ namespace.chain = run.catch(() => { });
270
282
  return run;
271
283
  };
272
284
  const readTable = () => {
@@ -306,7 +318,7 @@ export class TuiPluginStorageRuntime extends Service {
306
318
  resource: { kind: 'permission', id: permission },
307
319
  result: 'failed',
308
320
  errorCode: 'PERMISSION_NOT_GRANTED',
309
- }, pluginCtx);
321
+ }, caller);
310
322
  throw new PluginStorageError('PERMISSION_NOT_GRANTED', `storage.${permission.endsWith('.read') ? 'get' : 'set/delete'} from plugin "${plugin}" denied — grant "${permission}" ` +
311
323
  `for "${plugin}" in ~/.dsh-tui/extension-grants.json first`);
312
324
  }
@@ -316,7 +328,7 @@ export class TuiPluginStorageRuntime extends Service {
316
328
  // path (read paths tolerate ENOENT as an empty namespace and never
317
329
  // materialize the tree).
318
330
  const ensureDir = () => {
319
- mkdirSync(this.dir, { recursive: true, mode: 0o700 });
331
+ mkdirSync(state.dir, { recursive: true, mode: 0o700 });
320
332
  };
321
333
  return {
322
334
  get: (input) => enqueue(async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,wBAAwB,CAAA;AAGhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAiCnC;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6iBvE;AA6GD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG;IACjF,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,YAAY,EAAE,MAAM,IAAI,CAAA;CACzB,CAcA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,KAAK,GAAG,SAAS,CAAA;IAC5B,YAAY,EAAE,KAAK,CAAA;CACpB,GAAG,OAAO,CAQV"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,wBAAwB,CAAA;AAGhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAmCnC;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmmBvE;AA6GD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG;IACjF,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,YAAY,EAAE,MAAM,IAAI,CAAA;CACzB,CAcA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,KAAK,GAAG,SAAS,CAAA;IAC5B,YAAY,EAAE,KAAK,CAAA;CACpB,GAAG,OAAO,CAQV"}