@tangle-network/agent-profile-materialize 0.25.4 → 0.26.1
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 +2 -1
- package/dist/index.js +2 -1
- package/dist/managed-workspace-bind.d.ts +1 -1
- package/dist/managed-workspace-bind.js +11 -7
- package/dist/profile-start-check.d.ts +32 -0
- package/dist/profile-start-check.js +39 -0
- package/dist/secret-concurrent-revision.js +28 -1
- package/package.json +6 -2
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +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, openCodeMcpConfig } from "./mcp.js";
|
|
9
9
|
export { isMaterializerMcpConfig } from "./mcp-config-ownership.js";
|
|
10
|
-
export
|
|
10
|
+
export * from "./model-id.js";
|
|
11
11
|
export { buildPiModelsConfig, buildPiModelsConfigFile, PI_MODELS_CONFIG_PATH, type PiModelsConfigFile, type PiModelsConfigInput, type PiRuntimePaths, resolvePiRuntimePaths, } from "./pi-models-config.js";
|
|
12
12
|
export { applyWorkspacePlan, applyWorkspacePlanWithSecretProvider, } from "./plan-application.js";
|
|
13
13
|
export { agentCandidateProfilePlanMaterial, canonicalizeAgentCandidateProfilePlan, captureAgentCandidateProfilePlan, hashWorkspacePlan, } from "./plan-digest.js";
|
|
@@ -22,6 +22,7 @@ export type { PrimeProfileControls, PrimeProfileSkill, PrimeProfileSubagent, } f
|
|
|
22
22
|
export { assertPrimeModelAgreement, hasPrimeProfileMaterial, primeProfileNeedsAgentDir, primeSubagentEntryId, readPrimeProfileControls, } from "./prime-profile.js";
|
|
23
23
|
export * from "./profile-bundle-cache.js";
|
|
24
24
|
export { executionControlConflict, materializeProfile, UNAVAILABLE_AGENT_PROFILE_SECRET_PROVIDER, } from "./profile-materializer.js";
|
|
25
|
+
export * from "./profile-start-check.js";
|
|
25
26
|
export type { ResolveAgentProfileResourcesOptions } from "./resource-resolution.js";
|
|
26
27
|
export { normalizeSkillMd, resolveAgentProfileResources, } from "./resource-resolution.js";
|
|
27
28
|
export { prepareSecretConcurrentProfileRevision } from "./secret-concurrent-revision.js";
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export * from "./harness-prompt.js";
|
|
|
5
5
|
export { ensurePublicConcurrentProfileRoots, preparePublicConcurrentProfileRevision, } from "./managed-workspace.js";
|
|
6
6
|
export { openCodeMcpConfig } from "./mcp.js";
|
|
7
7
|
export { isMaterializerMcpConfig } from "./mcp-config-ownership.js";
|
|
8
|
-
export
|
|
8
|
+
export * from "./model-id.js";
|
|
9
9
|
export { buildPiModelsConfig, buildPiModelsConfigFile, PI_MODELS_CONFIG_PATH, resolvePiRuntimePaths, } from "./pi-models-config.js";
|
|
10
10
|
export { applyWorkspacePlan, applyWorkspacePlanWithSecretProvider, } from "./plan-application.js";
|
|
11
11
|
export { agentCandidateProfilePlanMaterial, canonicalizeAgentCandidateProfilePlan, captureAgentCandidateProfilePlan, hashWorkspacePlan, } from "./plan-digest.js";
|
|
@@ -17,5 +17,6 @@ export { matchPrimeKernelDeadMarker, PRIME_AGENT_DIR_ENV_VAR, PRIME_AGENT_DIR_EN
|
|
|
17
17
|
export { assertPrimeModelAgreement, hasPrimeProfileMaterial, primeProfileNeedsAgentDir, primeSubagentEntryId, readPrimeProfileControls, } from "./prime-profile.js";
|
|
18
18
|
export * from "./profile-bundle-cache.js";
|
|
19
19
|
export { executionControlConflict, materializeProfile, UNAVAILABLE_AGENT_PROFILE_SECRET_PROVIDER, } from "./profile-materializer.js";
|
|
20
|
+
export * from "./profile-start-check.js";
|
|
20
21
|
export { normalizeSkillMd, resolveAgentProfileResources, } from "./resource-resolution.js";
|
|
21
22
|
export { prepareSecretConcurrentProfileRevision } from "./secret-concurrent-revision.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { WorkspacePlan, WorkspacePlanApplication } from "./plan-types.js";
|
|
2
2
|
export declare function bindConcurrentLaunch(plan: WorkspacePlan, application: WorkspacePlanApplication, revisionWorkspaceDir: string, options?: {
|
|
3
|
-
readonly
|
|
3
|
+
readonly secretOpenCodeRevision?: boolean;
|
|
4
4
|
}): WorkspacePlanApplication["spawn"];
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
/**
|
|
3
|
-
* Paths OpenCode
|
|
4
|
-
*
|
|
3
|
+
* Paths a secret OpenCode revision serves from its own immutable tree:
|
|
4
|
+
* read-only profile content under `reference/`, skills, and OpenCode's
|
|
5
|
+
* standard agent and command definitions (OpenCode reads
|
|
5
6
|
* `.opencode/{agent,agents}/**\/*.md` and `.opencode/{command,commands}/**\/*.md`).
|
|
6
7
|
*/
|
|
7
8
|
const SECRET_REVISION_NATIVE_PATH = /^(?:reference\/|\.opencode\/skills\/|\.opencode\/(?:agents?|commands?)\/(?:[^./][^/]*\/)*[^./][^/]*\.md$)/u;
|
|
8
9
|
/**
|
|
9
|
-
* A secret OpenCode
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* A secret OpenCode turn launches with its revision's `workspace` as cwd and
|
|
11
|
+
* that tree's `.opencode` as its explicit config directory (the sealed launch
|
|
12
|
+
* in sdk-provider-opencode). So skills, agents and commands load from the
|
|
13
|
+
* digest-attested revision, and a cwd-relative `reference/` path resolves to
|
|
14
|
+
* this revision's own read-only copy, never to the shared workspace that
|
|
15
|
+
* concurrent revisions share. The binding stays limited to these paths, never
|
|
16
|
+
* an app's own directory.
|
|
13
17
|
*/
|
|
14
18
|
function ridesSecretRevision(plan, file, options) {
|
|
15
|
-
return (options.
|
|
19
|
+
return (options.secretOpenCodeRevision === true &&
|
|
16
20
|
plan.harness === "opencode" &&
|
|
17
21
|
(file.root ?? "workspace") === "workspace" &&
|
|
18
22
|
SECRET_REVISION_NATIVE_PATH.test(file.relPath));
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BindableLaunchControl } from "./plan-types.js";
|
|
2
|
+
import { type ResolveAgentProfileResourcesOptions } from "./resource-resolution.js";
|
|
3
|
+
/** Whether a profile starts on a harness, answered before any spawn. */
|
|
4
|
+
export interface ProfileStartCheck {
|
|
5
|
+
readonly ok: boolean;
|
|
6
|
+
/** Each error a spawn would raise, in the materializer's own words. */
|
|
7
|
+
readonly refusals: readonly string[];
|
|
8
|
+
/** Controls `resources.failOnError: false` lets the run skip, as a spawn logs them. */
|
|
9
|
+
readonly warnings: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
export interface CheckProfileStartsOptions extends ResolveAgentProfileResourcesOptions {
|
|
12
|
+
/** Harness to check; defaults to the profile's own `harness`. */
|
|
13
|
+
harness?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Launch controls the launcher binds. Defaults to what the maintained
|
|
16
|
+
* launchers bind: `systemPrompt` on opencode (the sidecar and the opencode
|
|
17
|
+
* provider both pass it), nothing elsewhere, where binds change no refusal.
|
|
18
|
+
*/
|
|
19
|
+
binds?: readonly BindableLaunchControl[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run the spawn path's profile steps without spawning: resolve resources,
|
|
23
|
+
* materialize for the harness, then enforce `resources.failOnError`.
|
|
24
|
+
*
|
|
25
|
+
* A refusal here is the error a spawn gives, word for word, because it is the
|
|
26
|
+
* same code. Profile builders and validators call this instead of copying the
|
|
27
|
+
* native tool maps, which change whenever a harness does. Inline resources
|
|
28
|
+
* need no network; GitHub refs are fetched exactly as a spawn fetches them.
|
|
29
|
+
* Model and harness compatibility is not decided here: its owner is
|
|
30
|
+
* `harnessModelSupportFor` in `@tangle-network/cli-agent-registry`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function checkProfileStarts(input: unknown, options?: CheckProfileStartsOptions): Promise<ProfileStartCheck>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { agentProfileSchema } from "@tangle-network/agent-interface";
|
|
2
|
+
import { assertMaterializerHarness } from "./harness.js";
|
|
3
|
+
import { enforceWorkspacePlanSupport } from "./plan-support.js";
|
|
4
|
+
import { materializeProfile } from "./profile-materializer.js";
|
|
5
|
+
import { resolveAgentProfileResources, } from "./resource-resolution.js";
|
|
6
|
+
/**
|
|
7
|
+
* Run the spawn path's profile steps without spawning: resolve resources,
|
|
8
|
+
* materialize for the harness, then enforce `resources.failOnError`.
|
|
9
|
+
*
|
|
10
|
+
* A refusal here is the error a spawn gives, word for word, because it is the
|
|
11
|
+
* same code. Profile builders and validators call this instead of copying the
|
|
12
|
+
* native tool maps, which change whenever a harness does. Inline resources
|
|
13
|
+
* need no network; GitHub refs are fetched exactly as a spawn fetches them.
|
|
14
|
+
* Model and harness compatibility is not decided here: its owner is
|
|
15
|
+
* `harnessModelSupportFor` in `@tangle-network/cli-agent-registry`.
|
|
16
|
+
*/
|
|
17
|
+
export async function checkProfileStarts(input, options = {}) {
|
|
18
|
+
const { harness: harnessOption, binds, ...resolveOptions } = options;
|
|
19
|
+
try {
|
|
20
|
+
const parsed = agentProfileSchema.parse(input);
|
|
21
|
+
const harness = harnessOption ?? parsed.harness;
|
|
22
|
+
if (harness === undefined) {
|
|
23
|
+
return refused(["harness: the profile names no harness to start on"]);
|
|
24
|
+
}
|
|
25
|
+
assertMaterializerHarness(harness);
|
|
26
|
+
const profile = await resolveAgentProfileResources(parsed, resolveOptions);
|
|
27
|
+
const plan = materializeProfile(profile, harness, {
|
|
28
|
+
binds: binds ?? (harness === "opencode" ? ["systemPrompt"] : []),
|
|
29
|
+
});
|
|
30
|
+
const warnings = enforceWorkspacePlanSupport(plan, profile.resources?.failOnError);
|
|
31
|
+
return { ok: true, refusals: [], warnings };
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return refused([error instanceof Error ? error.message : String(error)]);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function refused(refusals) {
|
|
38
|
+
return { ok: false, refusals, warnings: [] };
|
|
39
|
+
}
|
|
@@ -20,6 +20,18 @@ export async function prepareSecretConcurrentProfileRevision(input) {
|
|
|
20
20
|
!input.ownerId) {
|
|
21
21
|
throw new Error("Secret concurrent profile needs a root executor and reserved identity");
|
|
22
22
|
}
|
|
23
|
+
for (const name of [
|
|
24
|
+
"OPENCODE_DISABLE_PROJECT_CONFIG",
|
|
25
|
+
"OPENCODE_CONFIG_DIR",
|
|
26
|
+
"TANGLE_SHARED_WORKSPACE",
|
|
27
|
+
]) {
|
|
28
|
+
if (Object.hasOwn(input.plan.env, name)) {
|
|
29
|
+
throw new Error(`Secret OpenCode reserves ${name} for its launch binding`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Workspace `reference/` files stay in this revision's read-only tree. The
|
|
33
|
+
// sealed launch runs from that tree, so a cwd-relative `reference/` path
|
|
34
|
+
// reads this revision's copy and nothing is written to the shared workspace.
|
|
23
35
|
let plan = prepareExplicitOpenCodePlan(input.plan);
|
|
24
36
|
if (input.providerConfig) {
|
|
25
37
|
plan = {
|
|
@@ -53,7 +65,13 @@ export async function prepareSecretConcurrentProfileRevision(input) {
|
|
|
53
65
|
recursive: true,
|
|
54
66
|
mode: 0o700,
|
|
55
67
|
});
|
|
68
|
+
mkdirSync(join(prepared.root, "workspace", ".opencode"), {
|
|
69
|
+
recursive: true,
|
|
70
|
+
mode: 0o700,
|
|
71
|
+
});
|
|
56
72
|
const identity = createHmac("sha256", input.namingKey)
|
|
73
|
+
.update(input.sharedWorkspaceDir)
|
|
74
|
+
.update("\0")
|
|
57
75
|
.update(input.ownerId)
|
|
58
76
|
.update("\0")
|
|
59
77
|
.update(String(input.subprocessUid))
|
|
@@ -61,7 +79,16 @@ export async function prepareSecretConcurrentProfileRevision(input) {
|
|
|
61
79
|
.update(prepared.manifest.materializedDigest)
|
|
62
80
|
.digest("hex");
|
|
63
81
|
const directory = join(input.launchRoot, `revision-${identity}`);
|
|
64
|
-
const
|
|
82
|
+
const bound = bindConcurrentLaunch(plan, prepared.application, join(directory, "workspace"), { secretOpenCodeRevision: true });
|
|
83
|
+
const spawn = {
|
|
84
|
+
...bound,
|
|
85
|
+
env: {
|
|
86
|
+
...bound.env,
|
|
87
|
+
OPENCODE_DISABLE_PROJECT_CONFIG: "1",
|
|
88
|
+
OPENCODE_CONFIG_DIR: join(directory, "workspace", ".opencode"),
|
|
89
|
+
TANGLE_SHARED_WORKSPACE: input.sharedWorkspaceDir,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
65
92
|
prepared.manifest.launchDigest = createHash("sha256")
|
|
66
93
|
.update(JSON.stringify(spawn))
|
|
67
94
|
.digest("hex");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-profile-materialize",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.1",
|
|
4
4
|
"description": "Materialize an AgentProfile into the native per-harness workspace files (the one shared materializer for cli-bridge, agent-container, and VB).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@tangle-network/agent-interface": "^3.0.0 || ^4.0.0 || ^5.0.0"
|
|
36
36
|
},
|
|
37
|
+
"tangleRelease": {
|
|
38
|
+
"sourceSha": "849b4be77fab1a4702850664c67dfcb8079bceb4",
|
|
39
|
+
"mergedAt": "2026-10-18T23:46:03-06:00"
|
|
40
|
+
},
|
|
37
41
|
"scripts": {
|
|
38
42
|
"build": "tsc -p tsconfig.json",
|
|
39
43
|
"check-types": "tsc --noEmit",
|
|
@@ -41,7 +45,7 @@
|
|
|
41
45
|
"test": "pnpm run test:installed-peer && pnpm run test:current-peer",
|
|
42
46
|
"test:installed-peer": "vitest run",
|
|
43
47
|
"test:current-peer": "vitest run --config vitest.current-peer.config.ts",
|
|
44
|
-
"test:root": "node ../../scripts/ci/vitest-as-root.mjs tests/concurrent-subagents.test.ts tests/secret-provider-revision.test.ts",
|
|
48
|
+
"test:root": "node ../../scripts/ci/vitest-as-root.mjs tests/concurrent-subagents.test.ts tests/secret-provider-revision.test.ts tests/secret-reference-resources.test.ts",
|
|
45
49
|
"test:watch": "vitest"
|
|
46
50
|
}
|
|
47
51
|
}
|