@unoverse-platform/studio 0.1.90 → 0.1.92
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/local/plugin.ts +21 -65
- package/package.json +13 -3
- package/vendor/base/src/agent-mcp/discovery.ts +203 -0
- package/vendor/base/src/agent-mcp/handoff.ts +56 -0
- package/vendor/base/src/agent-mcp/index.ts +44 -0
- package/vendor/base/src/agent-mcp/invoke.ts +153 -0
- package/vendor/base/src/agent-mcp/lean.ts +114 -0
- package/vendor/base/src/agent-mcp/memoryContext.ts +104 -0
- package/vendor/base/src/agent-mcp/memoryIngest.ts +88 -0
- package/vendor/base/src/agent-mcp/rowComponents.ts +234 -0
- package/vendor/base/src/agent-mcp/tools.ts +134 -0
- package/vendor/base/src/agent-mcp/types.ts +138 -0
- package/vendor/base/src/auth/auth.ts +307 -0
- package/vendor/base/src/auth/authConfig.ts +73 -0
- package/vendor/base/src/boot.ts +245 -0
- package/vendor/base/src/definitions/definitions.ts +18 -0
- package/vendor/base/src/definitions/dsPackage.ts +45 -0
- package/vendor/base/src/definitions/fsCache.ts +155 -0
- package/vendor/base/src/definitions/index.ts +19 -0
- package/vendor/base/src/definitions/inputs.ts +30 -0
- package/vendor/base/src/definitions/load/compile/component.ts +80 -0
- package/vendor/base/src/definitions/load/compile/index.ts +12 -0
- package/vendor/base/src/definitions/load/compile/sections.ts +132 -0
- package/vendor/base/src/definitions/load/compile/template.ts +153 -0
- package/vendor/base/src/definitions/load/expand/includes.ts +27 -0
- package/vendor/base/src/definitions/load/expand/index.ts +17 -0
- package/vendor/base/src/definitions/load/expand/inherit.ts +169 -0
- package/vendor/base/src/definitions/load/expand/referenced.ts +164 -0
- package/vendor/base/src/definitions/load/expand/refs.ts +106 -0
- package/vendor/base/src/definitions/load/expand/walk.ts +91 -0
- package/vendor/base/src/definitions/load/load.ts +487 -0
- package/vendor/base/src/definitions/model.ts +238 -0
- package/vendor/base/src/definitions/registry/catalog.ts +659 -0
- package/vendor/base/src/definitions/registry/homes.ts +252 -0
- package/vendor/base/src/definitions/theme.ts +383 -0
- package/vendor/base/src/executor.ts +284 -0
- package/vendor/base/src/http/uwsFetchAdapter.ts +397 -0
- package/vendor/base/src/items/baseVersion.ts +96 -0
- package/vendor/base/src/items/collect.ts +246 -0
- package/vendor/base/src/items/fingerprint.ts +34 -0
- package/vendor/base/src/items/publish.ts +212 -0
- package/vendor/base/src/lint/context.mjs +56 -0
- package/vendor/base/src/lint/design/defs.mjs +46 -0
- package/vendor/base/src/lint/design/file.mjs +737 -0
- package/vendor/base/src/lint/design/index.d.mts +30 -0
- package/vendor/base/src/lint/design/index.mjs +996 -0
- package/vendor/base/src/lint/design/tokens.mjs +128 -0
- package/vendor/base/src/lint/design/vocabulary.mjs +181 -0
- package/vendor/base/src/lint/design/walk.mjs +444 -0
- package/vendor/base/src/lint/index.mjs +28 -0
- package/vendor/base/src/lint/nodes/blocks.mjs +63 -0
- package/vendor/base/src/lint/nodes/index.d.mts +28 -0
- package/vendor/base/src/lint/nodes/index.mjs +82 -0
- package/vendor/base/src/lint/nodes/load.mjs +126 -0
- package/vendor/base/src/lint/nodes/node.mjs +764 -0
- package/vendor/base/src/lint/nodes/package.mjs +112 -0
- package/vendor/base/src/lint/nodes/schema.mjs +75 -0
- package/vendor/base/src/logger.ts +33 -0
- package/vendor/base/src/manifests/compose.ts +539 -0
- package/vendor/base/src/manifests/executor/callback.ts +107 -0
- package/vendor/base/src/manifests/executor/context.ts +110 -0
- package/vendor/base/src/manifests/executor/index.ts +47 -0
- package/vendor/base/src/manifests/executor/lookup.ts +21 -0
- package/vendor/base/src/manifests/executor/memory.ts +62 -0
- package/vendor/base/src/manifests/executor/promise.ts +71 -0
- package/vendor/base/src/manifests/executor/refire.ts +34 -0
- package/vendor/base/src/manifests/executor/send.ts +65 -0
- package/vendor/base/src/manifests/executor/session.ts +46 -0
- package/vendor/base/src/manifests/executor/toolBridge.ts +266 -0
- package/vendor/base/src/manifests/index.ts +280 -0
- package/vendor/base/src/manifests/integrity.ts +93 -0
- package/vendor/base/src/manifests/rows.ts +33 -0
- package/vendor/base/src/manifests/runtime/auth/aws.ts +240 -0
- package/vendor/base/src/manifests/runtime/auth/oauth.ts +76 -0
- package/vendor/base/src/manifests/runtime/authorize.ts +159 -0
- package/vendor/base/src/manifests/runtime/channels/final.ts +171 -0
- package/vendor/base/src/manifests/runtime/channels/service.ts +138 -0
- package/vendor/base/src/manifests/runtime/channels/workflow.ts +310 -0
- package/vendor/base/src/manifests/runtime/context.ts +156 -0
- package/vendor/base/src/manifests/runtime/docstore/handlers.ts +547 -0
- package/vendor/base/src/manifests/runtime/docstore/index.ts +135 -0
- package/vendor/base/src/manifests/runtime/docstore/sectionizer.ts +184 -0
- package/vendor/base/src/manifests/runtime/docstore/store.ts +172 -0
- package/vendor/base/src/manifests/runtime/docstore/types.ts +60 -0
- package/vendor/base/src/manifests/runtime/duplex/audioLane.ts +165 -0
- package/vendor/base/src/manifests/runtime/duplex/session.ts +511 -0
- package/vendor/base/src/manifests/runtime/events.ts +223 -0
- package/vendor/base/src/manifests/runtime/helpers.ts +53 -0
- package/vendor/base/src/manifests/runtime/http/allowedHosts.ts +118 -0
- package/vendor/base/src/manifests/runtime/http/request.ts +637 -0
- package/vendor/base/src/manifests/runtime/http/response.ts +162 -0
- package/vendor/base/src/manifests/runtime/index.ts +57 -0
- package/vendor/base/src/manifests/runtime/loops/chunk.ts +98 -0
- package/vendor/base/src/manifests/runtime/loops/loop.ts +191 -0
- package/vendor/base/src/manifests/runtime/loops/paginate.ts +143 -0
- package/vendor/base/src/manifests/runtime/loops/poll.ts +169 -0
- package/vendor/base/src/manifests/runtime/narrate.ts +53 -0
- package/vendor/base/src/manifests/runtime/state.ts +171 -0
- package/vendor/base/src/manifests/runtime/templating.ts +110 -0
- package/vendor/base/src/manifests/runtime/tools/regionSkills.ts +125 -0
- package/vendor/base/src/manifests/runtime/tools/toolloop.ts +550 -0
- package/vendor/base/src/manifests/runtime/usage.ts +87 -0
- package/vendor/base/src/manifests/source.ts +196 -0
- package/vendor/base/src/manifests/watch.ts +67 -0
- package/vendor/base/src/markdown/document.ts +141 -0
- package/vendor/base/src/paths.ts +180 -0
- package/vendor/base/src/platform/audioManager.ts +100 -0
- package/vendor/base/src/platform/clientTransport.ts +65 -0
- package/vendor/base/src/platform/executionContext.ts +157 -0
- package/vendor/base/src/platform/index.ts +10 -0
- package/vendor/base/src/platform/pluginAPI.ts +146 -0
- package/vendor/base/src/platform/redis.ts +54 -0
- package/vendor/base/src/platform/serviceCalls.ts +222 -0
- package/vendor/base/src/pluginBase.ts +248 -0
- package/vendor/base/src/plugins/README.md +57 -0
- package/vendor/base/src/plugins/discovery.ts +87 -0
- package/vendor/base/src/plugins/index.ts +16 -0
- package/vendor/base/src/plugins/loader.ts +179 -0
- package/vendor/base/src/plugins/redis.ts +36 -0
- package/vendor/base/src/plugins/state.ts +53 -0
- package/vendor/base/src/registry.ts +95 -0
- package/vendor/base/src/security/permissions.ts +93 -0
- package/vendor/base/src/security/rules.ts +73 -0
- package/vendor/base/src/template/SafeExpression.ts +556 -0
- package/vendor/base/src/template/StringTemplateResolver.ts +75 -0
- package/vendor/base/src/template/index.ts +22 -0
- package/vendor/base/src/types.ts +50 -0
- package/vendor/base/src/workflow/GetMemoryService.ts +158 -0
- package/vendor/base/src/workflow/PlatformMemoryCore.ts +449 -0
- package/vendor/base/src/workflow/routing.ts +122 -0
- package/vendor/sdk/lib/core/store.ts +26 -6
- package/local/mcp.ts +0 -265
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Base Types and Classes
|
|
3
|
+
*
|
|
4
|
+
* These are copied from @unoverse-platform/plugin-base to remove the
|
|
5
|
+
* compile-time dependency. This allows node-service to start without
|
|
6
|
+
* packages/ being mounted.
|
|
7
|
+
*
|
|
8
|
+
* SINCE 2026-07-29 THIS IS THE ONLY IN-REPO COPY: the plugin-base source retired to
|
|
9
|
+
* _legacy/plugin-base once the last code node (postgres-toolkit) did, and everything
|
|
10
|
+
* in-repo that still speaks the plugin protocol — packages/marketplace, the server's
|
|
11
|
+
* component runtime — imports from here. The PUBLISHED @unoverse-platform/plugin-base
|
|
12
|
+
* stays on npm, frozen, for externally installed plugins; the loader still names it by
|
|
13
|
+
* that string.
|
|
14
|
+
*
|
|
15
|
+
* A FAITHFUL transcription, including its loosest part: plugin-base's own index exported
|
|
16
|
+
* `export type EnhancedNodeDefinition = any` (and friends) as LOCAL declarations, which
|
|
17
|
+
* SHADOW its `export * from "./types"` star export — so every consumer's "typed" imports
|
|
18
|
+
* were `any` all along. The aliases below reproduce that, rather than pretending the old
|
|
19
|
+
* package gave marketplace type-safety it never had.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Platform dependencies for internal use
|
|
24
|
+
*/
|
|
25
|
+
export interface PlatformDependencies {
|
|
26
|
+
PromiseNode: any;
|
|
27
|
+
CallbackNode: any;
|
|
28
|
+
NodeInputType: any;
|
|
29
|
+
NodeConcurrency: any;
|
|
30
|
+
getNodeCredentials: (context: any, credentialName: string) => Promise<any>;
|
|
31
|
+
getConfig: () => any;
|
|
32
|
+
createLogger: (name: string) => any;
|
|
33
|
+
saveTokenUsage: (usage: any) => Promise<void>;
|
|
34
|
+
callService: (method: string, params: any, context: any) => Promise<any>;
|
|
35
|
+
getRedisClient: () => any;
|
|
36
|
+
gravityPublish: (channel: string, message: any) => Promise<void>;
|
|
37
|
+
executeNodeWithRouting?: (
|
|
38
|
+
executeNode: (inputs: any, config: any, context: any) => Promise<any>,
|
|
39
|
+
params: any,
|
|
40
|
+
config: any,
|
|
41
|
+
context: any
|
|
42
|
+
) => Promise<any>;
|
|
43
|
+
deliverToNode?: (
|
|
44
|
+
executionId: string,
|
|
45
|
+
nodeId: string,
|
|
46
|
+
handle: string,
|
|
47
|
+
value: unknown,
|
|
48
|
+
signal?: string,
|
|
49
|
+
) => Promise<void>;
|
|
50
|
+
getAudioWebSocketManager?: () => any;
|
|
51
|
+
[key: string]: any;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Base class for Promise-based nodes
|
|
56
|
+
*/
|
|
57
|
+
export class PromiseNode {
|
|
58
|
+
nodeType: string;
|
|
59
|
+
logger: any = { info: () => {}, error: () => {}, debug: () => {} };
|
|
60
|
+
|
|
61
|
+
constructor(name: string) {
|
|
62
|
+
this.nodeType = name || "stub";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
protected async validateConfig(config: any): Promise<any> {
|
|
66
|
+
return { success: true };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
protected async executeNode(inputs: any, config: any, context: any): Promise<any> {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected validateAndGetContext(context: any) {
|
|
74
|
+
return {
|
|
75
|
+
workflowId: context?.workflowId || context?.workflow?.id || "",
|
|
76
|
+
executionId: context?.executionId || context?.workflow?.runId || "",
|
|
77
|
+
nodeId: context?.nodeId || "",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected getExecutionContext(context: any) {
|
|
82
|
+
return {
|
|
83
|
+
workflowId: context?.workflowId || context?.workflow?.id || "",
|
|
84
|
+
executionId: context?.executionId || context?.workflow?.runId || "",
|
|
85
|
+
nodeId: context?.nodeId || "",
|
|
86
|
+
nodeType: context?.nodeType || this.nodeType || "",
|
|
87
|
+
config: context?.config || {},
|
|
88
|
+
credentials: context?.credentials || {},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async execute(inputs: any, config: any, context: any): Promise<any> {
|
|
93
|
+
return this.executeNode(inputs, config, context);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Base class for Callback-based nodes
|
|
99
|
+
*/
|
|
100
|
+
export class CallbackNode {
|
|
101
|
+
constructor(name: string) {}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Node input type enum
|
|
106
|
+
*/
|
|
107
|
+
export const NodeInputType = {
|
|
108
|
+
STRING: "string",
|
|
109
|
+
OBJECT: "object",
|
|
110
|
+
ARRAY: "array",
|
|
111
|
+
NUMBER: "number",
|
|
112
|
+
BOOLEAN: "boolean",
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Global platform instance
|
|
116
|
+
let platformDeps: PlatformDependencies | null = null;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Set platform dependencies (called by plugin setup)
|
|
120
|
+
*/
|
|
121
|
+
export function setPlatformDependencies(deps: PlatformDependencies) {
|
|
122
|
+
if (platformDeps !== null) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
platformDeps = deps;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The loose compile-time aliases plugin-base actually exported (see the header). */
|
|
129
|
+
export type EnhancedNodeDefinition = any;
|
|
130
|
+
export type ValidationResult = any;
|
|
131
|
+
export type NodeExecutionContext = any;
|
|
132
|
+
|
|
133
|
+
/** Plugin interface that packages implement. Transcribed from plugin-base index.ts. */
|
|
134
|
+
export interface GravityPlugin {
|
|
135
|
+
name: string;
|
|
136
|
+
version?: string;
|
|
137
|
+
description?: string;
|
|
138
|
+
setup(api: GravityPluginAPI): void | Promise<void>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The API the platform hands a plugin's setup(). Transcribed from plugin-base index.ts. */
|
|
142
|
+
export interface GravityPluginAPI {
|
|
143
|
+
registerNode(node: PluginNodeDefinition): void;
|
|
144
|
+
registerService(name: string, service: any): void;
|
|
145
|
+
registerCredential(credential: any): void;
|
|
146
|
+
registerComponentPath?(packagePath: string): void;
|
|
147
|
+
createLogger(name: string): any;
|
|
148
|
+
getConfig(): any;
|
|
149
|
+
saveTokenUsage(usage: any): Promise<void>;
|
|
150
|
+
saveMCPTrace(trace: any): Promise<string>;
|
|
151
|
+
getNodeCredentials(context: any, credentialName: string): Promise<any>;
|
|
152
|
+
callService(method: string, params: any, context: any): Promise<any>;
|
|
153
|
+
getRedisClient(): any;
|
|
154
|
+
gravityPublish(channel: string, message: any): Promise<void>;
|
|
155
|
+
getAudioWebSocketManager?: () => any;
|
|
156
|
+
getWebSocketManager?: () => any;
|
|
157
|
+
publishStreamingUpdate?: (config: { componentType: string; props: Record<string, any> }) => void;
|
|
158
|
+
executeNodeWithRouting?: (
|
|
159
|
+
executeNode: (inputs: any, config: any, context: any) => Promise<any>,
|
|
160
|
+
params: any,
|
|
161
|
+
config: any,
|
|
162
|
+
context: any,
|
|
163
|
+
) => Promise<any>;
|
|
164
|
+
deliverToNode?: (
|
|
165
|
+
executionId: string,
|
|
166
|
+
nodeId: string,
|
|
167
|
+
handle: string,
|
|
168
|
+
value: unknown,
|
|
169
|
+
signal?: string,
|
|
170
|
+
) => Promise<void>;
|
|
171
|
+
classes: { PromiseNode: any; CallbackNode: any };
|
|
172
|
+
types: { NodeInputType: any; NodeConcurrency: any };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface PluginNodeDefinition {
|
|
176
|
+
definition: any;
|
|
177
|
+
executor: any;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Helper to create a plugin — the identity function plugin-base always was. */
|
|
181
|
+
export function createPlugin(config: {
|
|
182
|
+
name: string;
|
|
183
|
+
version?: string;
|
|
184
|
+
description?: string;
|
|
185
|
+
setup: (api: GravityPluginAPI) => void | Promise<void>;
|
|
186
|
+
}): GravityPlugin {
|
|
187
|
+
return config;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Initialize platform dependencies from the plugin API — transcribed from plugin-base
|
|
192
|
+
* index.ts, the same field mapping, so a plugin moving its import here behaves
|
|
193
|
+
* identically at setup time.
|
|
194
|
+
*/
|
|
195
|
+
export function initializePlatformFromAPI(api: GravityPluginAPI) {
|
|
196
|
+
setPlatformDependencies({
|
|
197
|
+
PromiseNode: api.classes.PromiseNode,
|
|
198
|
+
CallbackNode: api.classes.CallbackNode,
|
|
199
|
+
NodeInputType: api.types.NodeInputType,
|
|
200
|
+
NodeConcurrency: api.types.NodeConcurrency,
|
|
201
|
+
getNodeCredentials: api.getNodeCredentials,
|
|
202
|
+
getConfig: api.getConfig,
|
|
203
|
+
createLogger: api.createLogger,
|
|
204
|
+
saveTokenUsage: api.saveTokenUsage,
|
|
205
|
+
saveMCPTrace: api.saveMCPTrace,
|
|
206
|
+
callService: api.callService,
|
|
207
|
+
getRedisClient: api.getRedisClient,
|
|
208
|
+
gravityPublish: api.gravityPublish,
|
|
209
|
+
executeNodeWithRouting: api.executeNodeWithRouting,
|
|
210
|
+
deliverToNode: api.deliverToNode,
|
|
211
|
+
getAudioWebSocketManager: api.getAudioWebSocketManager,
|
|
212
|
+
} as PlatformDependencies);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// The prop-key reader marketplace's synthesizer uses — base's own copy, re-exported so a
|
|
216
|
+
// plugin needs ONE import root here rather than knowing base's internal layout.
|
|
217
|
+
export { inputPropKeys, type InputPropLike } from "./definitions/inputs.js";
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get platform dependencies
|
|
221
|
+
*/
|
|
222
|
+
export function getPlatformDependencies(): PlatformDependencies {
|
|
223
|
+
if (!platformDeps) {
|
|
224
|
+
return {
|
|
225
|
+
packageVersion: "1.1.1",
|
|
226
|
+
PromiseNode,
|
|
227
|
+
CallbackNode,
|
|
228
|
+
NodeInputType,
|
|
229
|
+
NodeConcurrency: {},
|
|
230
|
+
getConfig: () => ({}),
|
|
231
|
+
createLogger: () => ({ info: () => {}, error: () => {}, debug: () => {}, warn: () => {} }),
|
|
232
|
+
saveTokenUsage: () => Promise.resolve(),
|
|
233
|
+
saveMCPTrace: () => Promise.resolve(),
|
|
234
|
+
callService: () => Promise.resolve(null),
|
|
235
|
+
getRedisClient: () => null,
|
|
236
|
+
gravityPublish: async () => {},
|
|
237
|
+
executeNodeWithRouting: async () => ({}),
|
|
238
|
+
getAudioWebSocketManager: () => null,
|
|
239
|
+
getNodeCredentials: async (context: any, credentialName: string) => {
|
|
240
|
+
if (platformDeps?.getNodeCredentials) {
|
|
241
|
+
return platformDeps.getNodeCredentials(context, credentialName);
|
|
242
|
+
}
|
|
243
|
+
return context?.credentials?.[credentialName] || {};
|
|
244
|
+
},
|
|
245
|
+
} as any;
|
|
246
|
+
}
|
|
247
|
+
return platformDeps;
|
|
248
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# plugins/ — LEGACY. Nothing new goes here.
|
|
2
|
+
|
|
3
|
+
This folder loads **JavaScript** nodes: a package with a `src/`, a `setup(api)` call, and
|
|
4
|
+
classes extending `PromiseNode` or `CallbackNode`. Every node becomes a YAML manifest
|
|
5
|
+
(`../manifests/`), so this whole folder is scheduled for deletion.
|
|
6
|
+
|
|
7
|
+
**Do not add to it. Do not tidy it. Do not move it.** Work spent here is work spent twice.
|
|
8
|
+
|
|
9
|
+
## What is still holding it open
|
|
10
|
+
|
|
11
|
+
17 packages still ship `src/`. Most are vendor integrations and are pure migration work,
|
|
12
|
+
but three are platform capability and are why this cannot simply be switched off:
|
|
13
|
+
|
|
14
|
+
| Package | Why it matters |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `flow` | `IfElse`, `LoopStart`, `LoopEnd`, `Code`, `Context`, `FieldValidator`. **Control flow.** Deleting this folder today breaks every workflow with a loop or a condition |
|
|
17
|
+
| `ingest` | the content pipeline (document parsing, sheets, connectors) |
|
|
18
|
+
| `spatial` | `SpatialSearch` |
|
|
19
|
+
|
|
20
|
+
Two of the remaining ones also need the executor to grow first
|
|
21
|
+
(`DECLARATIVE_NODES.md` §10): a socket transport with audio framing for `openai-realtime`
|
|
22
|
+
and the streaming half of `elevenlabs`, and catalog matching for the hubspot and salesforce
|
|
23
|
+
attribute pull. Everything else is conversion, not invention.
|
|
24
|
+
|
|
25
|
+
## The folder is two jobs, dying on two timelines
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
discovery.ts loader.ts redis.ts state.ts ← code-node loading. Dies with the last src/ package
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**The npm half is GONE.** `install.ts` and `startup.ts` are deleted, and with them
|
|
32
|
+
`CORE_PACKAGES` — the boot step that installed `@unoverse-platform/marketplace` so the
|
|
33
|
+
design system could resolve out of that package. `013_items.sql` said where it was going:
|
|
34
|
+
"Installing something from the marketplace is an INSERT... no npm, no image, no rsync, no
|
|
35
|
+
restart." Rows replaced npm for content exactly as manifests replaced code for nodes, and
|
|
36
|
+
MARKETPLACE.md §5 is now the whole story — a universe fetches from the marketplace URL at
|
|
37
|
+
install time and writes a row.
|
|
38
|
+
|
|
39
|
+
The last traces went with it (2026-08-30): `apps/unoverse` no longer declares
|
|
40
|
+
`@unoverse-platform/marketplace` as a dependency (nothing ever imported it — the only
|
|
41
|
+
mention was a string label), and `definitions/registry/homes.ts` no longer lists the
|
|
42
|
+
`PLUGINS_DIR/node_modules/...` bundle as a candidate. The core platform has no
|
|
43
|
+
relationship with the marketplace package at all; it talks to the marketplace over HTTP.
|
|
44
|
+
|
|
45
|
+
What remains here is one job, not two: loading code nodes.
|
|
46
|
+
|
|
47
|
+
## How it ends
|
|
48
|
+
|
|
49
|
+
No flag day. Each package that converts to YAML removes one reason for this folder to exist.
|
|
50
|
+
When no package ships `src/`, delete `discovery.ts`, `loader.ts`, `redis.ts` and
|
|
51
|
+
`../pluginBase.ts`. When installs are rows, delete the rest and the folder with it.
|
|
52
|
+
|
|
53
|
+
The two Vite warnings a developer sees when starting Studio come from `discovery.ts` and
|
|
54
|
+
`loader.ts` (dynamic `import()` of a package by name). They are correct, they are
|
|
55
|
+
unfixable while this folder exists, and they leave when it does.
|
|
56
|
+
|
|
57
|
+
See `docs/architecture/DECLARATIVE_NODES.md` §7a and §10.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Discovery — finds plugins from /packages folder
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import type { PluginMetadata } from "../types.js";
|
|
8
|
+
import { PACKAGES_PATH, PLUGINS_DIR, NODES_HOME } from "../paths.js";
|
|
9
|
+
|
|
10
|
+
let ALL_PLUGINS: string[] = [];
|
|
11
|
+
|
|
12
|
+
export function getAllPlugins(): string[] {
|
|
13
|
+
return ALL_PLUGINS;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function setAllPlugins(plugins: string[]): void {
|
|
17
|
+
ALL_PLUGINS = plugins;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Scan a directory for plugin packages (a package.json whose name is in `scopes` and has an entry). */
|
|
21
|
+
function scanPluginDir(dir: string, scopes: string[]): string[] {
|
|
22
|
+
const plugins: string[] = [];
|
|
23
|
+
if (!fs.existsSync(dir)) return plugins;
|
|
24
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
25
|
+
if (!entry.isDirectory()) continue;
|
|
26
|
+
const pkgJsonPath = path.join(dir, entry.name, "package.json");
|
|
27
|
+
if (!fs.existsSync(pkgJsonPath)) continue;
|
|
28
|
+
try {
|
|
29
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
30
|
+
const pkgName = pkgJson.name;
|
|
31
|
+
if (!pkgName || pkgName.includes("/docs")) continue;
|
|
32
|
+
if (scopes.some((s) => pkgName.startsWith(s)) && (pkgJson.main || pkgJson.exports)) {
|
|
33
|
+
plugins.push(pkgName);
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
// Skip invalid package.json files
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return plugins;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Nodes that have been moved INTO Unoverse (apps/unoverse/nodes). */
|
|
43
|
+
export function discoverNodesHome(): string[] {
|
|
44
|
+
return scanPluginDir(NODES_HOME, ["@unoverse-platform/"]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* LEGACY starter-kit packages. The fallback from before nodes moved to `nodes/`.
|
|
49
|
+
*
|
|
50
|
+
* Absent is the NORMAL state now, so its absence is not worth a line at boot. It used to
|
|
51
|
+
* warn, which was right when the folder was expected and missing meant something was wrong.
|
|
52
|
+
*/
|
|
53
|
+
export function discoverPlugins(): string[] {
|
|
54
|
+
return scanPluginDir(PACKAGES_PATH, ["@unoverse-platform/"]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function getPluginMetadata(pluginName: string): Promise<PluginMetadata> {
|
|
58
|
+
try {
|
|
59
|
+
const packagesPath = PACKAGES_PATH;
|
|
60
|
+
const shortName = pluginName.replace("@unoverse-platform/", "");
|
|
61
|
+
const pkgJsonPath = path.join(packagesPath, shortName, "package.json");
|
|
62
|
+
|
|
63
|
+
const pluginsDir = PLUGINS_DIR;
|
|
64
|
+
const npmPkgJsonPath = path.join(pluginsDir, "node_modules", pluginName, "package.json");
|
|
65
|
+
|
|
66
|
+
let pkg: any;
|
|
67
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
68
|
+
pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
69
|
+
} else if (fs.existsSync(npmPkgJsonPath)) {
|
|
70
|
+
pkg = JSON.parse(fs.readFileSync(npmPkgJsonPath, "utf-8"));
|
|
71
|
+
} else {
|
|
72
|
+
pkg = await import(/* @vite-ignore */ `${pluginName}/package.json`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const gravity = pkg.gravity || {};
|
|
76
|
+
return {
|
|
77
|
+
version: pkg.version || "1.0.0",
|
|
78
|
+
description: pkg.description || "",
|
|
79
|
+
category: gravity.category || undefined,
|
|
80
|
+
displayName: gravity.displayName || undefined,
|
|
81
|
+
features: gravity.features || undefined,
|
|
82
|
+
credentials: gravity.credentials || undefined,
|
|
83
|
+
};
|
|
84
|
+
} catch {
|
|
85
|
+
return { version: "1.0.0", description: "" };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { getPluginState, setPluginState, isPluginEnabled, persistPluginState, removePluginState, listPluginState } from "./state.js";
|
|
2
|
+
export { getAllPlugins, getPluginMetadata, discoverPlugins } from "./discovery.js";
|
|
3
|
+
export { loadPlugin, unloadPlugin } from "./loader.js";
|
|
4
|
+
/**
|
|
5
|
+
* NO INSTALL EXPORT, DELIBERATELY. `install.ts` held `installPlugin`/`uninstallPlugin`,
|
|
6
|
+
* which shelled out to `npm install` at runtime and imported whatever came back, gated by
|
|
7
|
+
* an `@unoverse-platform` scope allowlist. That whole lane is retired (MARKETPLACE.md §10
|
|
8
|
+
* q3, removed 2026-08-30): a universe takes content from the marketplace as ROWS via
|
|
9
|
+
* `POST /marketplace/install`, and node packages are declarative YAML already on the box.
|
|
10
|
+
* Nothing fetches or executes code from a package registry any more.
|
|
11
|
+
*/
|
|
12
|
+
export { warnMissingNodeMeta } from "./redis.js";
|
|
13
|
+
// startup.ts (loadInstalledNpmPlugins) retired 2026-08-06: it survived the npm-plugin
|
|
14
|
+
// plane only as the bridge that loaded @unoverse-platform/marketplace's duplicate of the
|
|
15
|
+
// Component-node executor. The server registers the node from its own code
|
|
16
|
+
// (runtime/components/registerComponentNode); the package is content only.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Loader — loads plugins and registers nodes into the in-memory registry.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import { pathToFileURL } from "url";
|
|
9
|
+
import { getNodeRegistry, getPluginNodeMap, deleteNode } from "../registry.js";
|
|
10
|
+
import { PACKAGES_PATH, MARKETPLACE_PATH, PLUGINS_DIR, NODES_HOME } from "../paths.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a package directory's entry file from its package.json.
|
|
14
|
+
*
|
|
15
|
+
* CommonJS `require(dir)` reads package.json "main" and loads it; ESM `import(dir)`
|
|
16
|
+
* does NOT do directory/main resolution for a filesystem path (it only does so for
|
|
17
|
+
* bare package specifiers). Since the engine moved to ESM we resolve the entry
|
|
18
|
+
* ourselves — supporting `exports["."]` and `main`, falling back to index.js.
|
|
19
|
+
*/
|
|
20
|
+
function resolveEntry(dir: string): string {
|
|
21
|
+
// Unoverse runs under tsx in BOTH dev and prod (see Dockerfile), so it can import a
|
|
22
|
+
// package's TypeScript source directly. For the in-Unoverse node packages (NODES_HOME),
|
|
23
|
+
// prefer `src/index.ts` when it exists: this sidesteps the whole "stale/missing dist"
|
|
24
|
+
// failure class — most painfully the `components` package, which is regenerated by
|
|
25
|
+
// `nodegen:local` and whose gitignored dist silently goes missing (the runtime then
|
|
26
|
+
// boots without those nodes and the workflow pulls a partial catalog → "Unknown node
|
|
27
|
+
// type: CardFinder"). Loading from src also means a regenerated node takes effect on
|
|
28
|
+
// restart with no separate build. Scoped to NODES_HOME so legacy /packages and
|
|
29
|
+
// marketplace plugins keep their existing (bundled-dist) resolution untouched.
|
|
30
|
+
if (path.resolve(dir).startsWith(path.resolve(NODES_HOME) + path.sep)) {
|
|
31
|
+
const srcTs = path.join(dir, "src", "index.ts");
|
|
32
|
+
if (fs.existsSync(srcTs)) return srcTs;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf-8"));
|
|
36
|
+
let entry: string = pkg.main || "index.js";
|
|
37
|
+
const exp = pkg.exports;
|
|
38
|
+
if (exp) {
|
|
39
|
+
const dot = typeof exp === "string" ? exp : exp["."] ?? exp;
|
|
40
|
+
if (typeof dot === "string") entry = dot;
|
|
41
|
+
else if (dot && typeof dot === "object") entry = dot.import || dot.require || dot.default || entry;
|
|
42
|
+
}
|
|
43
|
+
return path.join(dir, entry);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Import a plugin from a directory (resolving its entry file as a file:// URL) or,
|
|
48
|
+
* if no local directory exists, from its bare package specifier (npm-resolved).
|
|
49
|
+
*/
|
|
50
|
+
export async function importPluginModule(dirOrName: string): Promise<any> {
|
|
51
|
+
let hasPkg = false;
|
|
52
|
+
try {
|
|
53
|
+
hasPkg = fs.existsSync(path.join(dirOrName, "package.json"));
|
|
54
|
+
} catch {
|
|
55
|
+
hasPkg = false;
|
|
56
|
+
}
|
|
57
|
+
// @vite-ignore: loading a plugin by name IS the job here, so the specifier cannot be
|
|
58
|
+
// static. Vite is right to warn and there is nothing to fix while this folder exists;
|
|
59
|
+
// the warning printed on every Studio start (see ./README.md, this folder is legacy).
|
|
60
|
+
const target = hasPkg ? pathToFileURL(resolveEntry(dirOrName)).href : dirOrName;
|
|
61
|
+
return import(/* @vite-ignore */ target);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Clear all cached modules under a given path so re-import loads fresh code.
|
|
66
|
+
*
|
|
67
|
+
* Under ESM there is no `require.cache`, and `import()` caches by URL with no
|
|
68
|
+
* public API to evict — so plugin hot-reload is a no-op here and is handled by
|
|
69
|
+
* a process restart instead (see the marketplace upgrade docs). Guarded so the
|
|
70
|
+
* call site stays identical to the CJS original.
|
|
71
|
+
*/
|
|
72
|
+
export function clearModuleCache(modulePath: string): void {
|
|
73
|
+
const req = (globalThis as { require?: { cache?: Record<string, unknown> } }).require;
|
|
74
|
+
if (!req?.cache) return;
|
|
75
|
+
const resolved = path.resolve(modulePath);
|
|
76
|
+
for (const key of Object.keys(req.cache)) {
|
|
77
|
+
if (key.startsWith(resolved)) {
|
|
78
|
+
delete req.cache[key];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function loadPlugin(pluginName: string, api: any): Promise<string[]> {
|
|
84
|
+
const nodeRegistry = getNodeRegistry();
|
|
85
|
+
const nodeTypes: string[] = [];
|
|
86
|
+
const nodesBefore = new Set(nodeRegistry.keys());
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const shortName = pluginName.replace(/@[^/]+\//, "");
|
|
90
|
+
// Resolution order: the in-Unoverse node home wins, then legacy /packages,
|
|
91
|
+
// then /packages-marketplace, then the bare npm specifier.
|
|
92
|
+
const homePath = path.join(NODES_HOME, shortName);
|
|
93
|
+
const pkgPath = path.join(PACKAGES_PATH, shortName);
|
|
94
|
+
const mktPath = path.join(MARKETPLACE_PATH, shortName);
|
|
95
|
+
const resolvedPath = fs.existsSync(homePath)
|
|
96
|
+
? homePath
|
|
97
|
+
: fs.existsSync(pkgPath)
|
|
98
|
+
? pkgPath
|
|
99
|
+
: fs.existsSync(mktPath)
|
|
100
|
+
? mktPath
|
|
101
|
+
: pluginName;
|
|
102
|
+
|
|
103
|
+
const pkg = await importPluginModule(resolvedPath);
|
|
104
|
+
const plugin = pkg.default || pkg;
|
|
105
|
+
|
|
106
|
+
if (plugin && typeof plugin.setup === "function") {
|
|
107
|
+
await plugin.setup(api);
|
|
108
|
+
|
|
109
|
+
for (const [type] of nodeRegistry) {
|
|
110
|
+
if (!nodesBefore.has(type)) {
|
|
111
|
+
nodeTypes.push(type);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Stamp package-level marketplace metadata (package.json `gravity`) onto the nodes
|
|
116
|
+
// this plugin just registered. By the authoring convention (packages/docs/nodes), a
|
|
117
|
+
// package's icon lives in `gravity.logoUrl` and applies to every node in the package
|
|
118
|
+
// — it is NOT repeated on each createNodeDefinition(). node-service merged this; the
|
|
119
|
+
// Unoverse runtime didn't, so the Canvas drew these nodes with no icon. A node that
|
|
120
|
+
// sets its own logoUrl wins; the package value is only a fallback.
|
|
121
|
+
try {
|
|
122
|
+
const pkgJsonPath = path.join(resolvedPath, "package.json");
|
|
123
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
124
|
+
const gravity = (JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8")).gravity ?? {}) as {
|
|
125
|
+
logoUrl?: string;
|
|
126
|
+
};
|
|
127
|
+
if (gravity.logoUrl) {
|
|
128
|
+
for (const type of nodeTypes) {
|
|
129
|
+
const def = nodeRegistry.get(type) as { logoUrl?: string } | undefined;
|
|
130
|
+
if (def && !def.logoUrl) def.logoUrl = gravity.logoUrl;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// Marketplace metadata is best-effort — a missing/!malformed manifest must not
|
|
136
|
+
// fail node loading.
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch (error: any) {
|
|
140
|
+
console.log(` ⚠ Failed to load ${pluginName}: ${error.message}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return nodeTypes;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function unloadPlugin(pluginName: string): void {
|
|
147
|
+
const pluginNodeMap = getPluginNodeMap();
|
|
148
|
+
const nodeTypes = pluginNodeMap.get(pluginName) || [];
|
|
149
|
+
|
|
150
|
+
// Drop from the in-memory registry. Workflow re-pulls the catalog (GET /nodes),
|
|
151
|
+
// so there is no Redis catalog to purge.
|
|
152
|
+
for (const nodeType of nodeTypes) {
|
|
153
|
+
deleteNode(nodeType);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
pluginNodeMap.delete(pluginName);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function getPluginsDir(): string {
|
|
160
|
+
return PLUGINS_DIR;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function ensurePluginsDir(): void {
|
|
164
|
+
if (!fs.existsSync(PLUGINS_DIR)) {
|
|
165
|
+
fs.mkdirSync(PLUGINS_DIR, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
// No seeded dependencies. plugin-base retired 2026-07-29 with the last code node —
|
|
168
|
+
// an installed package that needs it (an OLD published version) declares it as an
|
|
169
|
+
// ordinary npm dependency and npm installs it transitively; the self-heal that used
|
|
170
|
+
// to force-upgrade it existed for PEER-dep node packages, and none remain.
|
|
171
|
+
const pkgJsonPath = path.join(PLUGINS_DIR, "package.json");
|
|
172
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
173
|
+
fs.writeFileSync(pkgJsonPath, JSON.stringify({
|
|
174
|
+
name: "gravity-plugins",
|
|
175
|
+
private: true,
|
|
176
|
+
dependencies: {},
|
|
177
|
+
}, null, 2));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-meta guard. (Formerly also held the Redis node/credential catalog writers —
|
|
3
|
+
* removed with the catalog repoint: the catalog now lives in the in-memory registry
|
|
4
|
+
* and is pulled by workflow over HTTP, so there is nothing to write to Redis.)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { boot } from "../boot.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Meta sync guard — LOUD signal whenever a batch of node defs is registered.
|
|
11
|
+
*
|
|
12
|
+
* `whenToUse` is the field the node ranker (CatalogService / semantic search)
|
|
13
|
+
* embeds; if it's empty the search silently degrades to name+description. A stale
|
|
14
|
+
* build or a freshly imported package that never authored it leaves the meta
|
|
15
|
+
* unsynced with NO error. Call this right after registering a batch (startup,
|
|
16
|
+
* import/update, reload) so a gap is visible at the moment it enters the registry.
|
|
17
|
+
*
|
|
18
|
+
* Returns the missing node types so callers can surface them in an API response too.
|
|
19
|
+
*/
|
|
20
|
+
export function warnMissingNodeMeta(defs: any[], context: string): { total: number; missing: string[] } {
|
|
21
|
+
const missing = defs
|
|
22
|
+
.filter((d: any) => !d?.whenToUse || !String(d.whenToUse).trim())
|
|
23
|
+
.map((d: any) => d?.type)
|
|
24
|
+
.filter(Boolean);
|
|
25
|
+
// Only the shortfall is worth saying. "All 84 nodes have whenToUse" is the expected
|
|
26
|
+
// state and a line that only ever reports success trains you to skim past it.
|
|
27
|
+
if (missing.length > 0) {
|
|
28
|
+
const named = `${missing.slice(0, 10).join(", ")}${missing.length > 10 ? `, and ${missing.length - 10} more` : ""}`;
|
|
29
|
+
const text =
|
|
30
|
+
`${missing.length} of ${defs.length} nodes have no whenToUse, so catalog ranking is degraded for them: ` +
|
|
31
|
+
`${named}. Author whenToUse on the node, then rebuild and restart.`;
|
|
32
|
+
if (boot.isBooting()) boot.notice("warn", text);
|
|
33
|
+
else console.warn(`[${context}] ${text}`);
|
|
34
|
+
}
|
|
35
|
+
return { total: defs.length, missing };
|
|
36
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin State — in-memory cache sourced from PostgreSQL
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { persistPluginState, removePluginState, fetchPluginState } from "../platform/serviceCalls.js";
|
|
6
|
+
import { boot } from "../boot.js";
|
|
7
|
+
|
|
8
|
+
export type PluginStateEntry = {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
version: string;
|
|
11
|
+
source: string;
|
|
12
|
+
metadata?: any;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let PLUGIN_STATE: Map<string, PluginStateEntry> = new Map();
|
|
16
|
+
|
|
17
|
+
export function getPluginState(name: string): PluginStateEntry | undefined {
|
|
18
|
+
return PLUGIN_STATE.get(name);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function setPluginState(name: string, state: PluginStateEntry): void {
|
|
22
|
+
PLUGIN_STATE.set(name, state);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isPluginEnabled(name: string): boolean {
|
|
26
|
+
const state = PLUGIN_STATE.get(name);
|
|
27
|
+
if (!state) return true;
|
|
28
|
+
return state.enabled;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function loadStateFromPostgres(): Promise<void> {
|
|
32
|
+
const pgState = await fetchPluginState();
|
|
33
|
+
PLUGIN_STATE.clear();
|
|
34
|
+
if (pgState.length > 0) {
|
|
35
|
+
boot.pluginStates(pgState.length);
|
|
36
|
+
for (const plugin of pgState) {
|
|
37
|
+
PLUGIN_STATE.set(plugin.name, {
|
|
38
|
+
enabled: plugin.enabled,
|
|
39
|
+
version: plugin.version,
|
|
40
|
+
source: plugin.source,
|
|
41
|
+
metadata: plugin.metadata,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** All state entries (the in-memory mirror of installed_plugins). Used by the
|
|
48
|
+
* registry-toggle routes to read enable/spatial state for skills/apps/nodes. */
|
|
49
|
+
export function listPluginState(): Array<{ name: string } & PluginStateEntry> {
|
|
50
|
+
return [...PLUGIN_STATE.entries()].map(([name, state]) => ({ name, ...state }));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { persistPluginState, removePluginState };
|