@tangle-network/agent-profile-materialize 0.20.9 → 0.22.0
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/control-maps.d.ts +14 -1
- package/dist/control-maps.js +61 -25
- package/dist/execution-controls.js +19 -15
- package/dist/harness.d.ts +17 -20
- package/dist/harness.js +92 -39
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/managed-workspace-bind.d.ts +4 -0
- package/dist/managed-workspace-bind.js +94 -0
- package/dist/managed-workspace-stage.d.ts +24 -0
- package/dist/managed-workspace-stage.js +195 -0
- package/dist/managed-workspace-types.d.ts +38 -0
- package/dist/managed-workspace-types.js +1 -0
- package/dist/managed-workspace.d.ts +37 -0
- package/dist/managed-workspace.js +283 -0
- package/dist/profile-materializer.d.ts +10 -0
- package/dist/profile-materializer.js +19 -0
- package/dist/secret-concurrent-revision.d.ts +19 -0
- package/dist/secret-concurrent-revision.js +164 -0
- package/dist/workspace-apply-files.d.ts +7 -0
- package/dist/workspace-apply-files.js +17 -3
- package/dist/workspace-apply.js +73 -24
- package/package.json +4 -4
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { createHash, createHmac } from "node:crypto";
|
|
3
|
+
import { chmodSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { assertLaunchRoot, listRevisionFiles, makeStageWritable, prepareExplicitOpenCodePlan, } from "./managed-workspace.js";
|
|
6
|
+
import { bindConcurrentLaunch } from "./managed-workspace-bind.js";
|
|
7
|
+
import { lstatIfPresent, prepareManagedWorkspacePlan, } from "./managed-workspace-stage.js";
|
|
8
|
+
import { assertWorkspacePlanSupported } from "./plan-support.js";
|
|
9
|
+
/**
|
|
10
|
+
* Publish a resolved revision for exactly one native subprocess UID. The
|
|
11
|
+
* executor-owned naming key hides low-entropy credential guesses from paths;
|
|
12
|
+
* the resolved digest and manifest never leave this server-side result.
|
|
13
|
+
*/
|
|
14
|
+
export async function prepareSecretConcurrentProfileRevision(input) {
|
|
15
|
+
if (process.platform !== "linux" ||
|
|
16
|
+
process.getuid?.() !== 0 ||
|
|
17
|
+
!Number.isSafeInteger(input.subprocessUid) ||
|
|
18
|
+
input.subprocessUid < 200_000 ||
|
|
19
|
+
input.namingKey.length !== 32 ||
|
|
20
|
+
!input.ownerId) {
|
|
21
|
+
throw new Error("Secret concurrent profile needs a root executor and reserved identity");
|
|
22
|
+
}
|
|
23
|
+
const plan = prepareExplicitOpenCodePlan(input.plan);
|
|
24
|
+
if (plan.files.some((file) => file.relPath.startsWith(".config/opencode/"))) {
|
|
25
|
+
throw new Error("Secret OpenCode reserves its native config directory");
|
|
26
|
+
}
|
|
27
|
+
assertWorkspacePlanSupported(plan);
|
|
28
|
+
assertLaunchRoot(input.launchRoot);
|
|
29
|
+
const prepared = await prepareManagedWorkspacePlan(plan, input.managedRoot, input.sharedWorkspaceDir, input.secretProvider, input.options);
|
|
30
|
+
try {
|
|
31
|
+
// XDG_CONFIG_HOME is bound to this executor-owned tree at native launch.
|
|
32
|
+
// An agent-owned HOME may hold SQLite state, never native authority.
|
|
33
|
+
mkdirSync(join(prepared.root, "workspace", ".config", "opencode"), {
|
|
34
|
+
recursive: true,
|
|
35
|
+
mode: 0o700,
|
|
36
|
+
});
|
|
37
|
+
const identity = createHmac("sha256", input.namingKey)
|
|
38
|
+
.update(input.ownerId)
|
|
39
|
+
.update("\0")
|
|
40
|
+
.update(String(input.subprocessUid))
|
|
41
|
+
.update("\0")
|
|
42
|
+
.update(prepared.manifest.materializedDigest)
|
|
43
|
+
.digest("hex");
|
|
44
|
+
const directory = join(input.launchRoot, `revision-${identity}`);
|
|
45
|
+
const spawn = bindConcurrentLaunch(plan, prepared.application, join(directory, "workspace"), { secretOpenCodeRevisionCwd: true });
|
|
46
|
+
prepared.manifest.launchDigest = createHash("sha256")
|
|
47
|
+
.update(JSON.stringify(spawn))
|
|
48
|
+
.digest("hex");
|
|
49
|
+
writeFileSync(join(prepared.root, "manifest.json"), JSON.stringify(prepared.manifest));
|
|
50
|
+
const existing = lstatIfPresent(directory);
|
|
51
|
+
if (existing) {
|
|
52
|
+
assertPublishedSecretRevision(directory, prepared.manifest);
|
|
53
|
+
prepared.dispose();
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
try {
|
|
57
|
+
makeSecretRevisionReadOnly(prepared.root, input.subprocessUid);
|
|
58
|
+
renameSync(prepared.root, directory);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
const raced = lstatIfPresent(directory);
|
|
62
|
+
if (!raced?.isDirectory() || raced.isSymbolicLink())
|
|
63
|
+
throw error;
|
|
64
|
+
assertPublishedSecretRevision(directory, prepared.manifest);
|
|
65
|
+
makeStageWritable(prepared.root);
|
|
66
|
+
prepared.dispose();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
directory,
|
|
71
|
+
manifest: prepared.manifest,
|
|
72
|
+
application: { ...prepared.application, spawn },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (lstatIfPresent(prepared.root)) {
|
|
77
|
+
makeStageWritable(prepared.root);
|
|
78
|
+
prepared.dispose();
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function secretRevisionPaths(root) {
|
|
84
|
+
const all = [root];
|
|
85
|
+
const exposed = [root];
|
|
86
|
+
const walk = (directory) => {
|
|
87
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
88
|
+
const child = join(directory, entry.name);
|
|
89
|
+
const stat = lstatSync(child);
|
|
90
|
+
if (stat.uid !== 0 || stat.isSymbolicLink()) {
|
|
91
|
+
throw new Error("Secret concurrent profile stage contains an untrusted inode");
|
|
92
|
+
}
|
|
93
|
+
if (!stat.isDirectory() && !stat.isFile()) {
|
|
94
|
+
throw new Error("Secret concurrent profile stage contains a special file");
|
|
95
|
+
}
|
|
96
|
+
all.push(child);
|
|
97
|
+
if (entry.name !== "manifest.json" || directory !== root) {
|
|
98
|
+
exposed.push(child);
|
|
99
|
+
}
|
|
100
|
+
if (stat.isDirectory())
|
|
101
|
+
walk(child);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
walk(root);
|
|
105
|
+
return { all, exposed };
|
|
106
|
+
}
|
|
107
|
+
function runSetfacl(args, paths) {
|
|
108
|
+
for (let start = 0; start < paths.length; start += 64) {
|
|
109
|
+
execFileSync("/usr/bin/setfacl", [...args, "--", ...paths.slice(start, start + 64)], {
|
|
110
|
+
env: { PATH: "/usr/bin:/bin", LANG: "C" },
|
|
111
|
+
timeout: 30_000,
|
|
112
|
+
stdio: "pipe",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function makeSecretRevisionReadOnly(root, uid) {
|
|
117
|
+
const paths = secretRevisionPaths(root);
|
|
118
|
+
// Remove inherited named ACLs before widening the ACL mask for this UID.
|
|
119
|
+
runSetfacl(["-b"], paths.all);
|
|
120
|
+
for (const path of paths.all) {
|
|
121
|
+
const stat = lstatSync(path);
|
|
122
|
+
chmodSync(path, stat.isDirectory() ? 0o500 : stat.mode & 0o111 ? 0o500 : 0o400);
|
|
123
|
+
}
|
|
124
|
+
// The private manifest has resolved-file hashes. The CLI can read its native
|
|
125
|
+
// files, but never the executor inventory or another revision's files.
|
|
126
|
+
runSetfacl(["-m", `u:${uid}:r-X`], paths.exposed);
|
|
127
|
+
}
|
|
128
|
+
function assertPublishedSecretRevision(directory, expected) {
|
|
129
|
+
const root = lstatSync(directory);
|
|
130
|
+
if (!root.isDirectory() ||
|
|
131
|
+
root.isSymbolicLink() ||
|
|
132
|
+
root.uid !== 0 ||
|
|
133
|
+
(root.mode & 0o222) !== 0) {
|
|
134
|
+
throw new Error("Secret concurrent profile revision is not immutable");
|
|
135
|
+
}
|
|
136
|
+
const persisted = JSON.parse(readFileSync(join(directory, "manifest.json"), "utf8"));
|
|
137
|
+
if (persisted.materializedDigest !== expected.materializedDigest ||
|
|
138
|
+
persisted.launchDigest !== expected.launchDigest) {
|
|
139
|
+
throw new Error("Secret concurrent profile revision digest collision");
|
|
140
|
+
}
|
|
141
|
+
for (const file of expected.files) {
|
|
142
|
+
const filePath = join(directory, file.root, file.relPath);
|
|
143
|
+
const stat = lstatSync(filePath);
|
|
144
|
+
if (!stat.isFile() ||
|
|
145
|
+
stat.isSymbolicLink() ||
|
|
146
|
+
stat.uid !== 0 ||
|
|
147
|
+
(stat.mode & 0o222) !== 0 ||
|
|
148
|
+
createHash("sha256").update(readFileSync(filePath)).digest("hex") !==
|
|
149
|
+
file.sha256) {
|
|
150
|
+
throw new Error("Secret concurrent profile revision file changed");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const expectedPaths = new Set(expected.files.map((file) => `${file.root}/${file.relPath}`));
|
|
154
|
+
for (const rootName of ["workspace", "agent"]) {
|
|
155
|
+
for (const relPath of listRevisionFiles(join(directory, rootName))) {
|
|
156
|
+
if (!expectedPaths.delete(`${rootName}/${relPath}`)) {
|
|
157
|
+
throw new Error("Secret concurrent profile revision has an unexpected file");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (expectedPaths.size) {
|
|
162
|
+
throw new Error("Secret concurrent profile revision is missing a manifest file");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
declare function workspaceFileExists(pathValue: string): boolean;
|
|
2
|
+
/**
|
|
3
|
+
* A regular file's text, or `undefined` when no regular file is there or its
|
|
4
|
+
* bytes are not UTF-8. This package writes only UTF-8, so such bytes are not
|
|
5
|
+
* its output, and decoding them lossily would rewrite them on replacement.
|
|
6
|
+
*/
|
|
2
7
|
declare function regularFileContent(pathValue: string): string | undefined;
|
|
3
8
|
declare function workspaceFileMatches(file: NormalizedPlanFile & {
|
|
4
9
|
absPath: string;
|
|
@@ -33,6 +38,8 @@ interface PreparedWorkspaceFile {
|
|
|
33
38
|
temporaryPath: string;
|
|
34
39
|
identity: WorkspaceFileIdentity;
|
|
35
40
|
published: boolean;
|
|
41
|
+
/** A link to the replaced file while it holds an agent's text, for rollback. */
|
|
42
|
+
superseded?: string;
|
|
36
43
|
}
|
|
37
44
|
declare function prepareWorkspaceFile(workspaceRoot: string, file: NormalizedPlanFile & {
|
|
38
45
|
absPath: string;
|
|
@@ -9,10 +9,24 @@ function workspaceFileExists(pathValue) {
|
|
|
9
9
|
throw error;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
const STRICT_UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
13
|
+
/**
|
|
14
|
+
* A regular file's text, or `undefined` when no regular file is there or its
|
|
15
|
+
* bytes are not UTF-8. This package writes only UTF-8, so such bytes are not
|
|
16
|
+
* its output, and decoding them lossily would rewrite them on replacement.
|
|
17
|
+
*/
|
|
12
18
|
function regularFileContent(pathValue) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
if (!lstatSync(pathValue).isFile())
|
|
20
|
+
return undefined;
|
|
21
|
+
const bytes = readFileSync(pathValue);
|
|
22
|
+
try {
|
|
23
|
+
return STRICT_UTF8.decode(bytes);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof TypeError)
|
|
27
|
+
return undefined;
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
16
30
|
}
|
|
17
31
|
function workspaceFileMatches(file) {
|
|
18
32
|
const stats = lstatSync(file.absPath);
|
package/dist/workspace-apply.js
CHANGED
|
@@ -70,35 +70,53 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
70
70
|
}
|
|
71
71
|
// An owned path holds this package's own earlier output, so re-applying
|
|
72
72
|
// into the same workspace replaces it rather than failing the turn — once
|
|
73
|
-
// the bytes already there prove that provenance.
|
|
74
|
-
//
|
|
75
|
-
// the
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
// the bytes already there prove that provenance. The replacement keeps what
|
|
74
|
+
// an agent appended below a generated context file (its memory), so it can
|
|
75
|
+
// differ from the planned bytes. `reject` still refuses: that policy is
|
|
76
|
+
// "nothing may pre-exist here", which is a statement about the workspace,
|
|
77
|
+
// not authorship.
|
|
78
|
+
const plannedContent = new Map(filesToApply.map((file) => [fileIdentity(file), file.content]));
|
|
79
|
+
const replacement = (file) => {
|
|
80
|
+
const planned = plannedContent.get(fileIdentity(file));
|
|
81
|
+
if (existingFiles !== "allow-identical" ||
|
|
82
|
+
file.ownership !== "materializer" ||
|
|
83
|
+
planned === undefined) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return materializerReplacement(file.relPath, () => regularFileContent(file.absPath), planned);
|
|
87
|
+
};
|
|
79
88
|
// Validate the complete plan before the first write so a late traversal or
|
|
80
89
|
// existing symlink cannot leave a partially materialized workspace.
|
|
90
|
+
const writes = [];
|
|
81
91
|
for (const file of filesToApply) {
|
|
82
92
|
assertNoSymlinkPathComponents(file.rootDir, file.absPath);
|
|
83
|
-
if (workspaceFileExists(file.absPath)
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
if (!workspaceFileExists(file.absPath)) {
|
|
94
|
+
writes.push(file);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const content = replacement(file);
|
|
98
|
+
if (content !== undefined) {
|
|
99
|
+
writes.push({ ...file, content });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (existingFiles === "reject" || !workspaceFileMatches(file)) {
|
|
86
103
|
throw new Error(workspaceFileConflictMessage(file, existingFiles, publicPlanDigest));
|
|
87
104
|
}
|
|
105
|
+
writes.push(file);
|
|
88
106
|
}
|
|
89
107
|
// Parent directories remain on failure because they may contain concurrent
|
|
90
108
|
// workspace state that this call does not own.
|
|
91
|
-
for (const file of
|
|
109
|
+
for (const file of writes) {
|
|
92
110
|
mkdirSync(dirname(file.absPath), { recursive: true });
|
|
93
111
|
}
|
|
94
|
-
for (const file of
|
|
112
|
+
for (const file of writes) {
|
|
95
113
|
assertNoSymlinkPathComponents(file.rootDir, file.absPath);
|
|
96
114
|
}
|
|
97
115
|
const prepared = [];
|
|
98
116
|
// Prepared temp links stay alive through publication and rollback. Their
|
|
99
117
|
// inodes distinguish files created here from concurrent replacements.
|
|
100
118
|
try {
|
|
101
|
-
for (const file of
|
|
119
|
+
for (const file of writes) {
|
|
102
120
|
if (existingFiles === "allow-identical" &&
|
|
103
121
|
workspaceFileExists(file.absPath) &&
|
|
104
122
|
workspaceFileMatches(file) &&
|
|
@@ -124,7 +142,12 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
124
142
|
workspaceFileHasOwnership(entry.file.absPath, ownership))) {
|
|
125
143
|
continue;
|
|
126
144
|
}
|
|
127
|
-
if (isFsError(error, "EEXIST") &&
|
|
145
|
+
if (isFsError(error, "EEXIST") &&
|
|
146
|
+
replacement(entry.file) === entry.file.content) {
|
|
147
|
+
// The replacement is recomposed from the bytes there now, so an
|
|
148
|
+
// agent append that landed since validation refuses below instead
|
|
149
|
+
// of being overwritten; one landing between that read and the
|
|
150
|
+
// rename is not detected.
|
|
128
151
|
// rename() replaces in one step, so no window exists in which the
|
|
129
152
|
// path is missing and a concurrent spawn reads nothing. The staged
|
|
130
153
|
// inode moves onto the path, which is what rollback identifies the
|
|
@@ -132,8 +155,16 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
132
155
|
// published — leaving the path ABSENT rather than restored to the
|
|
133
156
|
// superseded bytes. That is the fail-closed direction: the harness
|
|
134
157
|
// whose flag names a missing file refuses to start, whereas silently
|
|
135
|
-
// restored previous-turn bytes would run the wrong prompt.
|
|
158
|
+
// restored previous-turn bytes would run the wrong prompt. A replaced
|
|
159
|
+
// file that holds an agent's appended text is the exception: it is
|
|
160
|
+
// linked aside first and restored, because removing it would delete
|
|
161
|
+
// the agent's own text.
|
|
136
162
|
assertNoSymlinkPathComponents(rootFor(entry.file.root), entry.file.absPath);
|
|
163
|
+
if (entry.file.content !== plannedContent.get(fileIdentity(entry.file))) {
|
|
164
|
+
const superseded = join(dirname(entry.file.absPath), `.agent-profile-${randomUUID()}.superseded`);
|
|
165
|
+
linkSync(entry.file.absPath, superseded);
|
|
166
|
+
entry.superseded = superseded;
|
|
167
|
+
}
|
|
137
168
|
renameSync(entry.temporaryPath, entry.file.absPath);
|
|
138
169
|
entry.published = true;
|
|
139
170
|
continue;
|
|
@@ -151,12 +182,25 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
151
182
|
try {
|
|
152
183
|
if (entry.published &&
|
|
153
184
|
workspacePathHasIdentity(entry.file.absPath, entry.identity)) {
|
|
154
|
-
|
|
185
|
+
if (entry.superseded) {
|
|
186
|
+
renameSync(entry.superseded, entry.file.absPath);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
unlinkSync(entry.file.absPath);
|
|
190
|
+
}
|
|
155
191
|
}
|
|
156
192
|
}
|
|
157
193
|
catch (cleanupError) {
|
|
158
194
|
cleanupErrors.push(cleanupError);
|
|
159
195
|
}
|
|
196
|
+
try {
|
|
197
|
+
if (entry.superseded)
|
|
198
|
+
unlinkSync(entry.superseded);
|
|
199
|
+
}
|
|
200
|
+
catch (cleanupError) {
|
|
201
|
+
if (!isFsError(cleanupError, "ENOENT"))
|
|
202
|
+
cleanupErrors.push(cleanupError);
|
|
203
|
+
}
|
|
160
204
|
try {
|
|
161
205
|
if (workspacePathHasIdentity(entry.temporaryPath, entry.identity)) {
|
|
162
206
|
unlinkSync(entry.temporaryPath);
|
|
@@ -175,13 +219,17 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
175
219
|
const cleanupErrors = [];
|
|
176
220
|
const temporaryPaths = [];
|
|
177
221
|
for (const entry of prepared) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
222
|
+
for (const path of [entry.temporaryPath, entry.superseded]) {
|
|
223
|
+
if (path === undefined)
|
|
224
|
+
continue;
|
|
225
|
+
try {
|
|
226
|
+
unlinkSync(path);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
if (!isFsError(error, "ENOENT")) {
|
|
230
|
+
cleanupErrors.push(error);
|
|
231
|
+
temporaryPaths.push(path);
|
|
232
|
+
}
|
|
185
233
|
}
|
|
186
234
|
}
|
|
187
235
|
}
|
|
@@ -216,9 +264,10 @@ function safeWorkspacePlanReceipt(plan, writtenFiles, workspacePlanDigest = hash
|
|
|
216
264
|
writtenFiles: writtenFiles.map((file) => ({ ...file })),
|
|
217
265
|
};
|
|
218
266
|
}
|
|
267
|
+
import { randomUUID } from "node:crypto";
|
|
219
268
|
import { linkSync, mkdirSync, renameSync, unlinkSync } from "node:fs";
|
|
220
|
-
import { dirname, isAbsolute, resolve } from "node:path";
|
|
221
|
-
import {
|
|
269
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
270
|
+
import { materializerReplacement } from "./harness.js";
|
|
222
271
|
import { hashWorkspacePlan } from "./plan-digest.js";
|
|
223
272
|
import { normalizePlanFiles, normalizeWorkspacePlanArgument, } from "./plan-normalize.js";
|
|
224
273
|
import { promptIntentFields, samePromptIntentValue, systemPromptBindingField, } from "./plan-prompt.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-profile-materialize",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
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": "3.0.0",
|
|
29
|
+
"@tangle-network/agent-interface-current": "npm:@tangle-network/agent-interface@3.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": "^
|
|
35
|
+
"@tangle-network/agent-interface": "^3.0.0"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc -p tsconfig.json",
|