@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.
- package/README.md +22 -13
- package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -0
- package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -0
- package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -0
- package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -0
- package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -0
- package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -0
- package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -0
- package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -0
- package/dsh-ecosystem-spec/registry/README.md +14 -0
- package/dsh-ecosystem-spec/registry/contracts/scene-v1alpha1.json +13 -0
- package/dsh-ecosystem-spec/registry/contracts/settings-section-v1alpha1.json +13 -0
- package/dsh-ecosystem-spec/registry/contracts/tui-channel-v1alpha1.json +30 -0
- package/dsh-ecosystem-spec/registry/contracts/workspace-provider-v1alpha1.json +6 -0
- package/{ecosystem-spec → dsh-ecosystem-spec}/registry/permissions-0.1.json +1 -1
- package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -0
- package/lib/types/command-trees.d.ts +2 -1
- package/lib/types/command-trees.d.ts.map +1 -1
- package/lib/types/command-trees.js +1 -1
- package/lib/types/components/LoadedContextPanel.d.ts +10 -5
- package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
- package/lib/types/components/LoadedContextPanel.js +18 -7
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +16 -7
- package/lib/types/components/Markdown.d.ts +13 -1
- package/lib/types/components/Markdown.d.ts.map +1 -1
- package/lib/types/components/Markdown.js +15 -1
- package/lib/types/components/MessageList.d.ts +3 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +90 -9
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +26 -12
- package/lib/types/components/StreamingMarkdown.d.ts +2 -8
- package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
- package/lib/types/components/StreamingMarkdown.js +51 -9
- package/lib/types/components/activityFrames.d.ts +1 -1
- package/lib/types/components/activityFrames.d.ts.map +1 -1
- package/lib/types/components/activityFrames.js +4 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts +5 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +26 -3
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +12 -1
- package/lib/types/dsh-adapter/channel.d.ts +31 -4
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +267 -102
- package/lib/types/dsh-adapter/childStderr.d.ts.map +1 -1
- package/lib/types/dsh-adapter/childStderr.js +10 -0
- package/lib/types/dsh-adapter/command-attribution.d.ts.map +1 -1
- package/lib/types/dsh-adapter/command-attribution.js +2 -2
- package/lib/types/dsh-adapter/command-trees.d.ts +7 -1
- package/lib/types/dsh-adapter/command-trees.d.ts.map +1 -1
- package/lib/types/dsh-adapter/command-trees.js +70 -20
- package/lib/types/dsh-adapter/component-identity.d.ts +5 -0
- package/lib/types/dsh-adapter/component-identity.d.ts.map +1 -1
- package/lib/types/dsh-adapter/component-identity.js +14 -14
- package/lib/types/dsh-adapter/decision-guard.d.ts.map +1 -1
- package/lib/types/dsh-adapter/decision-guard.js +10 -16
- package/lib/types/dsh-adapter/dialogs.d.ts +2 -3
- package/lib/types/dsh-adapter/dialogs.d.ts.map +1 -1
- package/lib/types/dsh-adapter/dialogs.js +114 -60
- package/lib/types/dsh-adapter/effect-ledger.d.ts +0 -8
- package/lib/types/dsh-adapter/effect-ledger.d.ts.map +1 -1
- package/lib/types/dsh-adapter/effect-ledger.js +73 -53
- package/lib/types/dsh-adapter/extension-events.d.ts.map +1 -1
- package/lib/types/dsh-adapter/extension-events.js +39 -1
- package/lib/types/dsh-adapter/grants.d.ts.map +1 -1
- package/lib/types/dsh-adapter/grants.js +43 -14
- package/lib/types/dsh-adapter/host-access.d.ts +37 -0
- package/lib/types/dsh-adapter/host-access.d.ts.map +1 -0
- package/lib/types/dsh-adapter/host-access.js +652 -0
- package/lib/types/dsh-adapter/host-descriptor.d.ts +2 -2
- package/lib/types/dsh-adapter/host-descriptor.d.ts.map +1 -1
- package/lib/types/dsh-adapter/host-descriptor.js +31 -8
- package/lib/types/dsh-adapter/index.d.ts +4 -0
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +1 -0
- package/lib/types/dsh-adapter/message-observer.d.ts +10 -15
- package/lib/types/dsh-adapter/message-observer.d.ts.map +1 -1
- package/lib/types/dsh-adapter/message-observer.js +114 -70
- package/lib/types/dsh-adapter/plugin-host.d.ts +39 -6
- package/lib/types/dsh-adapter/plugin-host.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin-host.js +139 -44
- package/lib/types/dsh-adapter/plugin-storage.d.ts +0 -5
- package/lib/types/dsh-adapter/plugin-storage.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin-storage.js +48 -36
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +80 -16
- package/lib/types/dsh-adapter/promptDebug.d.ts +0 -6
- package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -1
- package/lib/types/dsh-adapter/promptDebug.js +13 -4
- package/lib/types/dsh-adapter/renderers.d.ts +6 -14
- package/lib/types/dsh-adapter/renderers.d.ts.map +1 -1
- package/lib/types/dsh-adapter/renderers.js +81 -46
- package/lib/types/dsh-adapter/scenes.d.ts +8 -4
- package/lib/types/dsh-adapter/scenes.d.ts.map +1 -1
- package/lib/types/dsh-adapter/scenes.js +136 -37
- package/lib/types/dsh-adapter/settings-sections.d.ts +7 -3
- package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
- package/lib/types/dsh-adapter/settings-sections.js +106 -26
- package/lib/types/dsh-adapter/shortcuts.d.ts +8 -10
- package/lib/types/dsh-adapter/shortcuts.d.ts.map +1 -1
- package/lib/types/dsh-adapter/shortcuts.js +108 -36
- package/lib/types/dsh-adapter/status.d.ts +6 -7
- package/lib/types/dsh-adapter/status.d.ts.map +1 -1
- package/lib/types/dsh-adapter/status.js +88 -22
- package/lib/types/dsh-adapter/workspaces.d.ts +16 -5
- package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -1
- package/lib/types/dsh-adapter/workspaces.js +287 -170
- package/lib/types/extensions.d.ts +10 -5
- package/lib/types/extensions.d.ts.map +1 -1
- package/lib/types/extensions.js +11 -5
- package/lib/types/force-production-react.d.ts +1 -0
- package/lib/types/force-production-react.d.ts.map +1 -0
- package/lib/types/force-production-react.js +12 -0
- package/lib/types/i18n.d.ts +14 -6
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +4 -2
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1 -0
- package/lib/types/ink/components/App.d.ts +2 -0
- package/lib/types/ink/components/App.d.ts.map +1 -1
- package/lib/types/ink/components/App.js +4 -0
- package/lib/types/ink/ink.d.ts +20 -3
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +57 -5
- package/lib/types/ink/log-update.d.ts.map +1 -1
- package/lib/types/ink/log-update.js +128 -9
- package/lib/types/ink/output.d.ts +28 -2
- package/lib/types/ink/output.d.ts.map +1 -1
- package/lib/types/ink/output.js +158 -12
- package/lib/types/ink/screen.d.ts +39 -0
- package/lib/types/ink/screen.d.ts.map +1 -1
- package/lib/types/ink/screen.js +117 -0
- package/lib/types/ink/terminal.d.ts.map +1 -1
- package/lib/types/ink/terminal.js +10 -2
- package/lib/types/ink/wrapAnsi.d.ts +1 -1
- package/lib/types/ink/wrapAnsi.d.ts.map +1 -1
- package/lib/types/ink/wrapAnsi.js +29 -2
- package/lib/types/plugin-host.d.ts +13 -11
- package/lib/types/plugin-host.d.ts.map +1 -1
- package/lib/types/plugin-host.js +16 -16
- package/lib/types/plugin-spec/registry.d.ts +2 -2
- package/lib/types/plugin-spec/registry.d.ts.map +1 -1
- package/lib/types/plugin-spec/registry.js +113 -18
- package/lib/types/plugin-spec/tui-extension.d.ts.map +1 -1
- package/lib/types/plugin-spec/tui-extension.js +2 -0
- package/lib/types/plugin-spec/validate.d.ts.map +1 -1
- package/lib/types/plugin-spec/validate.js +20 -1
- package/lib/types/scenes.d.ts +2 -1
- package/lib/types/scenes.d.ts.map +1 -1
- package/lib/types/scenes.js +1 -1
- package/lib/types/screens/Chat.d.ts +3 -3
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +38 -17
- package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
- package/lib/types/screens/SessionBrowser.js +10 -3
- package/lib/types/sessions/view.d.ts +3 -2
- package/lib/types/sessions/view.d.ts.map +1 -1
- package/lib/types/sessions/view.js +18 -0
- package/lib/types/settings-sections.d.ts +2 -1
- package/lib/types/settings-sections.d.ts.map +1 -1
- package/lib/types/settings-sections.js +1 -1
- package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
- package/lib/types/utils/execFileNoThrow.js +3 -0
- package/lib/types/workspaces.d.ts +2 -1
- package/lib/types/workspaces.d.ts.map +1 -1
- package/lib/types/workspaces.js +1 -1
- package/node_modules/@dsh-std/command/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/command/package.json +1 -1
- package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/connection/lib/memory.d.ts +1 -1
- package/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -1
- package/node_modules/@dsh-std/connection/lib/memory.js +1 -0
- package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/node_modules/@dsh-std/connection/package.json +1 -1
- package/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/core/package.json +1 -1
- package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -0
- package/node_modules/@dsh-std/manifest/package.json +1 -1
- package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/messages/package.json +1 -1
- package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -0
- package/node_modules/@dsh-std/presentation/README.md +2 -1
- package/node_modules/@dsh-std/presentation/README.zh.md +2 -1
- package/node_modules/@dsh-std/presentation/lib/callback.d.ts +2 -0
- package/node_modules/@dsh-std/presentation/lib/callback.js +179 -0
- package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -0
- package/node_modules/@dsh-std/presentation/lib/index.d.ts +44 -1
- package/node_modules/@dsh-std/presentation/lib/index.d.ts.map +1 -1
- package/node_modules/@dsh-std/presentation/lib/index.js +6 -3
- package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/presentation/package.json +7 -3
- package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -0
- package/node_modules/@dsh-std/storage/package.json +1 -1
- package/package.json +10 -5
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.d.ts +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +2 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +2 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.d.ts +2 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js +179 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.d.ts +44 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.d.ts.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js +6 -3
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +7 -3
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +1 -1
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +1 -1
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.d.ts +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js +1 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +1 -1
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +1 -1
- package/ecosystem-spec/registry/registry-0.15.json +0 -57
- /package/{ecosystem-spec → dsh-ecosystem-spec}/registry/contracts/decision-events-v1alpha1.json +0 -0
- /package/{ecosystem-spec → dsh-ecosystem-spec}/registry/host-descriptor.tui.example.json +0 -0
- /package/{ecosystem-spec → dsh-ecosystem-spec}/schemas/conformance-claim.schema.json +0 -0
- /package/{ecosystem-spec → dsh-ecosystem-spec}/schemas/effect-ledger-record.schema.json +0 -0
- /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
|
|
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
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
// Probed on every descriptor request
|
|
86
|
-
// after
|
|
87
|
-
const commandsMounted =
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
204
|
+
}, caller);
|
|
160
205
|
return () => false;
|
|
161
206
|
}
|
|
162
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
-
},
|
|
260
|
+
}, caller);
|
|
210
261
|
throw mapped;
|
|
211
262
|
}
|
|
212
|
-
stampCommandOwner(
|
|
213
|
-
|
|
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(
|
|
220
|
-
|
|
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
|
-
|
|
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;
|
|
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 {
|
|
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
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
|
|
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' },
|
|
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)
|
|
247
|
-
// without `effect` simply never auto-close.
|
|
263
|
+
// idempotent (a double-close stays harmless by design).
|
|
248
264
|
let closed = false;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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 =
|
|
279
|
+
const run = namespace.chain.then(operation);
|
|
268
280
|
// Keep the chain alive after a failure without unhandled rejections.
|
|
269
|
-
|
|
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
|
-
},
|
|
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(
|
|
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;
|
|
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"}
|