@zhin.js/runtime 1.0.7 → 1.0.10
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/lib/config-composer.js +41 -13
- package/lib/feature-projector.d.ts +1 -1
- package/lib/feature-projector.js +4 -1
- package/lib/isolation.d.ts +1 -1
- package/lib/native-development-runtime.js +1 -1
- package/lib/plugin-scope-assembler.d.ts +11 -2
- package/lib/plugin-scope-assembler.js +31 -19
- package/lib/root-runtime.d.ts +3 -2
- package/lib/root-runtime.js +64 -40
- package/lib/slot-generation-preparer.d.ts +1 -1
- package/lib/slot-generation-preparer.js +4 -2
- package/lib/subtree-generation-preparer.d.ts +1 -1
- package/lib/subtree-generation-preparer.js +5 -3
- package/lib/topology-generation-preparer.d.ts +1 -1
- package/lib/topology-generation-preparer.js +4 -3
- package/package.json +13 -13
package/lib/config-composer.js
CHANGED
|
@@ -37,7 +37,7 @@ export class ConfigComposer {
|
|
|
37
37
|
await composeNode(child, ownSchemas),
|
|
38
38
|
]));
|
|
39
39
|
// Host-level keys (`http`, `database`, `ai`, `mcp`, `a2a`, `speech`,
|
|
40
|
-
// `htmlRenderer`, `assistant`, `
|
|
40
|
+
// `htmlRenderer`, `assistant`, `log_level`) are consumed by CLI Root installers /
|
|
41
41
|
// start-command, not Plugin ConfigViews.
|
|
42
42
|
const effectiveSchema = Object.freeze({
|
|
43
43
|
type: 'object',
|
|
@@ -82,10 +82,6 @@ export class ConfigComposer {
|
|
|
82
82
|
type: 'object',
|
|
83
83
|
additionalProperties: true,
|
|
84
84
|
}),
|
|
85
|
-
collaboration: Object.freeze({
|
|
86
|
-
type: 'object',
|
|
87
|
-
additionalProperties: true,
|
|
88
|
-
}),
|
|
89
85
|
log_level: Object.freeze({
|
|
90
86
|
type: ['string', 'number'],
|
|
91
87
|
}),
|
|
@@ -119,12 +115,12 @@ export class ConfigComposer {
|
|
|
119
115
|
const FRAMEWORK_ROLE_SCHEMA = Object.freeze({
|
|
120
116
|
master: Object.freeze({
|
|
121
117
|
type: ['string', 'number'],
|
|
122
|
-
description: '
|
|
118
|
+
description: 'Framework master user id (AI/tool privileges; not group owner/admin)',
|
|
123
119
|
}),
|
|
124
120
|
trusted: Object.freeze({
|
|
125
121
|
type: 'array',
|
|
126
122
|
items: Object.freeze({ type: ['string', 'number'] }),
|
|
127
|
-
description: '
|
|
123
|
+
description: 'Framework trusted user id list (weaker than master)',
|
|
128
124
|
}),
|
|
129
125
|
});
|
|
130
126
|
function hasArrayEndpoints(props) {
|
|
@@ -133,18 +129,50 @@ function hasArrayEndpoints(props) {
|
|
|
133
129
|
return false;
|
|
134
130
|
return ep.type === 'array';
|
|
135
131
|
}
|
|
132
|
+
/** 顶层 + endpoints[].properties 注入 master/trusted(已声明则不覆盖) */
|
|
133
|
+
function injectFrameworkRoleSchema(properties) {
|
|
134
|
+
for (const [key, schema] of Object.entries(FRAMEWORK_ROLE_SCHEMA)) {
|
|
135
|
+
if (!Object.hasOwn(properties, key)) {
|
|
136
|
+
properties[key] = schema;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!hasArrayEndpoints(properties))
|
|
140
|
+
return;
|
|
141
|
+
const ep = properties.endpoints;
|
|
142
|
+
const items = ep.items;
|
|
143
|
+
if (!items || typeof items !== 'object' || Array.isArray(items))
|
|
144
|
+
return;
|
|
145
|
+
const itemsObj = items;
|
|
146
|
+
const rawProps = itemsObj.properties;
|
|
147
|
+
const itemProps = rawProps && typeof rawProps === 'object' && !Array.isArray(rawProps)
|
|
148
|
+
? { ...rawProps }
|
|
149
|
+
: {};
|
|
150
|
+
let changed = false;
|
|
151
|
+
for (const [key, schema] of Object.entries(FRAMEWORK_ROLE_SCHEMA)) {
|
|
152
|
+
if (!Object.hasOwn(itemProps, key)) {
|
|
153
|
+
itemProps[key] = schema;
|
|
154
|
+
changed = true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (!changed)
|
|
158
|
+
return;
|
|
159
|
+
properties.endpoints = Object.freeze({
|
|
160
|
+
...ep,
|
|
161
|
+
items: Object.freeze({
|
|
162
|
+
...itemsObj,
|
|
163
|
+
properties: Object.freeze(itemProps),
|
|
164
|
+
}),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
136
167
|
async function composeNode(node, ownSchemas) {
|
|
137
168
|
const own = await readOwnSchema(node);
|
|
138
169
|
ownSchemas.set(node.id, own);
|
|
139
170
|
const properties = { ...schemaProperties(own) };
|
|
140
171
|
// Adapter plugins (schemas declaring array-typed `endpoints`) get
|
|
141
|
-
// framework-level `master` / `trusted` injected when not already declared
|
|
172
|
+
// framework-level `master` / `trusted` injected when not already declared
|
|
173
|
+
// (top-level and endpoints[].properties).
|
|
142
174
|
if (hasArrayEndpoints(properties)) {
|
|
143
|
-
|
|
144
|
-
if (!Object.hasOwn(properties, key)) {
|
|
145
|
-
properties[key] = schema;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
175
|
+
injectFrameworkRoleSchema(properties);
|
|
148
176
|
}
|
|
149
177
|
for (const child of node.children) {
|
|
150
178
|
if (Object.hasOwn(properties, child.instanceKey)) {
|
|
@@ -10,6 +10,6 @@ export interface ProjectedFeatures {
|
|
|
10
10
|
export declare class FeatureProjector {
|
|
11
11
|
private readonly providers;
|
|
12
12
|
constructor(providers: Iterable<FeatureProvider>);
|
|
13
|
-
project(generation: number, base: ProjectionState, retained?: ReadonlyMap<FeatureId, unknown>): Promise<ProjectedFeatures>;
|
|
13
|
+
project(generation: number, base: ProjectionState, signal: AbortSignal, retained?: ReadonlyMap<FeatureId, unknown>): Promise<ProjectedFeatures>;
|
|
14
14
|
}
|
|
15
15
|
export declare function composeGenerationHandoffs(...handoffs: readonly (GenerationHandoff | undefined)[]): GenerationHandoff | undefined;
|
package/lib/feature-projector.js
CHANGED
|
@@ -5,7 +5,7 @@ export class FeatureProjector {
|
|
|
5
5
|
constructor(providers) {
|
|
6
6
|
this.providers = providers;
|
|
7
7
|
}
|
|
8
|
-
async project(generation, base, retained = new Map()) {
|
|
8
|
+
async project(generation, base, signal, retained = new Map()) {
|
|
9
9
|
// Slot HMR seeds this map with the committed projections. Only providers
|
|
10
10
|
// passed to this projector replace their entry; every other Feature keeps
|
|
11
11
|
// its live instance and therefore keeps its external resources running.
|
|
@@ -15,10 +15,13 @@ export class FeatureProjector {
|
|
|
15
15
|
const state = { ...base, projections };
|
|
16
16
|
try {
|
|
17
17
|
for (const provider of this.providers) {
|
|
18
|
+
signal.throwIfAborted();
|
|
18
19
|
const slots = [...base.capabilities.values()].filter((slot) => slot.feature === provider.id);
|
|
19
20
|
const projection = await provider.runtime.project(slots, {
|
|
20
21
|
snapshot: createSnapshotView(generation, state),
|
|
22
|
+
signal,
|
|
21
23
|
});
|
|
24
|
+
signal.throwIfAborted();
|
|
22
25
|
projections.set(provider.id, projection.value);
|
|
23
26
|
if (projection.dispose)
|
|
24
27
|
disposers.set(provider.id, projection.dispose);
|
package/lib/isolation.d.ts
CHANGED
|
@@ -24,5 +24,5 @@ export interface PreparedIsolatedPlugin {
|
|
|
24
24
|
}
|
|
25
25
|
/** Adapter seam for child Plugin lifecycle that must not execute in the Host realm. */
|
|
26
26
|
export interface IsolatedPluginRuntimePort {
|
|
27
|
-
prepare(request: IsolatedPluginPrepareRequest): Promise<PreparedIsolatedPlugin>;
|
|
27
|
+
prepare(request: IsolatedPluginPrepareRequest, signal: AbortSignal): Promise<PreparedIsolatedPlugin>;
|
|
28
28
|
}
|
|
@@ -2,7 +2,7 @@ import { readdirSync, statSync, watch as watchDirectory, } from 'node:fs';
|
|
|
2
2
|
import { extname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
const ignoredDirectories = new Set([
|
|
5
|
-
'.git', '.zhin', 'coverage', 'dist', 'lib', 'node_modules',
|
|
5
|
+
'.git', '.zhin', 'coverage', 'data', 'dist', 'lib', 'node_modules',
|
|
6
6
|
]);
|
|
7
7
|
const watchedExtensions = new Set([
|
|
8
8
|
'.cjs', '.js', '.json', '.md', '.mjs', '.ts', '.tsx', '.yaml', '.yml',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DisposeStack, Scope, type Dispose, type FeatureId, type GenerationHandoff, type GenerationHandoffRegistry, type PluginId, type PluginNodeSnapshot, type SetupCapabilityRegistration, type TokenId } from '@zhin.js/plugin-runtime';
|
|
1
|
+
import { DisposeStack, Scope, type Dispose, type FeatureId, type GenerationHandoff, type GenerationAdmissionGate, type GenerationHandoffRegistry, type PluginId, type PluginNodeSnapshot, type SetupCapabilityRegistration, type TokenId } from '@zhin.js/plugin-runtime';
|
|
2
2
|
import { type RuntimeEnvironment } from './environment.js';
|
|
3
3
|
import { type EnvironmentLayers } from './environment-store.js';
|
|
4
4
|
import type { ModuleRuntime } from './module-runtime.js';
|
|
@@ -8,10 +8,19 @@ import { type PrimaryConfig } from './primary-config.js';
|
|
|
8
8
|
import type { RuntimeConfigDocument } from './config-composer.js';
|
|
9
9
|
export type PluginConfigResolver = (node: PluginGraphNode) => unknown;
|
|
10
10
|
export interface RootResourceContext {
|
|
11
|
+
readonly signal: AbortSignal;
|
|
11
12
|
readonly resources: Scope;
|
|
12
13
|
readonly lifecycle: DisposeStack;
|
|
13
14
|
readonly handoff: GenerationHandoffRegistry;
|
|
15
|
+
/** Candidate-wide gate atomically switched by SnapshotStore at commit. */
|
|
16
|
+
readonly admission: GenerationAdmissionGate;
|
|
14
17
|
readonly config: PrimaryConfig;
|
|
18
|
+
/**
|
|
19
|
+
* Adds a root-owned capability to this shadow generation. The definition is
|
|
20
|
+
* validated and projected with convention/setup capabilities; it is never
|
|
21
|
+
* visible before the generation commits.
|
|
22
|
+
*/
|
|
23
|
+
addFeature<TDefinition>(feature: FeatureId | string, localName: string, definition: TDefinition): void;
|
|
15
24
|
}
|
|
16
25
|
export type RootResourceInstaller = (context: RootResourceContext) => void | Promise<void>;
|
|
17
26
|
export interface PluginAssemblySeed {
|
|
@@ -36,7 +45,7 @@ export declare class PluginScopeAssembler {
|
|
|
36
45
|
constructor(modules: ModuleRuntime, configResolver: PluginConfigResolver, environment: RuntimeEnvironment, primaryConfigDocument: RuntimeConfigDocument, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, seed?: PluginAssemblySeed, isolation?: IsolatedPluginRuntimePort | undefined);
|
|
37
46
|
removeSubtrees(roots: readonly PluginId[]): void;
|
|
38
47
|
installSetupFeatureAliases(aliases: ReadonlyMap<string, FeatureId>): void;
|
|
39
|
-
setupTree(node: PluginGraphNode): Promise<void>;
|
|
48
|
+
setupTree(node: PluginGraphNode, signal: AbortSignal): Promise<void>;
|
|
40
49
|
synchronizeTree(node: PluginGraphNode): void;
|
|
41
50
|
createdScopeDisposers(): readonly (readonly [PluginId, Dispose])[];
|
|
42
51
|
generationHandoff(): GenerationHandoff | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import { GenerationHandoffStack, Scope, capabilityId, createPluginDatabaseHost, createPluginScheduleHost, databaseHostToken, databaseRootHostToken, featureId, rootPluginId, scheduleHostToken, scheduleRootHostToken, unwrapPluginDatabaseHost, unwrapPluginScheduleHost, } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { GenerationHandoffStack, Scope, capabilityId, createGenerationAdmissionGate, createToken, createPluginDatabaseHost, createPluginScheduleHost, databaseHostToken, databaseRootHostToken, featureId, rootPluginId, scheduleHostToken, scheduleRootHostToken, unwrapPluginDatabaseHost, unwrapPluginScheduleHost, } from '@zhin.js/plugin-runtime';
|
|
3
3
|
import { runtimeEnvironmentToken } from './environment.js';
|
|
4
4
|
import { EnvStoreFactory, envStoreToken, } from './environment-store.js';
|
|
5
5
|
import { createPrimaryConfig, primaryConfigToken, } from './primary-config.js';
|
|
@@ -18,6 +18,7 @@ export class PluginScopeAssembler {
|
|
|
18
18
|
#setupCapabilities = new Map();
|
|
19
19
|
#created = [];
|
|
20
20
|
#handoffs = new GenerationHandoffStack();
|
|
21
|
+
#admission = createGenerationAdmissionGate();
|
|
21
22
|
#envStores;
|
|
22
23
|
#setupFeatureAliases = new Map();
|
|
23
24
|
constructor(modules, configResolver, environment, primaryConfigDocument, installResources, environmentLayers = {}, seed, isolation) {
|
|
@@ -48,7 +49,8 @@ export class PluginScopeAssembler {
|
|
|
48
49
|
installSetupFeatureAliases(aliases) {
|
|
49
50
|
this.#setupFeatureAliases = new Map(aliases);
|
|
50
51
|
}
|
|
51
|
-
async setupTree(node) {
|
|
52
|
+
async setupTree(node, signal) {
|
|
53
|
+
signal.throwIfAborted();
|
|
52
54
|
const manifest = node.package.packageJson.zhin;
|
|
53
55
|
const parentScope = node.parent ? this.scopes.get(node.parent) : undefined;
|
|
54
56
|
if (node.parent && !parentScope)
|
|
@@ -59,15 +61,20 @@ export class PluginScopeAssembler {
|
|
|
59
61
|
// Every owner shadows the inherited EnvStore with its exact overlay view.
|
|
60
62
|
const environment = this.#envStores.create(node.id);
|
|
61
63
|
scope.provide(envStoreToken, environment);
|
|
64
|
+
const register = (feature, localName, capabilityDefinition) => this.#registerSetupCapability(node, manifest, feature, localName, capabilityDefinition);
|
|
62
65
|
if (!node.parent) {
|
|
63
66
|
scope.provide(runtimeEnvironmentToken, this.environment);
|
|
67
|
+
scope.provide(rootGenerationAdmissionToken, this.#admission);
|
|
64
68
|
const config = createPrimaryConfig(this.primaryConfigDocument, environment);
|
|
65
69
|
scope.provide(primaryConfigToken, config);
|
|
66
70
|
await this.installResources?.({
|
|
71
|
+
signal,
|
|
67
72
|
resources: scope,
|
|
68
73
|
lifecycle: scope.disposers,
|
|
69
74
|
handoff: this.#handoffs,
|
|
75
|
+
admission: this.#admission,
|
|
70
76
|
config,
|
|
77
|
+
addFeature: register,
|
|
71
78
|
});
|
|
72
79
|
}
|
|
73
80
|
if (node.parent)
|
|
@@ -98,7 +105,8 @@ export class PluginScopeAssembler {
|
|
|
98
105
|
entry: resolve(node.package.root, manifest.entry),
|
|
99
106
|
config,
|
|
100
107
|
environment: this.environment,
|
|
101
|
-
});
|
|
108
|
+
}, signal);
|
|
109
|
+
signal.throwIfAborted();
|
|
102
110
|
// Ownership transfers to the shadow Scope immediately. Every later
|
|
103
111
|
// validation or binding failure is then covered by normal rollback.
|
|
104
112
|
scope.disposers.add(prepared.dispose);
|
|
@@ -114,6 +122,7 @@ export class PluginScopeAssembler {
|
|
|
114
122
|
}
|
|
115
123
|
else {
|
|
116
124
|
const module = await this.modules.load(resolve(node.package.root, manifest.entry));
|
|
125
|
+
signal.throwIfAborted();
|
|
117
126
|
const definition = module.default;
|
|
118
127
|
if (!definition || typeof definition.name !== 'string') {
|
|
119
128
|
throw new TypeError(`${node.package.name} does not default-export a Plugin definition`);
|
|
@@ -123,22 +132,8 @@ export class PluginScopeAssembler {
|
|
|
123
132
|
throw new Error(`Missing resource ${token.id} for Plugin ${node.id}`);
|
|
124
133
|
}
|
|
125
134
|
}
|
|
126
|
-
const register = (feature, localName, capabilityDefinition) => {
|
|
127
|
-
const featureName = featureId(String(feature));
|
|
128
|
-
const id = capabilityId(node.id, featureName, localName);
|
|
129
|
-
if (this.#setupCapabilities.has(id)) {
|
|
130
|
-
throw new Error(`Duplicate setup Capability: ${id}`);
|
|
131
|
-
}
|
|
132
|
-
this.#setupCapabilities.set(id, Object.freeze({
|
|
133
|
-
id,
|
|
134
|
-
owner: node.id,
|
|
135
|
-
feature: featureName,
|
|
136
|
-
localName,
|
|
137
|
-
source: resolve(node.package.root, manifest.entry),
|
|
138
|
-
definition: capabilityDefinition,
|
|
139
|
-
}));
|
|
140
|
-
};
|
|
141
135
|
const setupContext = {
|
|
136
|
+
signal,
|
|
142
137
|
plugin,
|
|
143
138
|
config: view,
|
|
144
139
|
resources: scope,
|
|
@@ -150,6 +145,7 @@ export class PluginScopeAssembler {
|
|
|
150
145
|
setupContext[method] = (name, value) => register(feature, name, value);
|
|
151
146
|
}
|
|
152
147
|
const returned = await definition.setup?.(Object.freeze(setupContext));
|
|
148
|
+
signal.throwIfAborted();
|
|
153
149
|
if (returned)
|
|
154
150
|
scope.disposers.add(returned);
|
|
155
151
|
metadata = definition.metadata;
|
|
@@ -167,7 +163,22 @@ export class PluginScopeAssembler {
|
|
|
167
163
|
this.config.set(node.id, config);
|
|
168
164
|
this.resources.set(node.id, scope.snapshot());
|
|
169
165
|
for (const child of node.children)
|
|
170
|
-
await this.setupTree(child);
|
|
166
|
+
await this.setupTree(child, signal);
|
|
167
|
+
}
|
|
168
|
+
#registerSetupCapability(node, manifest, feature, localName, definition) {
|
|
169
|
+
const featureName = featureId(String(feature));
|
|
170
|
+
const id = capabilityId(node.id, featureName, localName);
|
|
171
|
+
if (this.#setupCapabilities.has(id)) {
|
|
172
|
+
throw new Error(`Duplicate setup Capability: ${id}`);
|
|
173
|
+
}
|
|
174
|
+
this.#setupCapabilities.set(id, Object.freeze({
|
|
175
|
+
id,
|
|
176
|
+
owner: node.id,
|
|
177
|
+
feature: featureName,
|
|
178
|
+
localName,
|
|
179
|
+
source: resolve(node.package.root, manifest.entry),
|
|
180
|
+
definition,
|
|
181
|
+
}));
|
|
171
182
|
}
|
|
172
183
|
synchronizeTree(node) {
|
|
173
184
|
const current = this.tree.get(node.id);
|
|
@@ -222,6 +233,7 @@ export class PluginScopeAssembler {
|
|
|
222
233
|
}
|
|
223
234
|
}
|
|
224
235
|
}
|
|
236
|
+
const rootGenerationAdmissionToken = createToken('zhin.runtime.root-generation-admission');
|
|
225
237
|
function isWithin(plugin, root) {
|
|
226
238
|
return plugin === root || plugin.startsWith(`${root}/`);
|
|
227
239
|
}
|
package/lib/root-runtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ControlErrorHandler, type GenerationCommitListener, type PluginId, type RuntimeSnapshot, type SnapshotReader } from '@zhin.js/plugin-runtime';
|
|
2
2
|
import { type RuntimeConfigDocument } from './config-composer.js';
|
|
3
3
|
import { type ConfigDocumentPort } from './config-document.js';
|
|
4
4
|
import { type ConfigPatch } from './config-patch-planner.js';
|
|
@@ -24,9 +24,10 @@ export interface RootRuntimeOptions {
|
|
|
24
24
|
export type RootHmrOptions = Omit<HmrCoordinatorOptions, 'modules' | 'ownership' | 'runtime'>;
|
|
25
25
|
export declare class RootRuntime {
|
|
26
26
|
#private;
|
|
27
|
-
readonly controller: RootController;
|
|
28
27
|
constructor(options: RootRuntimeOptions);
|
|
29
28
|
get snapshot(): RuntimeSnapshot;
|
|
29
|
+
get snapshots(): SnapshotReader;
|
|
30
|
+
onGenerationCommit(listener: GenerationCommitListener): () => void;
|
|
30
31
|
get sourceOwnership(): SourceOwnershipIndex;
|
|
31
32
|
start(): Promise<RuntimeSnapshot>;
|
|
32
33
|
reload(target?: PluginId | string): Promise<RuntimeSnapshot>;
|
package/lib/root-runtime.js
CHANGED
|
@@ -23,7 +23,6 @@ import { SubtreeGenerationPreparer, SubtreeTopologyChangedError, } from './subtr
|
|
|
23
23
|
import { TopologyGenerationPreparer } from './topology-generation-preparer.js';
|
|
24
24
|
import { RestartBoundaryPlanner } from './restart-boundary.js';
|
|
25
25
|
export class RootRuntime {
|
|
26
|
-
controller;
|
|
27
26
|
#projectRoot;
|
|
28
27
|
#modules;
|
|
29
28
|
#environment;
|
|
@@ -37,6 +36,8 @@ export class RootRuntime {
|
|
|
37
36
|
#ownership = SourceOwnershipIndex.empty();
|
|
38
37
|
#model;
|
|
39
38
|
#configPatchTail = Promise.resolve();
|
|
39
|
+
#stopResult;
|
|
40
|
+
#controller;
|
|
40
41
|
constructor(options) {
|
|
41
42
|
this.#projectRoot = resolve(options.projectRoot);
|
|
42
43
|
this.#modules = options.modules;
|
|
@@ -50,10 +51,16 @@ export class RootRuntime {
|
|
|
50
51
|
this.#configDocument = structuredClone(options.config ?? {});
|
|
51
52
|
this.#installResources = options.installResources;
|
|
52
53
|
this.#isolation = options.isolation;
|
|
53
|
-
this
|
|
54
|
+
this.#controller = new RootController(emptyState(), options.onControlError);
|
|
54
55
|
}
|
|
55
56
|
get snapshot() {
|
|
56
|
-
return this
|
|
57
|
+
return this.#controller.snapshot;
|
|
58
|
+
}
|
|
59
|
+
get snapshots() {
|
|
60
|
+
return this.#controller.snapshots;
|
|
61
|
+
}
|
|
62
|
+
onGenerationCommit(listener) {
|
|
63
|
+
return this.#controller.onGenerationCommit(listener);
|
|
57
64
|
}
|
|
58
65
|
get sourceOwnership() {
|
|
59
66
|
return this.#ownership;
|
|
@@ -65,8 +72,8 @@ export class RootRuntime {
|
|
|
65
72
|
this.#configDocument = structuredClone(snapshot.document);
|
|
66
73
|
}
|
|
67
74
|
let prepared;
|
|
68
|
-
const snapshot = await this
|
|
69
|
-
prepared = await this.#prepare(current);
|
|
75
|
+
const snapshot = await this.#controller.start(async (current, signal) => {
|
|
76
|
+
prepared = await this.#prepare(current, signal);
|
|
70
77
|
return prepared.generation;
|
|
71
78
|
});
|
|
72
79
|
this.#accept(requirePrepared(prepared));
|
|
@@ -74,8 +81,8 @@ export class RootRuntime {
|
|
|
74
81
|
}
|
|
75
82
|
async reload(target = rootPluginId()) {
|
|
76
83
|
let prepared;
|
|
77
|
-
const snapshot = await this
|
|
78
|
-
prepared = await this.#prepare(current);
|
|
84
|
+
const snapshot = await this.#controller.reload(target, async (current, signal) => {
|
|
85
|
+
prepared = await this.#prepare(current, signal);
|
|
79
86
|
return prepared.generation;
|
|
80
87
|
});
|
|
81
88
|
this.#accept(requirePrepared(prepared));
|
|
@@ -103,18 +110,24 @@ export class RootRuntime {
|
|
|
103
110
|
createProcessRestartExecutor(adapter) {
|
|
104
111
|
return new RootProcessRestartExecutor(this, adapter);
|
|
105
112
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
stop() {
|
|
114
|
+
if (this.#stopResult)
|
|
115
|
+
return this.#stopResult;
|
|
116
|
+
const result = (async () => {
|
|
117
|
+
try {
|
|
118
|
+
await this.#controller.stop();
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
await this.#modules.close();
|
|
122
|
+
}
|
|
123
|
+
})();
|
|
124
|
+
this.#stopResult = result;
|
|
125
|
+
return result;
|
|
113
126
|
}
|
|
114
127
|
async #reloadPlan(plan) {
|
|
115
128
|
let prepared;
|
|
116
129
|
let restart;
|
|
117
|
-
const snapshot = await this
|
|
130
|
+
const snapshot = await this.#controller.reload(plan.subtrees[0] ?? plan.slots[0] ?? rootPluginId(), async (current, signal) => {
|
|
118
131
|
const resolved = await this.#resolveCapabilityDelta(current, plan);
|
|
119
132
|
const effective = resolved.plan;
|
|
120
133
|
if (this.#model && effective.manifestSources.length > 0) {
|
|
@@ -123,19 +136,19 @@ export class RootRuntime {
|
|
|
123
136
|
if (restart)
|
|
124
137
|
return undefined;
|
|
125
138
|
prepared = effective.subtrees.includes(rootPluginId())
|
|
126
|
-
? await this.#prepareInspected(current, inspected)
|
|
127
|
-
: await new TopologyGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation).prepare(current, { ...effective, capabilities: resolved.capabilities });
|
|
139
|
+
? await this.#prepareInspected(current, inspected, signal)
|
|
140
|
+
: await new TopologyGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation).prepare(current, signal, { ...effective, capabilities: resolved.capabilities });
|
|
128
141
|
}
|
|
129
142
|
else if (effective.subtrees.length === 0 && resolved.capabilities.size > 0 && this.#model) {
|
|
130
143
|
prepared = await new SlotGenerationPreparer(this.#modules, this.#model)
|
|
131
|
-
.prepare(current, resolved.capabilities);
|
|
144
|
+
.prepare(current, resolved.capabilities, signal);
|
|
132
145
|
}
|
|
133
146
|
else if (this.#model && this.#canPrepareSubtrees(effective)) {
|
|
134
147
|
const inspected = await this.#inspectProject();
|
|
135
|
-
prepared = await this.#prepareSubtrees(current, inspected, effective.subtrees);
|
|
148
|
+
prepared = await this.#prepareSubtrees(current, inspected, effective.subtrees, signal);
|
|
136
149
|
}
|
|
137
150
|
else {
|
|
138
|
-
prepared = await this.#prepare(current);
|
|
151
|
+
prepared = await this.#prepare(current, signal);
|
|
139
152
|
}
|
|
140
153
|
return prepared?.generation;
|
|
141
154
|
});
|
|
@@ -176,8 +189,11 @@ export class RootRuntime {
|
|
|
176
189
|
return records.length > 0 && records.every((record) => record.role === 'plugin' || record.role === 'schema');
|
|
177
190
|
});
|
|
178
191
|
}
|
|
179
|
-
async #prepare(current) {
|
|
180
|
-
|
|
192
|
+
async #prepare(current, signal) {
|
|
193
|
+
signal.throwIfAborted();
|
|
194
|
+
const inspected = await this.#inspectProject();
|
|
195
|
+
signal.throwIfAborted();
|
|
196
|
+
return this.#prepareInspected(current, inspected, signal);
|
|
181
197
|
}
|
|
182
198
|
async #inspectProject() {
|
|
183
199
|
const resolver = await NodePackageResolver.create(this.#projectRoot);
|
|
@@ -234,7 +250,8 @@ export class RootRuntime {
|
|
|
234
250
|
let prepared;
|
|
235
251
|
let documentTransaction;
|
|
236
252
|
let committedDocument;
|
|
237
|
-
const snapshot = await this
|
|
253
|
+
const snapshot = await this.#controller.reload(rootPluginId(), async (current, signal) => {
|
|
254
|
+
signal.throwIfAborted();
|
|
238
255
|
const resolver = await NodePackageResolver.create(this.#projectRoot);
|
|
239
256
|
const graph = await new ProjectGraphService(resolver).inspect(this.#projectRoot);
|
|
240
257
|
const planned = await new ConfigPatchPlanner().plan(graph, currentDocument, patches);
|
|
@@ -269,10 +286,10 @@ export class RootRuntime {
|
|
|
269
286
|
// when present so a committed patch cannot leave Host services on the
|
|
270
287
|
// previous generation's document.
|
|
271
288
|
if (!this.#installResources && this.#model && !planned.roots.includes(rootPluginId())) {
|
|
272
|
-
prepared = await this.#prepareSubtrees(current, inspected, planned.roots);
|
|
289
|
+
prepared = await this.#prepareSubtrees(current, inspected, planned.roots, signal);
|
|
273
290
|
}
|
|
274
291
|
else {
|
|
275
|
-
prepared = await this.#prepareInspected(current, inspected);
|
|
292
|
+
prepared = await this.#prepareInspected(current, inspected, signal);
|
|
276
293
|
}
|
|
277
294
|
return documentTransaction
|
|
278
295
|
? withConfigDocumentHandoff(prepared.generation, documentTransaction, (committed) => { committedDocument = committed; })
|
|
@@ -295,20 +312,20 @@ export class RootRuntime {
|
|
|
295
312
|
this.#configSnapshot = committedDocument;
|
|
296
313
|
return snapshot;
|
|
297
314
|
}
|
|
298
|
-
#prepareInspected(current, inspected) {
|
|
315
|
+
#prepareInspected(current, inspected, signal) {
|
|
299
316
|
const assembler = new GenerationAssembler(inspected.graph, this.#modules, inspected.configResolver, inspected.primaryConfigDocument, current.generation + 1, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation);
|
|
300
|
-
return assembler.prepare();
|
|
317
|
+
return assembler.prepare(signal);
|
|
301
318
|
}
|
|
302
|
-
async #prepareSubtrees(current, inspected, roots) {
|
|
319
|
+
async #prepareSubtrees(current, inspected, roots, signal) {
|
|
303
320
|
if (!this.#model)
|
|
304
|
-
return this.#prepareInspected(current, inspected);
|
|
321
|
+
return this.#prepareInspected(current, inspected, signal);
|
|
305
322
|
try {
|
|
306
|
-
return await new SubtreeGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation).prepare(current, roots);
|
|
323
|
+
return await new SubtreeGenerationPreparer(this.#modules, this.#model, inspected.graph, inspected.configResolver, inspected.primaryConfigDocument, this.#environment, this.#installResources, this.#environmentLayers, this.#isolation).prepare(current, roots, signal);
|
|
307
324
|
}
|
|
308
325
|
catch (error) {
|
|
309
326
|
if (!(error instanceof SubtreeTopologyChangedError))
|
|
310
327
|
throw error;
|
|
311
|
-
return this.#prepareInspected(current, inspected);
|
|
328
|
+
return this.#prepareInspected(current, inspected, signal);
|
|
312
329
|
}
|
|
313
330
|
}
|
|
314
331
|
}
|
|
@@ -326,8 +343,10 @@ function withConfigDocumentHandoff(generation, document, committed) {
|
|
|
326
343
|
// File commit follows Resource activation, so reverse compensation restores
|
|
327
344
|
// the document before it deactivates the shadow generation.
|
|
328
345
|
handoffs.add({
|
|
329
|
-
async activateNext() {
|
|
346
|
+
async activateNext(signal) {
|
|
347
|
+
signal.throwIfAborted();
|
|
330
348
|
committed(await document.commit());
|
|
349
|
+
signal.throwIfAborted();
|
|
331
350
|
},
|
|
332
351
|
deactivateNext: () => document.rollback(),
|
|
333
352
|
});
|
|
@@ -389,16 +408,17 @@ class GenerationAssembler {
|
|
|
389
408
|
this.#host = new NodeDiscoveryHost(modules);
|
|
390
409
|
this.#plugins = new PluginScopeAssembler(modules, configResolver, environment, primaryConfigDocument, installResources, environmentLayers, undefined, isolation);
|
|
391
410
|
}
|
|
392
|
-
async prepare() {
|
|
411
|
+
async prepare(signal) {
|
|
412
|
+
signal.throwIfAborted();
|
|
393
413
|
try {
|
|
394
414
|
// Prepare is deliberately ordered: providers define discovery, setup
|
|
395
415
|
// creates owner scopes, then definitions can be projected against both.
|
|
396
|
-
await this.#loadProviders(this.graph.root);
|
|
416
|
+
await this.#loadProviders(this.graph.root, signal);
|
|
397
417
|
this.#plugins.installSetupFeatureAliases(featureSetupAliases(this.#catalog.values()));
|
|
398
|
-
await this.#plugins.setupTree(this.graph.root);
|
|
399
|
-
await this.#discover();
|
|
418
|
+
await this.#plugins.setupTree(this.graph.root, signal);
|
|
419
|
+
await this.#discover(signal);
|
|
400
420
|
const projected = await new FeatureProjector(this.#catalog.values())
|
|
401
|
-
.project(this.generation, this.#projectionState());
|
|
421
|
+
.project(this.generation, this.#projectionState(), signal);
|
|
402
422
|
for (const [feature, dispose] of projected.disposers) {
|
|
403
423
|
this.#projectionDisposers.set(feature, dispose);
|
|
404
424
|
}
|
|
@@ -431,10 +451,12 @@ class GenerationAssembler {
|
|
|
431
451
|
throw error;
|
|
432
452
|
}
|
|
433
453
|
}
|
|
434
|
-
async #loadProviders(node) {
|
|
454
|
+
async #loadProviders(node, signal) {
|
|
455
|
+
signal.throwIfAborted();
|
|
435
456
|
for (const requirement of node.features) {
|
|
436
457
|
const manifest = requirement.package.packageJson.zhin;
|
|
437
458
|
const module = await this.modules.load(resolve(requirement.package.root, manifest.entry));
|
|
459
|
+
signal.throwIfAborted();
|
|
438
460
|
const provider = module.default;
|
|
439
461
|
if (!provider || provider.protocol !== 1) {
|
|
440
462
|
throw new TypeError(`${requirement.package.name} does not default-export a Feature provider`);
|
|
@@ -451,14 +473,16 @@ class GenerationAssembler {
|
|
|
451
473
|
this.#rootsByFeature.set(provider.id, roots);
|
|
452
474
|
}
|
|
453
475
|
for (const child of node.children)
|
|
454
|
-
await this.#loadProviders(child);
|
|
476
|
+
await this.#loadProviders(child, signal);
|
|
455
477
|
}
|
|
456
|
-
async #discover() {
|
|
478
|
+
async #discover(signal) {
|
|
457
479
|
mergeSetupCapabilities(this.#capabilities, this.#plugins.setupCapabilities(), new Map(this.#catalog.values().map((provider) => [provider.id, provider])), this.#rootsByFeature);
|
|
458
480
|
const discovery = new FeatureDiscovery(this.#host);
|
|
459
481
|
for (const provider of this.#catalog.values()) {
|
|
482
|
+
signal.throwIfAborted();
|
|
460
483
|
const roots = this.#rootsByFeature.get(provider.id) ?? [];
|
|
461
484
|
const slots = await discovery.discover(provider, roots);
|
|
485
|
+
signal.throwIfAborted();
|
|
462
486
|
for (const slot of slots)
|
|
463
487
|
addCapabilitySlot(this.#capabilities, slot);
|
|
464
488
|
}
|
|
@@ -6,5 +6,5 @@ export declare class SlotGenerationPreparer {
|
|
|
6
6
|
private readonly modules;
|
|
7
7
|
private readonly model;
|
|
8
8
|
constructor(modules: ModuleRuntime, model: RuntimeGenerationModel);
|
|
9
|
-
prepare(current: RuntimeSnapshot, selected: readonly CapabilityId[] | CapabilityDelta): Promise<PreparedRuntimeGeneration>;
|
|
9
|
+
prepare(current: RuntimeSnapshot, selected: readonly CapabilityId[] | CapabilityDelta, signal: AbortSignal): Promise<PreparedRuntimeGeneration>;
|
|
10
10
|
}
|
|
@@ -11,7 +11,8 @@ export class SlotGenerationPreparer {
|
|
|
11
11
|
this.modules = modules;
|
|
12
12
|
this.model = model;
|
|
13
13
|
}
|
|
14
|
-
async prepare(current, selected) {
|
|
14
|
+
async prepare(current, selected, signal) {
|
|
15
|
+
signal.throwIfAborted();
|
|
15
16
|
const selectedByFeature = Array.isArray(selected)
|
|
16
17
|
? capabilityDeltaFromSlots(current, selected)
|
|
17
18
|
: selected;
|
|
@@ -24,6 +25,7 @@ export class SlotGenerationPreparer {
|
|
|
24
25
|
for (const id of ids)
|
|
25
26
|
capabilities.delete(id);
|
|
26
27
|
const replacements = await discovery.discover(provider, this.model.rootsByFeature.get(feature) ?? [], { capabilities: ids });
|
|
28
|
+
signal.throwIfAborted();
|
|
27
29
|
for (const slot of replacements)
|
|
28
30
|
capabilities.set(slot.id, slot);
|
|
29
31
|
}
|
|
@@ -41,7 +43,7 @@ export class SlotGenerationPreparer {
|
|
|
41
43
|
config: current.config,
|
|
42
44
|
resources: current.resources,
|
|
43
45
|
capabilities,
|
|
44
|
-
}, current.projections);
|
|
46
|
+
}, signal, current.projections);
|
|
45
47
|
try {
|
|
46
48
|
const snapshot = createSnapshotView(current.generation + 1, projected.state);
|
|
47
49
|
const ownership = SourceOwnershipIndex.fromGeneration(this.model.graph, snapshot, this.model.featureIdsByPackageRoot);
|
|
@@ -22,5 +22,5 @@ export declare class SubtreeGenerationPreparer {
|
|
|
22
22
|
private readonly environmentLayers;
|
|
23
23
|
private readonly isolation?;
|
|
24
24
|
constructor(modules: ModuleRuntime, model: RuntimeGenerationModel, graph: ProjectGraph, configResolver: PluginConfigResolver, primaryConfigDocument: RuntimeConfigDocument, environment: RuntimeEnvironment, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, isolation?: IsolatedPluginRuntimePort | undefined);
|
|
25
|
-
prepare(current: RuntimeSnapshot, roots: readonly PluginId[]): Promise<PreparedRuntimeGeneration>;
|
|
25
|
+
prepare(current: RuntimeSnapshot, roots: readonly PluginId[], signal: AbortSignal): Promise<PreparedRuntimeGeneration>;
|
|
26
26
|
}
|
|
@@ -33,7 +33,8 @@ export class SubtreeGenerationPreparer {
|
|
|
33
33
|
this.environmentLayers = environmentLayers;
|
|
34
34
|
this.isolation = isolation;
|
|
35
35
|
}
|
|
36
|
-
async prepare(current, roots) {
|
|
36
|
+
async prepare(current, roots, signal) {
|
|
37
|
+
signal.throwIfAborted();
|
|
37
38
|
const nodes = indexGraph(this.graph);
|
|
38
39
|
assertCompatibleTopology(indexGraph(this.model.graph), nodes, roots);
|
|
39
40
|
const plugins = new PluginScopeAssembler(this.modules, this.configResolver, this.environment, this.primaryConfigDocument, this.installResources, this.environmentLayers, {
|
|
@@ -50,7 +51,7 @@ export class SubtreeGenerationPreparer {
|
|
|
50
51
|
const node = nodes.get(root);
|
|
51
52
|
if (!node)
|
|
52
53
|
throw new SubtreeTopologyChangedError(`Missing subtree root: ${root}`);
|
|
53
|
-
await plugins.setupTree(node);
|
|
54
|
+
await plugins.setupTree(node, signal);
|
|
54
55
|
}
|
|
55
56
|
const capabilities = new Map(current.capabilities);
|
|
56
57
|
for (const [id, slot] of capabilities) {
|
|
@@ -65,6 +66,7 @@ export class SubtreeGenerationPreparer {
|
|
|
65
66
|
if (affectedRoots.length === 0)
|
|
66
67
|
continue;
|
|
67
68
|
const slots = await discovery.discover(provider, affectedRoots);
|
|
69
|
+
signal.throwIfAborted();
|
|
68
70
|
for (const slot of slots)
|
|
69
71
|
addCapabilitySlot(capabilities, slot);
|
|
70
72
|
}
|
|
@@ -74,7 +76,7 @@ export class SubtreeGenerationPreparer {
|
|
|
74
76
|
config: plugins.config,
|
|
75
77
|
resources: plugins.resources,
|
|
76
78
|
capabilities,
|
|
77
|
-
});
|
|
79
|
+
}, signal);
|
|
78
80
|
for (const [feature, dispose] of projected.disposers) {
|
|
79
81
|
projectionDisposers.set(feature, dispose);
|
|
80
82
|
}
|
|
@@ -27,5 +27,5 @@ export declare class TopologyGenerationPreparer {
|
|
|
27
27
|
private readonly environmentLayers;
|
|
28
28
|
private readonly isolation?;
|
|
29
29
|
constructor(modules: ModuleRuntime, model: RuntimeGenerationModel, graph: ProjectGraph, configResolver: PluginConfigResolver, primaryConfigDocument: RuntimeConfigDocument, environment: RuntimeEnvironment, installResources?: RootResourceInstaller | undefined, environmentLayers?: EnvironmentLayers, isolation?: IsolatedPluginRuntimePort | undefined);
|
|
30
|
-
prepare(current: RuntimeSnapshot, delta?: TopologyRuntimeDelta): Promise<PreparedRuntimeGeneration | undefined>;
|
|
30
|
+
prepare(current: RuntimeSnapshot, signal: AbortSignal, delta?: TopologyRuntimeDelta): Promise<PreparedRuntimeGeneration | undefined>;
|
|
31
31
|
}
|
|
@@ -31,7 +31,8 @@ export class TopologyGenerationPreparer {
|
|
|
31
31
|
this.environmentLayers = environmentLayers;
|
|
32
32
|
this.isolation = isolation;
|
|
33
33
|
}
|
|
34
|
-
async prepare(current, delta = { slots: [], subtrees: [] }) {
|
|
34
|
+
async prepare(current, signal, delta = { slots: [], subtrees: [] }) {
|
|
35
|
+
signal.throwIfAborted();
|
|
35
36
|
const planned = new TopologyTransactionPlanner().plan(this.model.graph, this.graph);
|
|
36
37
|
const nextNodes = graphNodes(this.graph);
|
|
37
38
|
const configReplacements = changedConfigRoots(current, nextNodes, this.configResolver);
|
|
@@ -69,7 +70,7 @@ export class TopologyGenerationPreparer {
|
|
|
69
70
|
const node = nextNodes.get(root);
|
|
70
71
|
if (!node)
|
|
71
72
|
throw new Error(`Missing topology setup root: ${root}`);
|
|
72
|
-
await plugins.setupTree(node);
|
|
73
|
+
await plugins.setupTree(node, signal);
|
|
73
74
|
}
|
|
74
75
|
// Retained parents still need a new immutable children view after add,
|
|
75
76
|
// remove, move, or reorder operations.
|
|
@@ -82,7 +83,7 @@ export class TopologyGenerationPreparer {
|
|
|
82
83
|
config: plugins.config,
|
|
83
84
|
resources: plugins.resources,
|
|
84
85
|
capabilities,
|
|
85
|
-
});
|
|
86
|
+
}, signal);
|
|
86
87
|
for (const [feature, dispose] of projected.disposers) {
|
|
87
88
|
projectionDisposers.set(feature, dispose);
|
|
88
89
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/runtime",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Static Plugin graph, generation transaction and HMR Root runtime for Zhin.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -18,22 +18,22 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"ajv": "8.18.0",
|
|
20
20
|
"semver": "7.8.5",
|
|
21
|
-
"@zhin.js/feature-kit": "1.0.
|
|
22
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
21
|
+
"@zhin.js/feature-kit": "1.0.9",
|
|
22
|
+
"@zhin.js/plugin-runtime": "1.1.6"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^26.1.2",
|
|
26
26
|
"typescript": "^6.0.3",
|
|
27
|
-
"@zhin.js/adapter": "1.1.
|
|
28
|
-
"@zhin.js/agent-feature": "1.0.
|
|
29
|
-
"@zhin.js/command": "1.0.
|
|
30
|
-
"@zhin.js/component": "1.0.
|
|
31
|
-
"@zhin.js/layout": "1.0.
|
|
32
|
-
"@zhin.js/mcp-feature": "1.0.
|
|
33
|
-
"@zhin.js/
|
|
34
|
-
"@zhin.js/
|
|
35
|
-
"@zhin.js/skill": "1.0.
|
|
36
|
-
"@zhin.js/tool": "1.0.
|
|
27
|
+
"@zhin.js/adapter": "1.1.8",
|
|
28
|
+
"@zhin.js/agent-feature": "1.0.9",
|
|
29
|
+
"@zhin.js/command": "1.0.12",
|
|
30
|
+
"@zhin.js/component": "1.0.9",
|
|
31
|
+
"@zhin.js/layout": "1.0.9",
|
|
32
|
+
"@zhin.js/mcp-feature": "1.0.9",
|
|
33
|
+
"@zhin.js/page": "1.0.9",
|
|
34
|
+
"@zhin.js/middleware": "1.0.9",
|
|
35
|
+
"@zhin.js/skill": "1.0.9",
|
|
36
|
+
"@zhin.js/tool": "1.0.9"
|
|
37
37
|
},
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|