@tangle-network/agent-profile-materialize 1.1.7 → 1.1.8
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/index.d.ts +1226 -29
- package/dist/index.js +4987 -23
- package/package.json +7 -5
- package/dist/candidate-types.d.ts +0 -36
- package/dist/candidate-types.js +0 -1
- package/dist/candidate.d.ts +0 -9
- package/dist/candidate.js +0 -283
- package/dist/commands.d.ts +0 -2
- package/dist/commands.js +0 -32
- package/dist/control-maps.d.ts +0 -58
- package/dist/control-maps.js +0 -277
- package/dist/execution-controls.d.ts +0 -5
- package/dist/execution-controls.js +0 -199
- package/dist/harness-prompt.d.ts +0 -22
- package/dist/harness-prompt.js +0 -64
- package/dist/harness.d.ts +0 -131
- package/dist/harness.js +0 -416
- package/dist/hooks.d.ts +0 -6
- package/dist/hooks.js +0 -250
- package/dist/managed-workspace-bind.d.ts +0 -4
- package/dist/managed-workspace-bind.js +0 -106
- package/dist/managed-workspace-stage.d.ts +0 -25
- package/dist/managed-workspace-stage.js +0 -199
- package/dist/managed-workspace-types.d.ts +0 -38
- package/dist/managed-workspace-types.js +0 -1
- package/dist/managed-workspace.d.ts +0 -31
- package/dist/managed-workspace.js +0 -271
- package/dist/mcp-config-ownership.d.ts +0 -13
- package/dist/mcp-config-ownership.js +0 -79
- package/dist/mcp-opencode.d.ts +0 -25
- package/dist/mcp-opencode.js +0 -52
- package/dist/mcp-timeouts.d.ts +0 -7
- package/dist/mcp-timeouts.js +0 -48
- package/dist/mcp.d.ts +0 -4
- package/dist/mcp.js +0 -246
- package/dist/model-id.d.ts +0 -55
- package/dist/model-id.js +0 -67
- package/dist/pi-models-config.d.ts +0 -32
- package/dist/pi-models-config.js +0 -65
- package/dist/plan-application.d.ts +0 -10
- package/dist/plan-application.js +0 -190
- package/dist/plan-digest.d.ts +0 -18
- package/dist/plan-digest.js +0 -116
- package/dist/plan-json.d.ts +0 -13
- package/dist/plan-json.js +0 -91
- package/dist/plan-normalize.d.ts +0 -12
- package/dist/plan-normalize.js +0 -168
- package/dist/plan-prompt.d.ts +0 -38
- package/dist/plan-prompt.js +0 -49
- package/dist/plan-support.d.ts +0 -9
- package/dist/plan-support.js +0 -22
- package/dist/plan-types.d.ts +0 -341
- package/dist/plan-types.js +0 -105
- package/dist/prime-agent-dir.d.ts +0 -64
- package/dist/prime-agent-dir.js +0 -173
- package/dist/prime-fork.d.ts +0 -155
- package/dist/prime-fork.js +0 -262
- package/dist/prime-profile.d.ts +0 -88
- package/dist/prime-profile.js +0 -297
- package/dist/profile-bundle-cache.d.ts +0 -37
- package/dist/profile-bundle-cache.js +0 -320
- package/dist/profile-bundle-key.d.ts +0 -69
- package/dist/profile-bundle-key.js +0 -62
- package/dist/profile-bundle-lru.d.ts +0 -56
- package/dist/profile-bundle-lru.js +0 -220
- package/dist/profile-materializer.d.ts +0 -24
- package/dist/profile-materializer.js +0 -114
- package/dist/profile-plan.d.ts +0 -16
- package/dist/profile-plan.js +0 -90
- package/dist/profile-prompt.d.ts +0 -4
- package/dist/profile-prompt.js +0 -107
- package/dist/profile-support.d.ts +0 -8
- package/dist/profile-support.js +0 -6
- package/dist/resource-lowering.d.ts +0 -6
- package/dist/resource-lowering.js +0 -103
- package/dist/resource-resolution.d.ts +0 -27
- package/dist/resource-resolution.js +0 -265
- package/dist/secret-concurrent-revision.d.ts +0 -30
- package/dist/secret-concurrent-revision.js +0 -249
- package/dist/secret-session-view.d.ts +0 -25
- package/dist/secret-session-view.js +0 -166
- package/dist/subagents.d.ts +0 -6
- package/dist/subagents.js +0 -137
- package/dist/tool-bin-path.d.ts +0 -23
- package/dist/tool-bin-path.js +0 -61
- package/dist/workspace-apply-files.d.ts +0 -57
- package/dist/workspace-apply-files.js +0 -193
- package/dist/workspace-apply-publish.d.ts +0 -22
- package/dist/workspace-apply-publish.js +0 -178
- package/dist/workspace-apply.d.ts +0 -11
- package/dist/workspace-apply.js +0 -124
- package/dist/workspace-files.d.ts +0 -8
- package/dist/workspace-files.js +0 -46
- package/dist/workspace-launch.d.ts +0 -3
- package/dist/workspace-launch.js +0 -13
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { AgentProfileFileMount } from "@tangle-network/agent-interface";
|
|
2
|
-
export declare const PROFILE_BUNDLE_SCHEMA = "agent-profile-bundle/v1";
|
|
3
|
-
export declare const PROFILE_BUNDLE_MAX_FILES = 512;
|
|
4
|
-
export declare const PROFILE_BUNDLE_MAX_BYTES: number;
|
|
5
|
-
export declare const PROFILE_BUNDLE_MAX_FILE_BYTES: number;
|
|
6
|
-
export declare const PROFILE_BUNDLE_DEFAULT_MAX_BYTES: number;
|
|
7
|
-
export interface ProfileBundleFileEntry {
|
|
8
|
-
/** Logical workspace-relative mount path, exactly as declared on the profile. */
|
|
9
|
-
mountPath: string;
|
|
10
|
-
/** SHA-256 of the content bytes as lowercase hex, WITHOUT the `sha256:` prefix. */
|
|
11
|
-
sha256: string;
|
|
12
|
-
/** Full file mode (0o644 default, 0o755 for executable mounts). */
|
|
13
|
-
mode: number;
|
|
14
|
-
}
|
|
15
|
-
export interface ProfileBundleManifest {
|
|
16
|
-
schema: string;
|
|
17
|
-
/** Raw bundle key (e.g. `sha256:<hex>`); also recoverable from the filename. */
|
|
18
|
-
bundle: string;
|
|
19
|
-
createdAt: string;
|
|
20
|
-
sizeBytes: number;
|
|
21
|
-
files: Array<ProfileBundleFileEntry & {
|
|
22
|
-
target: string;
|
|
23
|
-
}>;
|
|
24
|
-
}
|
|
25
|
-
export interface ProfileBundleClaim {
|
|
26
|
-
bundle: string;
|
|
27
|
-
files: Array<{
|
|
28
|
-
mountPath: string;
|
|
29
|
-
sha256: string;
|
|
30
|
-
}>;
|
|
31
|
-
}
|
|
32
|
-
export type ProfileBundleClaimResult = {
|
|
33
|
-
status: "hit";
|
|
34
|
-
linked: string[];
|
|
35
|
-
} | {
|
|
36
|
-
status: "miss";
|
|
37
|
-
missing: string[];
|
|
38
|
-
};
|
|
39
|
-
export interface ProfileBundleCache {
|
|
40
|
-
root: string;
|
|
41
|
-
maxBytes: number;
|
|
42
|
-
admit(manifest: ProfileBundleManifest, readObject: (sha256: string) => Promise<Uint8Array> | Uint8Array): Promise<void>;
|
|
43
|
-
evict(): Promise<void>;
|
|
44
|
-
sweep(): Promise<void>;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Derive the canonical bundle file entry for one inline mount. The sha256 is
|
|
48
|
-
* computed over the exact inline content bytes and the mode mirrors the SDK's
|
|
49
|
-
* `profileFileMode`/`isExecutableProfileFile`: `mount.executable` wins when
|
|
50
|
-
* present, otherwise a path under a `bin/` or `sbin/` directory is executable.
|
|
51
|
-
* A mount whose resource is not inline is a caller bug and throws, matching
|
|
52
|
-
* the fail-closed discipline of {@link resolveInlineRef}.
|
|
53
|
-
*/
|
|
54
|
-
export declare function profileBundleFileEntry(mount: AgentProfileFileMount): ProfileBundleFileEntry;
|
|
55
|
-
/**
|
|
56
|
-
* The canonical file table: one entry per mount, sorted by (mountPath, sha256)
|
|
57
|
-
* so any array order of the same mounts produces the same table.
|
|
58
|
-
*/
|
|
59
|
-
export declare function profileBundleFileTable(mounts: readonly AgentProfileFileMount[]): ProfileBundleFileEntry[];
|
|
60
|
-
/**
|
|
61
|
-
* The content-addressed bundle key. The digest covers EXACTLY the schema
|
|
62
|
-
* version and, per file, the logical mount path, the sha256 of the content
|
|
63
|
-
* bytes, and the full file mode — the same image-content-hash discipline as
|
|
64
|
-
* scripts/ci/image-content-hash.mjs: hash exactly the inputs that determine
|
|
65
|
-
* the cached bytes (and their placement semantics), nothing more. Any profile
|
|
66
|
-
* change — content, path, or mode — produces a new key; anything else
|
|
67
|
-
* (mount order, transport-only target paths, timestamps) does not.
|
|
68
|
-
*/
|
|
69
|
-
export declare function profileFileBundleKey(mounts: readonly AgentProfileFileMount[]): string;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { canonicalCandidateDigest } from "@tangle-network/agent-interface";
|
|
3
|
-
import { resolveInlineRef } from "./resource-resolution.js";
|
|
4
|
-
import { compareStrings } from "./workspace-files.js";
|
|
5
|
-
// The bundle key, wire shapes and limits shared by the SDK, sidecar and
|
|
6
|
-
// host-agent. The cache itself (admit, claim, open) is profile-bundle-cache.ts.
|
|
7
|
-
export const PROFILE_BUNDLE_SCHEMA = "agent-profile-bundle/v1";
|
|
8
|
-
export const PROFILE_BUNDLE_MAX_FILES = 512;
|
|
9
|
-
export const PROFILE_BUNDLE_MAX_BYTES = 256 * 1024 * 1024;
|
|
10
|
-
export const PROFILE_BUNDLE_MAX_FILE_BYTES = 32 * 1024 * 1024;
|
|
11
|
-
export const PROFILE_BUNDLE_DEFAULT_MAX_BYTES = 1024 * 1024 * 1024;
|
|
12
|
-
// Mirrors PROFILE_BIN_DIR_RE in products/sandbox/sdk/src/profile-file-mounts.ts,
|
|
13
|
-
// the canonical definition for staged profile delivery. The SDK consumes this
|
|
14
|
-
// package, so the regex source is mirrored (not imported); tests assert parity
|
|
15
|
-
// with the SDK's profileFileMode semantics for representative paths.
|
|
16
|
-
const PROFILE_BIN_DIR_RE = /(^|\/)(s?bin)\//;
|
|
17
|
-
/**
|
|
18
|
-
* Derive the canonical bundle file entry for one inline mount. The sha256 is
|
|
19
|
-
* computed over the exact inline content bytes and the mode mirrors the SDK's
|
|
20
|
-
* `profileFileMode`/`isExecutableProfileFile`: `mount.executable` wins when
|
|
21
|
-
* present, otherwise a path under a `bin/` or `sbin/` directory is executable.
|
|
22
|
-
* A mount whose resource is not inline is a caller bug and throws, matching
|
|
23
|
-
* the fail-closed discipline of {@link resolveInlineRef}.
|
|
24
|
-
*/
|
|
25
|
-
export function profileBundleFileEntry(mount) {
|
|
26
|
-
const resolved = resolveInlineRef(mount.resource);
|
|
27
|
-
if (resolved === null) {
|
|
28
|
-
throw new Error(`profileBundleFileEntry: ${mount.path} requires an inline resource`);
|
|
29
|
-
}
|
|
30
|
-
const executable = mount.executable ?? PROFILE_BIN_DIR_RE.test(mount.path);
|
|
31
|
-
return {
|
|
32
|
-
mountPath: mount.path,
|
|
33
|
-
sha256: createHash("sha256").update(resolved.content, "utf8").digest("hex"),
|
|
34
|
-
mode: executable ? 0o755 : 0o644,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* The canonical file table: one entry per mount, sorted by (mountPath, sha256)
|
|
39
|
-
* so any array order of the same mounts produces the same table.
|
|
40
|
-
*/
|
|
41
|
-
export function profileBundleFileTable(mounts) {
|
|
42
|
-
return mounts
|
|
43
|
-
.map(profileBundleFileEntry)
|
|
44
|
-
.sort((left, right) => left.mountPath !== right.mountPath
|
|
45
|
-
? compareStrings(left.mountPath, right.mountPath)
|
|
46
|
-
: compareStrings(left.sha256, right.sha256));
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* The content-addressed bundle key. The digest covers EXACTLY the schema
|
|
50
|
-
* version and, per file, the logical mount path, the sha256 of the content
|
|
51
|
-
* bytes, and the full file mode — the same image-content-hash discipline as
|
|
52
|
-
* scripts/ci/image-content-hash.mjs: hash exactly the inputs that determine
|
|
53
|
-
* the cached bytes (and their placement semantics), nothing more. Any profile
|
|
54
|
-
* change — content, path, or mode — produces a new key; anything else
|
|
55
|
-
* (mount order, transport-only target paths, timestamps) does not.
|
|
56
|
-
*/
|
|
57
|
-
export function profileFileBundleKey(mounts) {
|
|
58
|
-
return canonicalCandidateDigest({
|
|
59
|
-
schema: PROFILE_BUNDLE_SCHEMA,
|
|
60
|
-
files: profileBundleFileTable(mounts),
|
|
61
|
-
});
|
|
62
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Advisory lru accounting for the profile bundle cache (see
|
|
3
|
-
* profile-bundle-cache.ts for the cache layout and durability invariants).
|
|
4
|
-
* `lru.json` is never load-bearing: it is rebuilt from a disk scan whenever it
|
|
5
|
-
* is absent or corrupt, and every write to it is best-effort so a full or
|
|
6
|
-
* read-only cache can never break admit, claim, or eviction.
|
|
7
|
-
*/
|
|
8
|
-
interface ProfileBundleLru {
|
|
9
|
-
v: 1;
|
|
10
|
-
objects: Record<string, {
|
|
11
|
-
bytes: number;
|
|
12
|
-
lastUsed: string;
|
|
13
|
-
}>;
|
|
14
|
-
bundles: Record<string, {
|
|
15
|
-
bytes: number;
|
|
16
|
-
lastUsed: string;
|
|
17
|
-
createdAt: string;
|
|
18
|
-
}>;
|
|
19
|
-
}
|
|
20
|
-
export declare function pathExistsFollowingLinks(path: string): Promise<boolean>;
|
|
21
|
-
export declare function fsyncDir(dir: string): Promise<void>;
|
|
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;
|
|
25
|
-
export declare function readManifestFile(path: string): Promise<{
|
|
26
|
-
schema?: unknown;
|
|
27
|
-
bundle?: unknown;
|
|
28
|
-
createdAt?: unknown;
|
|
29
|
-
files?: unknown;
|
|
30
|
-
} | null>;
|
|
31
|
-
/**
|
|
32
|
-
* Bundle keys come from `canonicalCandidateDigest` as `sha256:<hex>`; the `:`
|
|
33
|
-
* is encoded so the key is a safe single path segment on any host filesystem.
|
|
34
|
-
* The raw key is always recoverable from the manifest's `bundle` field.
|
|
35
|
-
*/
|
|
36
|
-
export declare function profileBundleManifestPath(root: string, bundle: string): string;
|
|
37
|
-
/** Sweep only while holding the per-root lock; active admissions own incoming/. */
|
|
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>;
|
|
41
|
-
export declare function loadLru(root: string): Promise<ProfileBundleLru>;
|
|
42
|
-
/** Best-effort by contract: lru corruption or a full cache must never break admit/claim. */
|
|
43
|
-
export declare function saveLru(root: string, lru: ProfileBundleLru): Promise<void>;
|
|
44
|
-
/** Eviction body; callers must already hold the per-root lock. */
|
|
45
|
-
export declare function evictUnlocked(root: string, maxBytes: number): Promise<void>;
|
|
46
|
-
/**
|
|
47
|
-
* Evict least-recently-used objects until total object bytes are within
|
|
48
|
-
* `maxBytes`. Unreferenced objects go first (they reclaim space with no
|
|
49
|
-
* bundle impact); if the cap still does not hold, referenced objects are
|
|
50
|
-
* evicted oldest-first, which makes their bundles partial — claims then miss
|
|
51
|
-
* and re-deliver, so eviction can never serve wrong bytes. Bundle manifests
|
|
52
|
-
* referencing no existing objects are removed. Mid-admit `incoming/` dirs are
|
|
53
|
-
* never touched.
|
|
54
|
-
*/
|
|
55
|
-
export declare function evictProfileBundles(root: string, maxBytes: number): Promise<void>;
|
|
56
|
-
export {};
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { open, readdir, readFile, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
4
|
-
import { compareStrings } from "./workspace-files.js";
|
|
5
|
-
export async function pathExistsFollowingLinks(path) {
|
|
6
|
-
try {
|
|
7
|
-
await stat(path);
|
|
8
|
-
return true;
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
export async function fsyncDir(dir) {
|
|
15
|
-
try {
|
|
16
|
-
const handle = await open(dir, "r");
|
|
17
|
-
try {
|
|
18
|
-
await handle.sync();
|
|
19
|
-
}
|
|
20
|
-
finally {
|
|
21
|
-
await handle.close();
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
// Best-effort durability: a cache without directory fsync still verifies.
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
/** Serialize admissions, sweeps, and evictions per cache root. */
|
|
29
|
-
const rootQueues = new Map();
|
|
30
|
-
export function withRootLock(root, run) {
|
|
31
|
-
const key = resolve(root);
|
|
32
|
-
const previous = rootQueues.get(key) ?? Promise.resolve();
|
|
33
|
-
const next = previous.then(run, run);
|
|
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
|
-
});
|
|
40
|
-
return next;
|
|
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
|
-
}
|
|
46
|
-
export async function readManifestFile(path) {
|
|
47
|
-
try {
|
|
48
|
-
return JSON.parse(await readFile(path, "utf8"));
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Bundle keys come from `canonicalCandidateDigest` as `sha256:<hex>`; the `:`
|
|
56
|
-
* is encoded so the key is a safe single path segment on any host filesystem.
|
|
57
|
-
* The raw key is always recoverable from the manifest's `bundle` field.
|
|
58
|
-
*/
|
|
59
|
-
export function profileBundleManifestPath(root, bundle) {
|
|
60
|
-
const filename = bundle.replace(/[^A-Za-z0-9._-]/g, (char) => `_${char.codePointAt(0)?.toString(16) ?? "3f"}_`);
|
|
61
|
-
return join(root, "bundles", `${filename}.json`);
|
|
62
|
-
}
|
|
63
|
-
/** Sweep only while holding the per-root lock; active admissions own incoming/. */
|
|
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) {
|
|
69
|
-
const incoming = join(root, "incoming");
|
|
70
|
-
let entries;
|
|
71
|
-
try {
|
|
72
|
-
entries = await readdir(incoming);
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
for (const entry of entries) {
|
|
78
|
-
await rm(join(incoming, entry), { recursive: true, force: true });
|
|
79
|
-
}
|
|
80
|
-
await fsyncDir(incoming);
|
|
81
|
-
}
|
|
82
|
-
async function rebuildLru(root) {
|
|
83
|
-
const lru = { v: 1, objects: {}, bundles: {} };
|
|
84
|
-
try {
|
|
85
|
-
for (const name of await readdir(join(root, "objects"))) {
|
|
86
|
-
const info = await stat(join(root, "objects", name));
|
|
87
|
-
if (info.isFile()) {
|
|
88
|
-
lru.objects[name] = {
|
|
89
|
-
bytes: info.size,
|
|
90
|
-
lastUsed: info.mtime.toISOString(),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
// Missing objects dir: fresh cache.
|
|
97
|
-
}
|
|
98
|
-
try {
|
|
99
|
-
for (const name of await readdir(join(root, "bundles"))) {
|
|
100
|
-
const manifest = await readManifestFile(join(root, "bundles", name));
|
|
101
|
-
if (!manifest || typeof manifest.bundle !== "string")
|
|
102
|
-
continue;
|
|
103
|
-
const info = await stat(join(root, "bundles", name));
|
|
104
|
-
const files = Array.isArray(manifest.files) ? manifest.files : [];
|
|
105
|
-
const referenced = files.map((file) => file.sha256);
|
|
106
|
-
const bytes = referenced.reduce((sum, sha) => sum + (lru.objects[sha ?? ""]?.bytes ?? 0), 0);
|
|
107
|
-
lru.bundles[manifest.bundle] = {
|
|
108
|
-
bytes,
|
|
109
|
-
lastUsed: info.mtime.toISOString(),
|
|
110
|
-
createdAt: typeof manifest.createdAt === "string"
|
|
111
|
-
? manifest.createdAt
|
|
112
|
-
: info.mtime.toISOString(),
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
// Missing bundles dir: fresh cache.
|
|
118
|
-
}
|
|
119
|
-
return lru;
|
|
120
|
-
}
|
|
121
|
-
export async function loadLru(root) {
|
|
122
|
-
try {
|
|
123
|
-
const parsed = JSON.parse(await readFile(join(root, "lru.json"), "utf8"));
|
|
124
|
-
if (parsed &&
|
|
125
|
-
parsed.v === 1 &&
|
|
126
|
-
typeof parsed.objects === "object" &&
|
|
127
|
-
typeof parsed.bundles === "object") {
|
|
128
|
-
return parsed;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
// Corrupt or missing: rebuild below.
|
|
133
|
-
}
|
|
134
|
-
return rebuildLru(root);
|
|
135
|
-
}
|
|
136
|
-
/** Best-effort by contract: lru corruption or a full cache must never break admit/claim. */
|
|
137
|
-
export async function saveLru(root, lru) {
|
|
138
|
-
try {
|
|
139
|
-
const path = join(root, "lru.json");
|
|
140
|
-
const temporary = `${path}.tmp-${randomUUID()}`;
|
|
141
|
-
await writeFile(temporary, JSON.stringify(lru));
|
|
142
|
-
await rename(temporary, path);
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
// Read-only or full cache: eviction still held on disk.
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
/** Eviction body; callers must already hold the per-root lock. */
|
|
149
|
-
export async function evictUnlocked(root, maxBytes) {
|
|
150
|
-
const lru = await loadLru(root);
|
|
151
|
-
const objectsDir = join(root, "objects");
|
|
152
|
-
for (const sha of Object.keys(lru.objects)) {
|
|
153
|
-
if (!(await pathExistsFollowingLinks(join(objectsDir, sha)))) {
|
|
154
|
-
delete lru.objects[sha];
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
const manifests = new Map();
|
|
158
|
-
const bundlesDir = join(root, "bundles");
|
|
159
|
-
for (const name of await readdir(bundlesDir)) {
|
|
160
|
-
const manifest = await readManifestFile(join(bundlesDir, name));
|
|
161
|
-
if (manifest && typeof manifest.bundle === "string") {
|
|
162
|
-
manifests.set(manifest.bundle, manifest);
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
await rm(join(bundlesDir, name), { force: true });
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const referenced = new Set();
|
|
169
|
-
for (const manifest of manifests.values()) {
|
|
170
|
-
const files = Array.isArray(manifest.files) ? manifest.files : [];
|
|
171
|
-
for (const file of files) {
|
|
172
|
-
const sha = file.sha256;
|
|
173
|
-
if (typeof sha === "string")
|
|
174
|
-
referenced.add(sha);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
let total = Object.values(lru.objects).reduce((sum, o) => sum + o.bytes, 0);
|
|
178
|
-
const byLastUsed = (sha) => lru.objects[sha]?.lastUsed ?? "";
|
|
179
|
-
const live = Object.keys(lru.objects);
|
|
180
|
-
const unreferenced = live
|
|
181
|
-
.filter((sha) => !referenced.has(sha))
|
|
182
|
-
.sort((a, b) => compareStrings(byLastUsed(a), byLastUsed(b)));
|
|
183
|
-
const referencedLive = live
|
|
184
|
-
.filter((sha) => referenced.has(sha))
|
|
185
|
-
.sort((a, b) => compareStrings(byLastUsed(a), byLastUsed(b)));
|
|
186
|
-
for (const sha of [...unreferenced, ...referencedLive]) {
|
|
187
|
-
if (total <= maxBytes)
|
|
188
|
-
break;
|
|
189
|
-
await rm(join(objectsDir, sha), { force: true });
|
|
190
|
-
total -= lru.objects[sha]?.bytes ?? 0;
|
|
191
|
-
delete lru.objects[sha];
|
|
192
|
-
}
|
|
193
|
-
// Bundles whose every object is gone can never be claimed; drop their manifests.
|
|
194
|
-
for (const [bundle, manifest] of manifests) {
|
|
195
|
-
const files = Array.isArray(manifest.files) ? manifest.files : [];
|
|
196
|
-
const alive = files.some((file) => Boolean(lru.objects[file.sha256 ?? ""]));
|
|
197
|
-
if (!alive) {
|
|
198
|
-
await rm(profileBundleManifestPath(root, bundle), { force: true });
|
|
199
|
-
delete lru.bundles[bundle];
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
await fsyncDir(objectsDir);
|
|
203
|
-
await fsyncDir(bundlesDir);
|
|
204
|
-
await saveLru(root, lru);
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Evict least-recently-used objects until total object bytes are within
|
|
208
|
-
* `maxBytes`. Unreferenced objects go first (they reclaim space with no
|
|
209
|
-
* bundle impact); if the cap still does not hold, referenced objects are
|
|
210
|
-
* evicted oldest-first, which makes their bundles partial — claims then miss
|
|
211
|
-
* and re-deliver, so eviction can never serve wrong bytes. Bundle manifests
|
|
212
|
-
* referencing no existing objects are removed. Mid-admit `incoming/` dirs are
|
|
213
|
-
* never touched.
|
|
214
|
-
*/
|
|
215
|
-
export async function evictProfileBundles(root, maxBytes) {
|
|
216
|
-
if (!Number.isFinite(maxBytes) || maxBytes < 0) {
|
|
217
|
-
throw new Error(`Profile bundle cache maxBytes must be >= 0: ${maxBytes}`);
|
|
218
|
-
}
|
|
219
|
-
await withRootLock(root, () => evictUnlocked(root, maxBytes));
|
|
220
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { AgentProfile, AgentProfileSecretProvider, AgentRuntimeAttachments } from "@tangle-network/agent-interface";
|
|
2
|
-
import { type HarnessId } from "./harness.js";
|
|
3
|
-
import { type BindableLaunchControl, type PlanFileRoot, type SkippableDimension, type WorkspacePlan } from "./plan-types.js";
|
|
4
|
-
export declare const UNAVAILABLE_AGENT_PROFILE_SECRET_PROVIDER: AgentProfileSecretProvider;
|
|
5
|
-
/**
|
|
6
|
-
* Why a profile's tool and permission controls cannot reach one native policy
|
|
7
|
-
* on this harness, or undefined when they can.
|
|
8
|
-
*
|
|
9
|
-
* Admission boundaries (profile save, Sandbox create) call this so a
|
|
10
|
-
* contradictory control set is refused when it is submitted, not when a
|
|
11
|
-
* user's turn materializes it. Controls a harness cannot enforce are not
|
|
12
|
-
* contradictions: `resources.failOnError` decides those per turn.
|
|
13
|
-
*/
|
|
14
|
-
export declare function executionControlConflict(profile: Pick<AgentProfile, "tools" | "permissions" | "mcp">, harness: HarnessId): string | undefined;
|
|
15
|
-
/**
|
|
16
|
-
* Build a pure workspace plan for one canonical harness.
|
|
17
|
-
* The caller applies files, environment, and flags at its process boundary.
|
|
18
|
-
*/
|
|
19
|
-
export declare function materializeProfile(inputProfile: AgentProfile, harness: HarnessId, opts?: {
|
|
20
|
-
skip?: SkippableDimension[];
|
|
21
|
-
binds?: readonly BindableLaunchControl[];
|
|
22
|
-
instructionsRoot?: PlanFileRoot;
|
|
23
|
-
runtimeAttachments?: AgentRuntimeAttachments;
|
|
24
|
-
}): WorkspacePlan;
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { AgentRuntimeAttachmentsSchema, agentProfileSchema, } from "@tangle-network/agent-interface";
|
|
2
|
-
import { materializeExecutionControls } from "./execution-controls.js";
|
|
3
|
-
import { assertMaterializerHarness, harnessAutoLoadsAgentContextFile, } from "./harness.js";
|
|
4
|
-
import { materializeHooks } from "./hooks.js";
|
|
5
|
-
import { materializeMcp } from "./mcp.js";
|
|
6
|
-
import { assertBindableLaunchControls, assertSkippableDimensions, } from "./plan-types.js";
|
|
7
|
-
import { createProfilePlanBuilder } from "./profile-plan.js";
|
|
8
|
-
import { lowerProfilePrompt } from "./profile-prompt.js";
|
|
9
|
-
import { activeMcpServers, extensionNamespaces } from "./profile-support.js";
|
|
10
|
-
import { lowerProfileCommands, lowerProfileResources, lowerProfileSkills, } from "./resource-lowering.js";
|
|
11
|
-
import { materializeSubagent } from "./subagents.js";
|
|
12
|
-
export const UNAVAILABLE_AGENT_PROFILE_SECRET_PROVIDER = Object.freeze({
|
|
13
|
-
async get() {
|
|
14
|
-
return undefined;
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
/**
|
|
18
|
-
* Why a profile's tool and permission controls cannot reach one native policy
|
|
19
|
-
* on this harness, or undefined when they can.
|
|
20
|
-
*
|
|
21
|
-
* Admission boundaries (profile save, Sandbox create) call this so a
|
|
22
|
-
* contradictory control set is refused when it is submitted, not when a
|
|
23
|
-
* user's turn materializes it. Controls a harness cannot enforce are not
|
|
24
|
-
* contradictions: `resources.failOnError` decides those per turn.
|
|
25
|
-
*/
|
|
26
|
-
export function executionControlConflict(profile, harness) {
|
|
27
|
-
const builder = createProfilePlanBuilder(harness);
|
|
28
|
-
try {
|
|
29
|
-
materializeExecutionControls(harness, profile.tools, profile.permissions, undefined, builder.plan, builder.add, builder.unsupported, Object.keys(activeMcpServers(profile.mcp)));
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
return error instanceof Error ? error.message : String(error);
|
|
33
|
-
}
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Build a pure workspace plan for one canonical harness.
|
|
38
|
-
* The caller applies files, environment, and flags at its process boundary.
|
|
39
|
-
*/
|
|
40
|
-
export function materializeProfile(inputProfile, harness, opts = {}) {
|
|
41
|
-
assertMaterializerHarness(harness);
|
|
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
|
-
}
|
|
47
|
-
const skip = new Set(assertSkippableDimensions(opts.skip));
|
|
48
|
-
const binds = assertBindableLaunchControls(opts.binds);
|
|
49
|
-
const instructionsRoot = opts.instructionsRoot ?? "workspace";
|
|
50
|
-
if (instructionsRoot !== "workspace" && instructionsRoot !== "agent") {
|
|
51
|
-
throw new Error(`materializeProfile: unsupported instructions root ${String(instructionsRoot)}`);
|
|
52
|
-
}
|
|
53
|
-
if (instructionsRoot === "agent" &&
|
|
54
|
-
!harnessAutoLoadsAgentContextFile(harness)) {
|
|
55
|
-
throw new Error(`materializeProfile: harness "${harness}" does not load instructions from its agent directory`);
|
|
56
|
-
}
|
|
57
|
-
const builder = createProfilePlanBuilder(harness);
|
|
58
|
-
lowerProfileResources(profile, harness, builder);
|
|
59
|
-
lowerProfilePrompt(profile, harness, binds, instructionsRoot, builder);
|
|
60
|
-
materializeExecutionControls(harness, profile.tools, profile.permissions, profile.modes, builder.plan, builder.add, builder.unsupported, skip.has("mcp") ? [] : Object.keys(activeMcpServers(profile.mcp)));
|
|
61
|
-
if ((profile.connections?.length ?? 0) > 0) {
|
|
62
|
-
builder.unsupported("connections", `${harness}: Hub connections must be resolved to authorized MCP servers by the execution runtime`);
|
|
63
|
-
}
|
|
64
|
-
if (profile.confidential !== undefined) {
|
|
65
|
-
builder.unsupported("confidential", `${harness}: confidential execution requires runtime placement and attestation, not workspace files`);
|
|
66
|
-
}
|
|
67
|
-
lowerProfileSkills(profile, harness, builder);
|
|
68
|
-
const attachmentMcp = opts.runtimeAttachments === undefined
|
|
69
|
-
? undefined
|
|
70
|
-
: AgentRuntimeAttachmentsSchema.parse(opts.runtimeAttachments).mcp;
|
|
71
|
-
if (attachmentMcp && skip.has("mcp")) {
|
|
72
|
-
throw new Error("Runtime MCP attachments cannot be skipped");
|
|
73
|
-
}
|
|
74
|
-
const validatedAttachments = attachmentMcp;
|
|
75
|
-
for (const alias of Object.keys(validatedAttachments ?? {})) {
|
|
76
|
-
if (validatedAttachments?.[alias]?.enabled === false) {
|
|
77
|
-
throw new Error(`Runtime MCP attachment cannot be disabled: ${alias}`);
|
|
78
|
-
}
|
|
79
|
-
if (Object.hasOwn(profile.mcp ?? {}, alias)) {
|
|
80
|
-
throw new Error(`Runtime MCP attachment alias conflicts with profile: ${alias}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
const mcp = activeMcpServers({ ...profile.mcp, ...validatedAttachments });
|
|
84
|
-
if (!skip.has("mcp") && Object.keys(mcp).length > 0) {
|
|
85
|
-
materializeMcp(harness, mcp, builder.plan, builder.add, builder.unsupported);
|
|
86
|
-
if (attachmentMcp &&
|
|
87
|
-
builder.plan.unsupported.some((entry) => entry.dimension === "mcp")) {
|
|
88
|
-
throw new Error(`${harness} cannot materialize the requested runtime MCP attachments`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const hooks = profile.hooks ?? {};
|
|
92
|
-
if (Object.keys(hooks).length > 0) {
|
|
93
|
-
materializeHooks(harness, hooks, builder.add, builder.unsupported, builder.plan);
|
|
94
|
-
}
|
|
95
|
-
const subagents = profile.subagents ?? {};
|
|
96
|
-
for (const [name, subagent] of Object.entries(subagents)) {
|
|
97
|
-
materializeSubagent(harness, name, subagent, builder.plan, builder.add, builder.unsupported);
|
|
98
|
-
}
|
|
99
|
-
if (!skip.has("extensions")) {
|
|
100
|
-
for (const namespace of extensionNamespaces(harness)) {
|
|
101
|
-
const extension = profile.extensions?.[namespace];
|
|
102
|
-
// sdk-provider-pi reads extensions.pi natively (resolvePiExtensionArgs), with the same
|
|
103
|
-
// contract as cli-bridge's pi backend. Recording it as unsupported here would reject a
|
|
104
|
-
// profile that sets resources.failOnError, although the provider honors it.
|
|
105
|
-
if (harness === "pi")
|
|
106
|
-
continue;
|
|
107
|
-
if (extension && Object.keys(extension).length > 0) {
|
|
108
|
-
builder.unsupported("extensions", `${harness}: extension namespace "${namespace}" requires provider-native handling`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
lowerProfileCommands(profile, harness, builder);
|
|
113
|
-
return builder.finalize(profile.resources?.failOnError);
|
|
114
|
-
}
|
package/dist/profile-plan.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { AgentProfileConfigValue } from "@tangle-network/agent-interface";
|
|
2
|
-
import type { HarnessId } from "./harness.js";
|
|
3
|
-
import type { AddPlanFile, SerializedPlanFileContent } from "./plan-json.js";
|
|
4
|
-
import type { PlanFileRoot, Unsupported, WorkspacePlan } from "./plan-types.js";
|
|
5
|
-
export interface ProfilePlanBuilder {
|
|
6
|
-
plan: WorkspacePlan;
|
|
7
|
-
add: AddPlanFile;
|
|
8
|
-
addAtRoot: AddRootedPlanFile;
|
|
9
|
-
addExplicitResource: AddExplicitResource;
|
|
10
|
-
unsupported: (dimension: Unsupported["dimension"], reason: string) => void;
|
|
11
|
-
finalize: (failOnError?: boolean) => WorkspacePlan;
|
|
12
|
-
}
|
|
13
|
-
export type AddRootedPlanFile = (root: PlanFileRoot, path: string, content: string | SerializedPlanFileContent, mode?: number) => void;
|
|
14
|
-
export type AddExplicitResource = (dimension: Unsupported["dimension"], path: string, content: string, mode?: number) => void;
|
|
15
|
-
export declare function createProfilePlanBuilder(harness: HarnessId): ProfilePlanBuilder;
|
|
16
|
-
export declare function setPlanEnvironment(plan: WorkspacePlan, name: string, value: string | AgentProfileConfigValue): void;
|
package/dist/profile-plan.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { isMaterializerOwnedPath, MATERIALIZER_RESERVED_PATHS, } from "./harness.js";
|
|
2
|
-
import { workspacePlanConfigValue } from "./plan-json.js";
|
|
3
|
-
import { mergeJsonFiles } from "./plan-normalize.js";
|
|
4
|
-
import { formatUnsupported } from "./plan-support.js";
|
|
5
|
-
import { compareStrings, normalizeWorkspaceRelativePath, PRIVATE_FILE_MODE, validateFileMode, } from "./workspace-files.js";
|
|
6
|
-
export function createProfilePlanBuilder(harness) {
|
|
7
|
-
const plan = {
|
|
8
|
-
harness,
|
|
9
|
-
files: [],
|
|
10
|
-
env: {},
|
|
11
|
-
flags: [],
|
|
12
|
-
unsupported: [],
|
|
13
|
-
requires: [],
|
|
14
|
-
};
|
|
15
|
-
const pushPlanFile = (relPath, serialized, source, mode, root = "workspace") => {
|
|
16
|
-
const normalizedPath = normalizeWorkspaceRelativePath(relPath);
|
|
17
|
-
const content = typeof serialized === "string" ? serialized : serialized.content;
|
|
18
|
-
const secretSlots = typeof serialized === "string" ? undefined : serialized.secretSlots;
|
|
19
|
-
const fileMode = mode ??
|
|
20
|
-
(secretSlots !== undefined && secretSlots.length > 0
|
|
21
|
-
? PRIVATE_FILE_MODE
|
|
22
|
-
: undefined);
|
|
23
|
-
plan.files.push({
|
|
24
|
-
relPath: normalizedPath,
|
|
25
|
-
content,
|
|
26
|
-
source,
|
|
27
|
-
...(root === "workspace" ? {} : { root }),
|
|
28
|
-
...(source === "generated" && isMaterializerOwnedPath(normalizedPath)
|
|
29
|
-
? { ownership: "materializer" }
|
|
30
|
-
: {}),
|
|
31
|
-
...(fileMode === undefined ? {} : { mode: validateFileMode(fileMode) }),
|
|
32
|
-
...(secretSlots === undefined
|
|
33
|
-
? {}
|
|
34
|
-
: {
|
|
35
|
-
secretSlots: secretSlots.map((slot) => ({
|
|
36
|
-
path: [...slot.path],
|
|
37
|
-
ref: { ...slot.ref },
|
|
38
|
-
})),
|
|
39
|
-
}),
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
const add = (relPath, content, mode) => pushPlanFile(relPath, content, "generated", mode);
|
|
43
|
-
const addAtRoot = (root, relPath, content, mode) => pushPlanFile(relPath, content, "generated", mode, root);
|
|
44
|
-
const unsupported = (dimension, reason) => plan.unsupported.push({ dimension, reason });
|
|
45
|
-
const explicitResourcePaths = new Map();
|
|
46
|
-
const addExplicitResource = (dimension, relPath, content, mode) => {
|
|
47
|
-
const normalizedPath = normalizeWorkspaceRelativePath(relPath);
|
|
48
|
-
const previousDimension = explicitResourcePaths.get(normalizedPath);
|
|
49
|
-
if (previousDimension) {
|
|
50
|
-
throw new Error(`Duplicate profile resource path: ${normalizedPath} (${previousDimension}, ${dimension})`);
|
|
51
|
-
}
|
|
52
|
-
if (MATERIALIZER_RESERVED_PATHS.has(normalizedPath)) {
|
|
53
|
-
throw new Error(`Profile resource path is reserved by the materializer: ${normalizedPath} (${dimension}). ` +
|
|
54
|
-
"This package writes that path itself and replaces it on re-application, so a mount " +
|
|
55
|
-
"there cannot survive; choose another path.");
|
|
56
|
-
}
|
|
57
|
-
explicitResourcePaths.set(normalizedPath, dimension);
|
|
58
|
-
pushPlanFile(normalizedPath, content, "user", mode);
|
|
59
|
-
};
|
|
60
|
-
return {
|
|
61
|
-
plan,
|
|
62
|
-
add,
|
|
63
|
-
addAtRoot,
|
|
64
|
-
addExplicitResource,
|
|
65
|
-
unsupported,
|
|
66
|
-
finalize(failOnError) {
|
|
67
|
-
for (const [relPath, dimension] of explicitResourcePaths) {
|
|
68
|
-
const writers = plan.files.filter((file) => (file.root ?? "workspace") === "workspace" &&
|
|
69
|
-
file.relPath === relPath).length;
|
|
70
|
-
if (writers > 1) {
|
|
71
|
-
throw new Error(`Profile ${dimension} resource conflicts with generated harness file: ${relPath}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
plan.files = mergeJsonFiles(plan.files).sort((a, b) => compareStrings(`${a.root ?? "workspace"}\0${a.relPath}`, `${b.root ?? "workspace"}\0${b.relPath}`));
|
|
75
|
-
if (failOnError && plan.unsupported.length) {
|
|
76
|
-
throw new Error(formatUnsupported(plan.unsupported));
|
|
77
|
-
}
|
|
78
|
-
return plan;
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
export function setPlanEnvironment(plan, name, value) {
|
|
83
|
-
const normalized = typeof value === "string" ? value : workspacePlanConfigValue(value);
|
|
84
|
-
const previous = plan.env[name];
|
|
85
|
-
if (previous !== undefined &&
|
|
86
|
-
JSON.stringify(previous) !== JSON.stringify(normalized)) {
|
|
87
|
-
throw new Error(`profile environment values disagree for ${name}`);
|
|
88
|
-
}
|
|
89
|
-
plan.env[name] = normalized;
|
|
90
|
-
}
|
package/dist/profile-prompt.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { AgentProfile } from "@tangle-network/agent-interface";
|
|
2
|
-
import type { BindableLaunchControl, HarnessId, PlanFileRoot } from "./plan-types.js";
|
|
3
|
-
import type { ProfilePlanBuilder } from "./profile-plan.js";
|
|
4
|
-
export declare function lowerProfilePrompt(profile: AgentProfile, harness: HarnessId, binds: ReadonlySet<BindableLaunchControl>, instructionsRoot: PlanFileRoot, builder: ProfilePlanBuilder): void;
|