@tangle-network/agent-profile-materialize 0.23.1 → 0.25.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/candidate.js +1 -0
- package/dist/harness.js +6 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp-config-ownership.d.ts +31 -0
- package/dist/mcp-config-ownership.js +79 -0
- package/dist/profile-bundle-cache.d.ts +2 -2
- package/dist/profile-bundle-cache.js +8 -6
- package/dist/profile-bundle-lru.d.ts +5 -6
- package/dist/profile-bundle-lru.js +19 -10
- package/dist/profile-materializer.js +4 -0
- package/dist/workspace-files.d.ts +1 -1
- package/package.json +4 -4
package/dist/candidate.js
CHANGED
package/dist/harness.js
CHANGED
|
@@ -348,14 +348,15 @@ export function materializerReplacement(relPath, content, plannedContent) {
|
|
|
348
348
|
* there.
|
|
349
349
|
*
|
|
350
350
|
* An `opencode.json` this package wrote holds the opencode schema id, only the
|
|
351
|
-
* keys this package writes, and only instruction paths this package invented
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
* of the user's own, is refused.
|
|
351
|
+
* keys this package writes, and only instruction paths this package invented;
|
|
352
|
+
* a user config holding nothing else is replaced too, any other key or own
|
|
353
|
+
* instruction path is refused.
|
|
355
354
|
*/
|
|
356
355
|
function isMaterializerOutput(relPath, content, plannedContent) {
|
|
357
356
|
if (MATERIALIZER_RESERVED_PATHS.has(relPath))
|
|
358
357
|
return true;
|
|
358
|
+
if (relPath === ".pi/mcp.json" || relPath === ".kimi/mcp.json")
|
|
359
|
+
return isMaterializerMcpConfig(relPath, content);
|
|
359
360
|
if (MATERIALIZER_SUBAGENT_PATH.test(relPath)) {
|
|
360
361
|
const existing = content();
|
|
361
362
|
if (existing === undefined)
|
|
@@ -435,3 +436,4 @@ export function skillDirForHarness(harness) {
|
|
|
435
436
|
return HARNESS_SKILL_DIRS[harness];
|
|
436
437
|
}
|
|
437
438
|
import { createHash } from "node:crypto";
|
|
439
|
+
import { isMaterializerMcpConfig } from "./mcp-config-ownership.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./harness-prompt.js";
|
|
|
7
7
|
export { type ConcurrentProfileRevision, ensurePublicConcurrentProfileRoots, type ManagedWorkspaceFileDigest, type ManagedWorkspaceManifest, preparePublicConcurrentProfileRevision, } from "./managed-workspace.js";
|
|
8
8
|
export type { OpenCodeMcpConfig } from "./mcp.js";
|
|
9
9
|
export { openCodeMcpConfig } from "./mcp.js";
|
|
10
|
+
export { isMaterializerMcpConfig } from "./mcp-config-ownership.js";
|
|
10
11
|
export { modelIdsMatch, qualifyModelId, unqualifyModelId, } from "./model-id.js";
|
|
11
12
|
export { buildPiModelsConfig, buildPiModelsConfigFile, PI_MODELS_CONFIG_PATH, type PiModelsConfigFile, type PiModelsConfigInput, type PiRuntimePaths, resolvePiRuntimePaths, } from "./pi-models-config.js";
|
|
12
13
|
export { applyWorkspacePlan, applyWorkspacePlanWithSecretProvider, } from "./plan-application.js";
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { HARNESS_SKILL_DIRS, harnessAutoLoadsAgentContextFile, harnessAutoLoadsC
|
|
|
4
4
|
export * from "./harness-prompt.js";
|
|
5
5
|
export { ensurePublicConcurrentProfileRoots, preparePublicConcurrentProfileRevision, } from "./managed-workspace.js";
|
|
6
6
|
export { openCodeMcpConfig } from "./mcp.js";
|
|
7
|
+
export { isMaterializerMcpConfig } from "./mcp-config-ownership.js";
|
|
7
8
|
export { modelIdsMatch, qualifyModelId, unqualifyModelId, } from "./model-id.js";
|
|
8
9
|
export { buildPiModelsConfig, buildPiModelsConfigFile, PI_MODELS_CONFIG_PATH, resolvePiRuntimePaths, } from "./pi-models-config.js";
|
|
9
10
|
export { applyWorkspacePlan, applyWorkspacePlanWithSecretProvider, } from "./plan-application.js";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness MCP config files this package generates wholesale from
|
|
3
|
+
* `profile.mcp`: exactly one `mcpServers` object whose entries carry only
|
|
4
|
+
* the fields the generators write. Recognizable by that shape alone (no
|
|
5
|
+
* schema id to key on), so an existing file holding ONLY those registrations
|
|
6
|
+
* is this package's earlier output and a new plan replaces it — a per-turn
|
|
7
|
+
* profile changes these bytes routinely (physim's MCP env carries turn
|
|
8
|
+
* state). Anything else at the path (a user's own keys, extra fields) still
|
|
9
|
+
* refuses: the name alone proves nothing, same rule as `opencode.json`.
|
|
10
|
+
*/
|
|
11
|
+
/** Paths owned by this rule. `.gemini/settings.json` is deliberately absent:
|
|
12
|
+
* it is a broader user settings file the package only contributes one key
|
|
13
|
+
* to, so its bytes cannot prove authorship. */
|
|
14
|
+
export declare const MATERIALIZER_MCP_CONFIG_PATHS: Set<string>;
|
|
15
|
+
/** The parsed form this rule accepts: `JSON.parse` output narrowed at the
|
|
16
|
+
* I/O boundary by {@link parseMcpServersConfig}. */
|
|
17
|
+
export interface McpServersConfig {
|
|
18
|
+
mcpServers: Record<string, Record<string, unknown>>;
|
|
19
|
+
}
|
|
20
|
+
/** Parse workspace bytes into the one shape this rule owns, or `null` when
|
|
21
|
+
* the bytes are anything else (not an object, other top-level keys, or
|
|
22
|
+
* server entries that are not objects). The I/O boundary for
|
|
23
|
+
* {@link isGeneratedMcpConfigShape}. */
|
|
24
|
+
export declare function parseMcpServersConfig(bytes: string): McpServersConfig | null;
|
|
25
|
+
/** Whether a parsed MCP config carries ONLY the registrations this package
|
|
26
|
+
* generates: no extra top-level keys, and every server entry holds only
|
|
27
|
+
* generated fields. A user's own key anywhere makes this `false`. */
|
|
28
|
+
export declare function isGeneratedMcpConfigShape(config: McpServersConfig): boolean;
|
|
29
|
+
/** Whether the existing file at `relPath` is this package's own earlier MCP
|
|
30
|
+
* config output (shape rule above). `undefined` content = no regular file. */
|
|
31
|
+
export declare function isMaterializerMcpConfig(relPath: string, content: () => string | undefined): boolean;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness MCP config files this package generates wholesale from
|
|
3
|
+
* `profile.mcp`: exactly one `mcpServers` object whose entries carry only
|
|
4
|
+
* the fields the generators write. Recognizable by that shape alone (no
|
|
5
|
+
* schema id to key on), so an existing file holding ONLY those registrations
|
|
6
|
+
* is this package's earlier output and a new plan replaces it — a per-turn
|
|
7
|
+
* profile changes these bytes routinely (physim's MCP env carries turn
|
|
8
|
+
* state). Anything else at the path (a user's own keys, extra fields) still
|
|
9
|
+
* refuses: the name alone proves nothing, same rule as `opencode.json`.
|
|
10
|
+
*/
|
|
11
|
+
/** Paths owned by this rule. `.gemini/settings.json` is deliberately absent:
|
|
12
|
+
* it is a broader user settings file the package only contributes one key
|
|
13
|
+
* to, so its bytes cannot prove authorship. */
|
|
14
|
+
export const MATERIALIZER_MCP_CONFIG_PATHS = new Set([
|
|
15
|
+
".pi/mcp.json",
|
|
16
|
+
".kimi/mcp.json",
|
|
17
|
+
]);
|
|
18
|
+
const GENERATED_MCP_SERVER_KEYS = new Set([
|
|
19
|
+
"command",
|
|
20
|
+
"args",
|
|
21
|
+
"env",
|
|
22
|
+
"cwd",
|
|
23
|
+
"url",
|
|
24
|
+
"httpUrl",
|
|
25
|
+
"headers",
|
|
26
|
+
"transport",
|
|
27
|
+
"enabled",
|
|
28
|
+
"directTools",
|
|
29
|
+
]);
|
|
30
|
+
/** Parse workspace bytes into the one shape this rule owns, or `null` when
|
|
31
|
+
* the bytes are anything else (not an object, other top-level keys, or
|
|
32
|
+
* server entries that are not objects). The I/O boundary for
|
|
33
|
+
* {@link isGeneratedMcpConfigShape}. */
|
|
34
|
+
export function parseMcpServersConfig(bytes) {
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = JSON.parse(bytes);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const { mcpServers } = parsed;
|
|
46
|
+
if (mcpServers === null ||
|
|
47
|
+
typeof mcpServers !== "object" ||
|
|
48
|
+
Array.isArray(mcpServers)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
for (const server of Object.values(mcpServers)) {
|
|
52
|
+
if (server === null ||
|
|
53
|
+
typeof server !== "object" ||
|
|
54
|
+
Array.isArray(server)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { mcpServers: mcpServers };
|
|
59
|
+
}
|
|
60
|
+
/** Whether a parsed MCP config carries ONLY the registrations this package
|
|
61
|
+
* generates: no extra top-level keys, and every server entry holds only
|
|
62
|
+
* generated fields. A user's own key anywhere makes this `false`. */
|
|
63
|
+
export function isGeneratedMcpConfigShape(config) {
|
|
64
|
+
const keys = Object.keys(config);
|
|
65
|
+
if (keys.length !== 1 || keys[0] !== "mcpServers")
|
|
66
|
+
return false;
|
|
67
|
+
return Object.values(config.mcpServers).every((server) => Object.keys(server).every((key) => GENERATED_MCP_SERVER_KEYS.has(key)));
|
|
68
|
+
}
|
|
69
|
+
/** Whether the existing file at `relPath` is this package's own earlier MCP
|
|
70
|
+
* config output (shape rule above). `undefined` content = no regular file. */
|
|
71
|
+
export function isMaterializerMcpConfig(relPath, content) {
|
|
72
|
+
if (!MATERIALIZER_MCP_CONFIG_PATHS.has(relPath))
|
|
73
|
+
return false;
|
|
74
|
+
const bytes = content();
|
|
75
|
+
if (bytes === undefined)
|
|
76
|
+
return false;
|
|
77
|
+
const config = parseMcpServersConfig(bytes);
|
|
78
|
+
return config !== null && isGeneratedMcpConfigShape(config);
|
|
79
|
+
}
|
|
@@ -29,8 +29,8 @@ export declare function claimProfileBundle(root: string, claim: ProfileBundleCla
|
|
|
29
29
|
}) => Promise<boolean>): Promise<ProfileBundleClaimResult>;
|
|
30
30
|
/**
|
|
31
31
|
* Open a host-side cache handle over `root`, creating the layout and sweeping
|
|
32
|
-
* stale `incoming/` residue synchronously
|
|
33
|
-
*
|
|
32
|
+
* stale `incoming/` residue synchronously when the root is idle. A busy root
|
|
33
|
+
* leaves cleanup to its serialized admissions and sweeps.
|
|
34
34
|
*/
|
|
35
35
|
export declare function openProfileBundleCache(root: string, options?: {
|
|
36
36
|
maxBytes?: number;
|
|
@@ -3,7 +3,7 @@ import { mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { mkdir, open, readFile, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { PROFILE_BUNDLE_DEFAULT_MAX_BYTES, PROFILE_BUNDLE_MAX_BYTES, PROFILE_BUNDLE_MAX_FILE_BYTES, PROFILE_BUNDLE_MAX_FILES, PROFILE_BUNDLE_SCHEMA, } from "./profile-bundle-key.js";
|
|
6
|
-
import { evictProfileBundles, evictUnlocked, fsyncDir, loadLru, pathExists, profileBundleManifestPath, readManifestFile, saveLru, sweepProfileBundleCache, withRootLock, } from "./profile-bundle-lru.js";
|
|
6
|
+
import { evictProfileBundles, evictUnlocked, fsyncDir, hasPendingRootWork, loadLru, pathExists, profileBundleManifestPath, readManifestFile, saveLru, sweepProfileBundleCache, sweepUnlocked, withRootLock, } from "./profile-bundle-lru.js";
|
|
7
7
|
import { hasControlCharacter, normalizeWorkspaceRelativePath, validateFileMode, } from "./workspace-files.js";
|
|
8
8
|
export * from "./profile-bundle-key.js";
|
|
9
9
|
export { evictProfileBundles, profileBundleManifestPath, sweepProfileBundleCache, } from "./profile-bundle-lru.js";
|
|
@@ -116,11 +116,11 @@ function validateManifestFiles(manifest) {
|
|
|
116
116
|
export async function admitProfileBundle(root, manifest, readObject) {
|
|
117
117
|
validateManifestShape(manifest);
|
|
118
118
|
const entries = validateManifestFiles(manifest);
|
|
119
|
-
await sweepProfileBundleCache(root);
|
|
120
119
|
await mkdir(join(root, "objects"), { recursive: true });
|
|
121
120
|
await mkdir(join(root, "bundles"), { recursive: true });
|
|
122
121
|
await mkdir(join(root, "incoming"), { recursive: true });
|
|
123
122
|
await withRootLock(root, async () => {
|
|
123
|
+
await sweepUnlocked(root);
|
|
124
124
|
const manifestPath = profileBundleManifestPath(root, manifest.bundle);
|
|
125
125
|
const existing = await readManifestFile(manifestPath);
|
|
126
126
|
if (existing && existing.bundle !== manifest.bundle) {
|
|
@@ -293,8 +293,8 @@ export async function claimProfileBundle(root, claim, linkFile) {
|
|
|
293
293
|
}
|
|
294
294
|
/**
|
|
295
295
|
* Open a host-side cache handle over `root`, creating the layout and sweeping
|
|
296
|
-
* stale `incoming/` residue synchronously
|
|
297
|
-
*
|
|
296
|
+
* stale `incoming/` residue synchronously when the root is idle. A busy root
|
|
297
|
+
* leaves cleanup to its serialized admissions and sweeps.
|
|
298
298
|
*/
|
|
299
299
|
export function openProfileBundleCache(root, options) {
|
|
300
300
|
const maxBytes = options?.maxBytes ?? PROFILE_BUNDLE_DEFAULT_MAX_BYTES;
|
|
@@ -305,8 +305,10 @@ export function openProfileBundleCache(root, options) {
|
|
|
305
305
|
mkdirSync(join(root, "bundles"), { recursive: true });
|
|
306
306
|
const incoming = join(root, "incoming");
|
|
307
307
|
mkdirSync(incoming, { recursive: true });
|
|
308
|
-
|
|
309
|
-
|
|
308
|
+
if (!hasPendingRootWork(root)) {
|
|
309
|
+
for (const entry of readdirSync(incoming)) {
|
|
310
|
+
rmSync(join(incoming, entry), { recursive: true, force: true });
|
|
311
|
+
}
|
|
310
312
|
}
|
|
311
313
|
return {
|
|
312
314
|
root,
|
|
@@ -20,6 +20,8 @@ export interface ProfileBundleLru {
|
|
|
20
20
|
export declare function pathExists(path: string): Promise<boolean>;
|
|
21
21
|
export declare function fsyncDir(dir: string): Promise<void>;
|
|
22
22
|
export declare function withRootLock<T>(root: string, run: () => Promise<T>): Promise<T>;
|
|
23
|
+
/** Synchronous open must not sweep a root with pending asynchronous work. */
|
|
24
|
+
export declare function hasPendingRootWork(root: string): boolean;
|
|
23
25
|
export declare function readManifestFile(path: string): Promise<{
|
|
24
26
|
schema?: unknown;
|
|
25
27
|
bundle?: unknown;
|
|
@@ -32,13 +34,10 @@ export declare function readManifestFile(path: string): Promise<{
|
|
|
32
34
|
* The raw key is always recoverable from the manifest's `bundle` field.
|
|
33
35
|
*/
|
|
34
36
|
export declare function profileBundleManifestPath(root: string, bundle: string): string;
|
|
35
|
-
/**
|
|
36
|
-
/**
|
|
37
|
-
* Remove stale `incoming/` admission directories. An admit only ever renames
|
|
38
|
-
* OUT of `incoming/`, so anything found here is residue from an interrupted
|
|
39
|
-
* admission and is safe to delete; objects and manifests are untouched.
|
|
40
|
-
*/
|
|
37
|
+
/** Sweep only while holding the per-root lock; active admissions own incoming/. */
|
|
41
38
|
export declare function sweepProfileBundleCache(root: string): Promise<void>;
|
|
39
|
+
/** Remove interrupted admission residue; the caller must hold the per-root lock. */
|
|
40
|
+
export declare function sweepUnlocked(root: string): Promise<void>;
|
|
42
41
|
export declare function loadLru(root: string): Promise<ProfileBundleLru>;
|
|
43
42
|
/** Best-effort by contract: lru corruption or a full cache must never break admit/claim. */
|
|
44
43
|
export declare function saveLru(root: string, lru: ProfileBundleLru): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { open, readdir, readFile, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
4
|
import { compareStrings } from "./workspace-files.js";
|
|
5
5
|
export async function pathExists(path) {
|
|
6
6
|
try {
|
|
@@ -25,14 +25,24 @@ export async function fsyncDir(dir) {
|
|
|
25
25
|
// Best-effort durability: a cache without directory fsync still verifies.
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
/** Serialize
|
|
28
|
+
/** Serialize admissions, sweeps, and evictions per cache root. */
|
|
29
29
|
const rootQueues = new Map();
|
|
30
30
|
export function withRootLock(root, run) {
|
|
31
|
-
const
|
|
31
|
+
const key = resolve(root);
|
|
32
|
+
const previous = rootQueues.get(key) ?? Promise.resolve();
|
|
32
33
|
const next = previous.then(run, run);
|
|
33
|
-
|
|
34
|
+
const settled = next.then(() => undefined, () => undefined);
|
|
35
|
+
rootQueues.set(key, settled);
|
|
36
|
+
void settled.then(() => {
|
|
37
|
+
if (rootQueues.get(key) === settled)
|
|
38
|
+
rootQueues.delete(key);
|
|
39
|
+
});
|
|
34
40
|
return next;
|
|
35
41
|
}
|
|
42
|
+
/** Synchronous open must not sweep a root with pending asynchronous work. */
|
|
43
|
+
export function hasPendingRootWork(root) {
|
|
44
|
+
return rootQueues.has(resolve(root));
|
|
45
|
+
}
|
|
36
46
|
export async function readManifestFile(path) {
|
|
37
47
|
try {
|
|
38
48
|
return JSON.parse(await readFile(path, "utf8"));
|
|
@@ -50,13 +60,12 @@ export function profileBundleManifestPath(root, bundle) {
|
|
|
50
60
|
const filename = bundle.replace(/[^A-Za-z0-9._-]/g, (char) => `_${char.codePointAt(0)?.toString(16) ?? "3f"}_`);
|
|
51
61
|
return join(root, "bundles", `${filename}.json`);
|
|
52
62
|
}
|
|
53
|
-
/**
|
|
54
|
-
/**
|
|
55
|
-
* Remove stale `incoming/` admission directories. An admit only ever renames
|
|
56
|
-
* OUT of `incoming/`, so anything found here is residue from an interrupted
|
|
57
|
-
* admission and is safe to delete; objects and manifests are untouched.
|
|
58
|
-
*/
|
|
63
|
+
/** Sweep only while holding the per-root lock; active admissions own incoming/. */
|
|
59
64
|
export async function sweepProfileBundleCache(root) {
|
|
65
|
+
await withRootLock(root, () => sweepUnlocked(root));
|
|
66
|
+
}
|
|
67
|
+
/** Remove interrupted admission residue; the caller must hold the per-root lock. */
|
|
68
|
+
export async function sweepUnlocked(root) {
|
|
60
69
|
const incoming = join(root, "incoming");
|
|
61
70
|
let entries;
|
|
62
71
|
try {
|
|
@@ -40,6 +40,10 @@ export function executionControlConflict(profile, harness) {
|
|
|
40
40
|
export function materializeProfile(inputProfile, harness, opts = {}) {
|
|
41
41
|
assertMaterializerHarness(harness);
|
|
42
42
|
const profile = agentProfileSchema.parse(inputProfile);
|
|
43
|
+
const knowledge = profile.resources && Reflect.get(profile.resources, "knowledge");
|
|
44
|
+
if (Array.isArray(knowledge) && knowledge.length > 0) {
|
|
45
|
+
throw new Error("Knowledge source generations must be owner-authorized and resolved by the execution runtime before materialization");
|
|
46
|
+
}
|
|
43
47
|
const skip = new Set(assertSkippableDimensions(opts.skip));
|
|
44
48
|
const binds = assertBindableLaunchControls(opts.binds);
|
|
45
49
|
const instructionsRoot = opts.instructionsRoot ?? "workspace";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const compareStrings: (a: string, b: string) => 1 |
|
|
1
|
+
export declare const compareStrings: (a: string, b: string) => 1 | 0 | -1;
|
|
2
2
|
export declare const DEFAULT_FILE_MODE = 420;
|
|
3
3
|
export declare const PRIVATE_FILE_MODE = 384;
|
|
4
4
|
export declare const EXECUTABLE_FILE_MODE = 493;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-profile-materialize",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.3",
|
|
4
4
|
"description": "Materialize an AgentProfile into the native per-harness workspace files (the one shared materializer for cli-bridge, agent-dev-container, and VB).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
"dist"
|
|
26
26
|
],
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@tangle-network/agent-interface": "
|
|
29
|
-
"@tangle-network/agent-interface-current": "npm:@tangle-network/agent-interface@
|
|
28
|
+
"@tangle-network/agent-interface": "5.0.0",
|
|
29
|
+
"@tangle-network/agent-interface-current": "npm:@tangle-network/agent-interface@5.0.0",
|
|
30
30
|
"@types/node": "25.6.0",
|
|
31
31
|
"typescript": "^6.0.3",
|
|
32
32
|
"vitest": "^4.1.11"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@tangle-network/agent-interface": "^3.0.0"
|
|
35
|
+
"@tangle-network/agent-interface": "^3.0.0 || ^4.0.0 || ^5.0.0"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc -p tsconfig.json",
|