@tangle-network/agent-profile-materialize 0.18.0 → 0.19.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/candidate-types.d.ts +4 -0
- package/dist/candidate.d.ts +2 -2
- package/dist/candidate.js +5 -2
- package/dist/harness.d.ts +20 -3
- package/dist/harness.js +62 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/pi-models-config.d.ts +6 -6
- package/dist/pi-models-config.js +16 -0
- package/dist/plan-digest.js +22 -6
- package/dist/plan-normalize.d.ts +7 -2
- package/dist/plan-normalize.js +20 -8
- package/dist/plan-types.d.ts +18 -0
- package/dist/profile-materializer.d.ts +2 -1
- package/dist/profile-materializer.js +10 -2
- package/dist/profile-plan.d.ts +4 -2
- package/dist/profile-plan.js +9 -5
- package/dist/profile-prompt.d.ts +2 -2
- package/dist/profile-prompt.js +5 -5
- package/dist/subagents.js +9 -5
- package/dist/workspace-apply-files.js +2 -1
- package/dist/workspace-apply.d.ts +2 -2
- package/dist/workspace-apply.js +35 -19
- package/package.json +11 -3
|
@@ -17,6 +17,10 @@ export interface AgentCandidateWorkspacePlanReceipt {
|
|
|
17
17
|
appendSystemPrompt?: AgentCandidateConfigValue;
|
|
18
18
|
unsupported: Unsupported[];
|
|
19
19
|
}
|
|
20
|
+
export interface ApplyAgentCandidateWorkspacePlanOptions {
|
|
21
|
+
/** Absolute private agent directory required by agent-root plan files. */
|
|
22
|
+
agentDir?: string;
|
|
23
|
+
}
|
|
20
24
|
export interface MaterializeCandidateProfileOptions {
|
|
21
25
|
/** Exact text fetched from pinned resource refs, keyed by declared SHA-256. */
|
|
22
26
|
resolvedResources?: ReadonlyMap<Sha256Digest, string>;
|
package/dist/candidate.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { AgentCandidateProfile, AgentCandidateProfileApplication } from "@tangle-network/agent-interface";
|
|
2
|
-
import type { AgentCandidateWorkspacePlan, AgentCandidateWorkspacePlanReceipt, MaterializeCandidateProfileOptions } from "./candidate-types.js";
|
|
2
|
+
import type { AgentCandidateWorkspacePlan, AgentCandidateWorkspacePlanReceipt, ApplyAgentCandidateWorkspacePlanOptions, MaterializeCandidateProfileOptions } from "./candidate-types.js";
|
|
3
3
|
import type { HarnessId } from "./harness.js";
|
|
4
4
|
export declare function materializeCandidateProfile(profile: AgentCandidateProfile, harness: HarnessId, opts?: MaterializeCandidateProfileOptions): AgentCandidateWorkspacePlan;
|
|
5
5
|
/**
|
|
6
6
|
* Apply a candidate plan and return its spawn values together with captured
|
|
7
7
|
* canonical evidence. No caller needs to re-serialize or reinterpret the plan.
|
|
8
8
|
*/
|
|
9
|
-
export declare function applyAgentCandidateWorkspacePlan(plan: AgentCandidateWorkspacePlan, workspaceDir: string, targetWorkspace: AgentCandidateProfileApplication["targetWorkspace"]): AgentCandidateWorkspacePlanReceipt;
|
|
9
|
+
export declare function applyAgentCandidateWorkspacePlan(plan: AgentCandidateWorkspacePlan, workspaceDir: string, targetWorkspace: AgentCandidateProfileApplication["targetWorkspace"], options?: ApplyAgentCandidateWorkspacePlanOptions): AgentCandidateWorkspacePlanReceipt;
|
package/dist/candidate.js
CHANGED
|
@@ -29,6 +29,7 @@ export function materializeCandidateProfile(profile, harness, opts = {}) {
|
|
|
29
29
|
};
|
|
30
30
|
const classicPlan = materializeProfile(classicProfile, harness, {
|
|
31
31
|
binds: opts.binds,
|
|
32
|
+
...(harness === "pi" ? { instructionsRoot: "agent" } : {}),
|
|
32
33
|
});
|
|
33
34
|
// Canonical candidate evidence has no field for a launcher-addressed binding.
|
|
34
35
|
// Refuse that shape instead of returning a candidate plan whose receipt would
|
|
@@ -58,7 +59,7 @@ export function materializeCandidateProfile(profile, harness, opts = {}) {
|
|
|
58
59
|
* Apply a candidate plan and return its spawn values together with captured
|
|
59
60
|
* canonical evidence. No caller needs to re-serialize or reinterpret the plan.
|
|
60
61
|
*/
|
|
61
|
-
export function applyAgentCandidateWorkspacePlan(plan, workspaceDir, targetWorkspace) {
|
|
62
|
+
export function applyAgentCandidateWorkspacePlan(plan, workspaceDir, targetWorkspace, options = {}) {
|
|
62
63
|
assertWorkspacePlanSupported(plan);
|
|
63
64
|
const profilePlan = captureAgentCandidateProfilePlan(plan);
|
|
64
65
|
const activationFiles = normalizePlanFiles(plan.files).map((file) => ({
|
|
@@ -85,12 +86,14 @@ export function applyAgentCandidateWorkspacePlan(plan, workspaceDir, targetWorks
|
|
|
85
86
|
flags: plan.flags.map(candidatePublicString),
|
|
86
87
|
...promptIntentFields(plan, candidatePublicString),
|
|
87
88
|
unsupported: plan.unsupported,
|
|
88
|
-
}, workspaceDir);
|
|
89
|
+
}, workspaceDir, { agentDir: options.agentDir });
|
|
89
90
|
return {
|
|
90
91
|
profileActivation,
|
|
91
92
|
application: {
|
|
92
93
|
planDigest: profilePlan.digest,
|
|
93
94
|
targetWorkspace,
|
|
95
|
+
// The candidate execution-plan contract lists workspace mounts here.
|
|
96
|
+
// Agent-root placement remains explicit in profile-plan material.files.
|
|
94
97
|
mountPaths: applied.written,
|
|
95
98
|
},
|
|
96
99
|
env: { ...plan.env },
|
package/dist/harness.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export declare const OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
|
|
|
9
9
|
export declare const OPENCODE_PRIMARY_AGENT_PREFIX = "tangle-profile-";
|
|
10
10
|
export declare const CLAUDE_SETTINGS_PATH = ".tangle/claude-settings.json";
|
|
11
11
|
export declare function harnessAutoLoadsContextFile(harness: HarnessId): boolean;
|
|
12
|
+
/** Whether the shared materializer may place its context file in the harness agent directory. */
|
|
13
|
+
export declare function harnessAutoLoadsAgentContextFile(harness: HarnessId): boolean;
|
|
12
14
|
/**
|
|
13
15
|
* The project context file this harness reads from cwd — its stable, documented
|
|
14
16
|
* ALWAYS-LOADED surface. Exported so a caller lowering additive context (e.g. a
|
|
@@ -72,12 +74,27 @@ export declare const MATERIALIZER_RESERVED_PATHS: ReadonlySet<string>;
|
|
|
72
74
|
* one is worse than refusing the turn.
|
|
73
75
|
*/
|
|
74
76
|
export declare const MATERIALIZER_OWNED_PATHS: ReadonlySet<string>;
|
|
77
|
+
/** Mark one generated native agent file without changing its harness meaning. */
|
|
78
|
+
export declare function markMaterializerSubagent(relPath: string, content: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* Whether a generated file occupies a path that the profile materializer owns.
|
|
81
|
+
*
|
|
82
|
+
* Generated native agent files come from the active profile.
|
|
83
|
+
* The active profile must replace the prior profile version at the same path.
|
|
84
|
+
* Otherwise, a profile update stops every resumed session before launch.
|
|
85
|
+
* Explicit resources at these paths stay shared.
|
|
86
|
+
* The plan builder applies ownership only when the source is `generated`.
|
|
87
|
+
* An output marker proves ownership before replacement.
|
|
88
|
+
*/
|
|
89
|
+
export declare function isMaterializerOwnedPath(relPath: string): boolean;
|
|
75
90
|
/** The top-level keys this package writes into `opencode.json`. */
|
|
76
91
|
export declare const OPENCODE_CONFIG_KEYS: ReadonlySet<string>;
|
|
77
92
|
/**
|
|
78
93
|
* Whether the file already at owned path `relPath` is this package's own
|
|
79
|
-
* earlier output. A reserved name proves that by itself
|
|
80
|
-
*
|
|
94
|
+
* earlier output. A reserved name proves that by itself. Native agent files
|
|
95
|
+
* require the generated marker. An exact unmarked copy of the planned file is
|
|
96
|
+
* adopted once for upgrades from materializer versions without that marker.
|
|
97
|
+
* `undefined` content means no regular file is there.
|
|
81
98
|
*
|
|
82
99
|
* An `opencode.json` this package wrote holds the opencode schema id, only the
|
|
83
100
|
* keys this package writes, and only instruction paths this package invented.
|
|
@@ -85,7 +102,7 @@ export declare const OPENCODE_CONFIG_KEYS: ReadonlySet<string>;
|
|
|
85
102
|
* and is replaced too; a config with any other key, or with an instruction path
|
|
86
103
|
* of the user's own, is refused.
|
|
87
104
|
*/
|
|
88
|
-
export declare function isMaterializerOutput(relPath: string, content: () => string | undefined): boolean;
|
|
105
|
+
export declare function isMaterializerOutput(relPath: string, content: () => string | undefined, plannedContent?: string): boolean;
|
|
89
106
|
/**
|
|
90
107
|
* cwd-relative native skill dir per harness, or null if the harness has no
|
|
91
108
|
* cwd skill primitive (skills live in a user/global dir the workspace plan
|
package/dist/harness.js
CHANGED
|
@@ -29,6 +29,13 @@ export function harnessAutoLoadsContextFile(harness) {
|
|
|
29
29
|
assertMaterializerHarness(harness);
|
|
30
30
|
return harness !== "opencode";
|
|
31
31
|
}
|
|
32
|
+
/** Whether the shared materializer may place its context file in the harness agent directory. */
|
|
33
|
+
export function harnessAutoLoadsAgentContextFile(harness) {
|
|
34
|
+
assertMaterializerHarness(harness);
|
|
35
|
+
// Prime uses the fork-specific materializePrimeProfileControls path. It
|
|
36
|
+
// does not consume the shared agent-root plan produced by this predicate.
|
|
37
|
+
return harness === "pi";
|
|
38
|
+
}
|
|
32
39
|
/**
|
|
33
40
|
* The project context file this harness reads from cwd — its stable, documented
|
|
34
41
|
* ALWAYS-LOADED surface. Exported so a caller lowering additive context (e.g. a
|
|
@@ -142,6 +149,40 @@ export const MATERIALIZER_OWNED_PATHS = new Set([
|
|
|
142
149
|
...MATERIALIZER_RESERVED_PATHS,
|
|
143
150
|
OPENCODE_CONFIG_FILE,
|
|
144
151
|
]);
|
|
152
|
+
const MATERIALIZER_SUBAGENT_PATH = /^(?:(?:\.claude|\.opencode|\.gemini)\/agents\/[a-z0-9][a-z0-9_-]*\.md|\.codex\/agents\/[a-z0-9][a-z0-9_-]*\.toml)$/u;
|
|
153
|
+
const MATERIALIZER_SUBAGENT_MARKER = "# tangle-agent-profile-materialize: generated";
|
|
154
|
+
function materializerSubagentMarkerPrefix(relPath) {
|
|
155
|
+
return relPath.endsWith(".toml")
|
|
156
|
+
? `${MATERIALIZER_SUBAGENT_MARKER}\n`
|
|
157
|
+
: `---\n${MATERIALIZER_SUBAGENT_MARKER}\n`;
|
|
158
|
+
}
|
|
159
|
+
/** Mark one generated native agent file without changing its harness meaning. */
|
|
160
|
+
export function markMaterializerSubagent(relPath, content) {
|
|
161
|
+
if (!MATERIALIZER_SUBAGENT_PATH.test(relPath)) {
|
|
162
|
+
throw new Error(`Not a native agent path: ${relPath}`);
|
|
163
|
+
}
|
|
164
|
+
if (relPath.endsWith(".toml")) {
|
|
165
|
+
return `${materializerSubagentMarkerPrefix(relPath)}${content}`;
|
|
166
|
+
}
|
|
167
|
+
if (!content.startsWith("---\n")) {
|
|
168
|
+
throw new Error(`Native Markdown agent has no frontmatter: ${relPath}`);
|
|
169
|
+
}
|
|
170
|
+
return `${materializerSubagentMarkerPrefix(relPath)}${content.slice(4)}`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Whether a generated file occupies a path that the profile materializer owns.
|
|
174
|
+
*
|
|
175
|
+
* Generated native agent files come from the active profile.
|
|
176
|
+
* The active profile must replace the prior profile version at the same path.
|
|
177
|
+
* Otherwise, a profile update stops every resumed session before launch.
|
|
178
|
+
* Explicit resources at these paths stay shared.
|
|
179
|
+
* The plan builder applies ownership only when the source is `generated`.
|
|
180
|
+
* An output marker proves ownership before replacement.
|
|
181
|
+
*/
|
|
182
|
+
export function isMaterializerOwnedPath(relPath) {
|
|
183
|
+
return (MATERIALIZER_OWNED_PATHS.has(relPath) ||
|
|
184
|
+
MATERIALIZER_SUBAGENT_PATH.test(relPath));
|
|
185
|
+
}
|
|
145
186
|
/** The top-level keys this package writes into `opencode.json`. */
|
|
146
187
|
export const OPENCODE_CONFIG_KEYS = new Set([
|
|
147
188
|
"$schema",
|
|
@@ -152,8 +193,10 @@ export const OPENCODE_CONFIG_KEYS = new Set([
|
|
|
152
193
|
]);
|
|
153
194
|
/**
|
|
154
195
|
* Whether the file already at owned path `relPath` is this package's own
|
|
155
|
-
* earlier output. A reserved name proves that by itself
|
|
156
|
-
*
|
|
196
|
+
* earlier output. A reserved name proves that by itself. Native agent files
|
|
197
|
+
* require the generated marker. An exact unmarked copy of the planned file is
|
|
198
|
+
* adopted once for upgrades from materializer versions without that marker.
|
|
199
|
+
* `undefined` content means no regular file is there.
|
|
157
200
|
*
|
|
158
201
|
* An `opencode.json` this package wrote holds the opencode schema id, only the
|
|
159
202
|
* keys this package writes, and only instruction paths this package invented.
|
|
@@ -161,9 +204,25 @@ export const OPENCODE_CONFIG_KEYS = new Set([
|
|
|
161
204
|
* and is replaced too; a config with any other key, or with an instruction path
|
|
162
205
|
* of the user's own, is refused.
|
|
163
206
|
*/
|
|
164
|
-
export function isMaterializerOutput(relPath, content) {
|
|
207
|
+
export function isMaterializerOutput(relPath, content, plannedContent) {
|
|
165
208
|
if (MATERIALIZER_RESERVED_PATHS.has(relPath))
|
|
166
209
|
return true;
|
|
210
|
+
if (MATERIALIZER_SUBAGENT_PATH.test(relPath)) {
|
|
211
|
+
const existing = content();
|
|
212
|
+
if (existing === undefined)
|
|
213
|
+
return false;
|
|
214
|
+
const markerPrefix = materializerSubagentMarkerPrefix(relPath);
|
|
215
|
+
if (existing.startsWith(markerPrefix))
|
|
216
|
+
return true;
|
|
217
|
+
if (plannedContent === undefined)
|
|
218
|
+
return false;
|
|
219
|
+
if (!plannedContent.startsWith(markerPrefix))
|
|
220
|
+
return false;
|
|
221
|
+
const legacyContent = relPath.endsWith(".toml")
|
|
222
|
+
? plannedContent.slice(markerPrefix.length)
|
|
223
|
+
: `---\n${plannedContent.slice(markerPrefix.length)}`;
|
|
224
|
+
return existing === legacyContent;
|
|
225
|
+
}
|
|
167
226
|
if (relPath !== OPENCODE_CONFIG_FILE)
|
|
168
227
|
return false;
|
|
169
228
|
const bytes = content();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/** Stable public facade for profile materialization and workspace application. */
|
|
2
2
|
export { applyAgentCandidateWorkspacePlan, materializeCandidateProfile, } from "./candidate.js";
|
|
3
|
-
export type { AgentCandidateWorkspacePlan, AgentCandidateWorkspacePlanReceipt, MaterializeCandidateProfileOptions, } from "./candidate-types.js";
|
|
3
|
+
export type { AgentCandidateWorkspacePlan, AgentCandidateWorkspacePlanReceipt, ApplyAgentCandidateWorkspacePlanOptions, MaterializeCandidateProfileOptions, } from "./candidate-types.js";
|
|
4
4
|
export { OPENCODE_NATIVE_TOOL_IDS, openCodeNativeToolId, } from "./control-maps.js";
|
|
5
5
|
export type { HarnessId } from "./harness.js";
|
|
6
|
-
export { HARNESS_SKILL_DIRS, harnessAutoLoadsContextFile, harnessContextFile, isMaterializerHarness, MATERIALIZER_HARNESSES, skillDirForHarness, } from "./harness.js";
|
|
6
|
+
export { HARNESS_SKILL_DIRS, harnessAutoLoadsAgentContextFile, harnessAutoLoadsContextFile, harnessContextFile, isMaterializerHarness, MATERIALIZER_HARNESSES, skillDirForHarness, } from "./harness.js";
|
|
7
7
|
export type { OpenCodeMcpConfig } from "./mcp.js";
|
|
8
8
|
export { openCodeMcpConfig } from "./mcp.js";
|
|
9
9
|
export { modelIdsMatch, qualifyModelId, unqualifyModelId, } from "./model-id.js";
|
|
10
|
-
export { buildPiModelsConfig, buildPiModelsConfigFile, PI_MODELS_CONFIG_PATH, type PiModelsConfigFile, type PiModelsConfigInput, } from "./pi-models-config.js";
|
|
10
|
+
export { buildPiModelsConfig, buildPiModelsConfigFile, PI_MODELS_CONFIG_PATH, type PiModelsConfigFile, type PiModelsConfigInput, type PiRuntimePaths, resolvePiRuntimePaths, } from "./pi-models-config.js";
|
|
11
11
|
export { applyWorkspacePlan, applyWorkspacePlanWithSecretProvider, } from "./plan-application.js";
|
|
12
12
|
export { agentCandidateProfilePlanMaterial, canonicalizeAgentCandidateProfilePlan, captureAgentCandidateProfilePlan, hashWorkspacePlan, } from "./plan-digest.js";
|
|
13
13
|
export { takeSystemPromptBinding } from "./plan-prompt.js";
|
|
14
14
|
export { assertWorkspacePlanSupported, enforceWorkspacePlanSupport, } from "./plan-support.js";
|
|
15
|
-
export type { ApplyWorkspacePlanOptions, BindableLaunchControl, ExecutorSystemPromptBinding, PlanFile, PlanFileSecretSlot, PlanRequirement, SkippableDimension, Unsupported, WorkspacePlan, WorkspacePlanApplication, WorkspacePlanArgument, WorkspacePlanArgumentTemplate, WorkspacePlanConfigValue, WorkspacePlanReceipt, WorkspacePlanSecretSegment, WorkspacePlanSpawnInputs, WorkspacePlanSystemPromptBinding, } from "./plan-types.js";
|
|
15
|
+
export type { ApplyWorkspacePlanOptions, BindableLaunchControl, ExecutorSystemPromptBinding, PlanFile, PlanFileRoot, PlanFileSecretSlot, PlanRequirement, SkippableDimension, Unsupported, WorkspacePlan, WorkspacePlanApplication, WorkspacePlanArgument, WorkspacePlanArgumentTemplate, WorkspacePlanConfigValue, WorkspacePlanReceipt, WorkspacePlanSecretSegment, WorkspacePlanSpawnInputs, WorkspacePlanSystemPromptBinding, WorkspacePlanWrittenFile, } from "./plan-types.js";
|
|
16
16
|
export { BINDABLE_LAUNCH_CONTROLS, EXECUTOR_SYSTEM_PROMPT_BINDINGS, isExecutorSystemPromptBinding, isSkippableDimension, SKIPPABLE_DIMENSIONS, WorkspacePlanCleanupError, } from "./plan-types.js";
|
|
17
17
|
export type { PrimeMaterializeOptions } from "./prime-agent-dir.js";
|
|
18
18
|
export { materializePrimeProfileControls, prunePrimeProfileMaterial, seedPrimeProfileSubagents, writePrimeAgentFile, } from "./prime-agent-dir.js";
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** Stable public facade for profile materialization and workspace application. */
|
|
2
2
|
export { applyAgentCandidateWorkspacePlan, materializeCandidateProfile, } from "./candidate.js";
|
|
3
3
|
export { OPENCODE_NATIVE_TOOL_IDS, openCodeNativeToolId, } from "./control-maps.js";
|
|
4
|
-
export { HARNESS_SKILL_DIRS, harnessAutoLoadsContextFile, harnessContextFile, isMaterializerHarness, MATERIALIZER_HARNESSES, skillDirForHarness, } from "./harness.js";
|
|
4
|
+
export { HARNESS_SKILL_DIRS, harnessAutoLoadsAgentContextFile, harnessAutoLoadsContextFile, harnessContextFile, isMaterializerHarness, MATERIALIZER_HARNESSES, skillDirForHarness, } from "./harness.js";
|
|
5
5
|
export { openCodeMcpConfig } from "./mcp.js";
|
|
6
6
|
export { modelIdsMatch, qualifyModelId, unqualifyModelId, } from "./model-id.js";
|
|
7
|
-
export { buildPiModelsConfig, buildPiModelsConfigFile, PI_MODELS_CONFIG_PATH, } from "./pi-models-config.js";
|
|
7
|
+
export { buildPiModelsConfig, buildPiModelsConfigFile, PI_MODELS_CONFIG_PATH, resolvePiRuntimePaths, } from "./pi-models-config.js";
|
|
8
8
|
export { applyWorkspacePlan, applyWorkspacePlanWithSecretProvider, } from "./plan-application.js";
|
|
9
9
|
export { agentCandidateProfilePlanMaterial, canonicalizeAgentCandidateProfilePlan, captureAgentCandidateProfilePlan, hashWorkspacePlan, } from "./plan-digest.js";
|
|
10
10
|
export { takeSystemPromptBinding } from "./plan-prompt.js";
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One custom-model shape for upstream Pi and the Prime fork.
|
|
3
|
-
*
|
|
4
|
-
* Both agents read the same `models.json` provider schema.
|
|
5
|
-
* Callers own the file lifecycle, but they must not build different payloads.
|
|
6
|
-
*/
|
|
7
1
|
export interface PiModelsConfigInput {
|
|
8
2
|
/** Provider key in models.json and the matching `--provider` value. */
|
|
9
3
|
providerName: string;
|
|
@@ -25,6 +19,12 @@ export interface PiModelsConfigFile {
|
|
|
25
19
|
mode: number;
|
|
26
20
|
}
|
|
27
21
|
export declare const PI_MODELS_CONFIG_PATH = ".pi/agent/models.json";
|
|
22
|
+
export interface PiRuntimePaths {
|
|
23
|
+
agentDir: string;
|
|
24
|
+
sessionDir: string;
|
|
25
|
+
}
|
|
26
|
+
/** Resolve Pi's private roots from the one runtime HOME supplied to Pi. */
|
|
27
|
+
export declare function resolvePiRuntimePaths(runtimeHome: string): PiRuntimePaths;
|
|
28
28
|
export declare function buildPiModelsConfig(input: PiModelsConfigInput): Record<string, unknown>;
|
|
29
29
|
/** Build the owner-only Pi file used by headless and interactive processes. */
|
|
30
30
|
export declare function buildPiModelsConfigFile(input: PiModelsConfigInput): PiModelsConfigFile;
|
package/dist/pi-models-config.js
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One custom-model shape for upstream Pi and the Prime fork.
|
|
3
|
+
*
|
|
4
|
+
* Both agents read the same `models.json` provider schema.
|
|
5
|
+
* Callers own the file lifecycle, but they must not build different payloads.
|
|
6
|
+
*/
|
|
7
|
+
import path from "node:path";
|
|
1
8
|
export const PI_MODELS_CONFIG_PATH = ".pi/agent/models.json";
|
|
9
|
+
/** Resolve Pi's private roots from the one runtime HOME supplied to Pi. */
|
|
10
|
+
export function resolvePiRuntimePaths(runtimeHome) {
|
|
11
|
+
const home = path.resolve(runtimeHome);
|
|
12
|
+
const agentDir = path.dirname(path.join(home, PI_MODELS_CONFIG_PATH));
|
|
13
|
+
return {
|
|
14
|
+
agentDir,
|
|
15
|
+
sessionDir: path.join(agentDir, "sessions"),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
2
18
|
const DEFAULT_CONTEXT_WINDOW = 128_000;
|
|
3
19
|
export function buildPiModelsConfig(input) {
|
|
4
20
|
for (const [field, value] of [
|
package/dist/plan-digest.js
CHANGED
|
@@ -2,6 +2,7 @@ export const sha256 = (value) => createHash("sha256").update(value, "utf8").dige
|
|
|
2
2
|
export function hashWorkspacePlan(plan) {
|
|
3
3
|
const files = normalizePlanFiles(plan.files).map((file) => ({
|
|
4
4
|
relPath: file.relPath,
|
|
5
|
+
...(file.root === "workspace" ? {} : { root: file.root }),
|
|
5
6
|
mode: file.mode,
|
|
6
7
|
contentSha256: sha256(file.content),
|
|
7
8
|
// Ownership decides whether re-application may replace the file, so two
|
|
@@ -42,20 +43,35 @@ export function hashWorkspacePlan(plan) {
|
|
|
42
43
|
}
|
|
43
44
|
/** Build the exact digest-free identity document required by agent-interface. */
|
|
44
45
|
export function agentCandidateProfilePlanMaterial(plan) {
|
|
46
|
+
const files = normalizePlanFiles(plan.files);
|
|
47
|
+
const materialFiles = files.map((file) => ({
|
|
48
|
+
relPath: file.relPath,
|
|
49
|
+
mode: file.mode,
|
|
50
|
+
contentSha256: `sha256:${sha256(file.content)}`,
|
|
51
|
+
...(file.root === "agent" ? { root: "agent" } : {}),
|
|
52
|
+
}));
|
|
45
53
|
const material = {
|
|
46
54
|
sourceProfileDigest: plan.sourceProfileDigest,
|
|
47
55
|
harness: plan.harness,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
})),
|
|
56
|
+
// Interface 1 has the same candidate material shape without the optional
|
|
57
|
+
// root field. Keep the local file model richer, then validate against the
|
|
58
|
+
// installed peer schema at the compatibility boundary.
|
|
59
|
+
files: materialFiles,
|
|
53
60
|
env: Object.fromEntries(Object.entries(plan.env).sort(([left], [right]) => compareStrings(left, right))),
|
|
54
61
|
flags: [...plan.flags],
|
|
55
62
|
...promptIntentFields(plan, samePromptIntentValue),
|
|
56
63
|
unsupported: [...plan.unsupported].sort((left, right) => compareStrings(`${left.dimension}\0${left.reason}`, `${right.dimension}\0${right.reason}`)),
|
|
57
64
|
};
|
|
58
|
-
|
|
65
|
+
const hasAgentRoot = materialFiles.some((file) => file.root === "agent");
|
|
66
|
+
if (!hasAgentRoot) {
|
|
67
|
+
return agentCandidateProfilePlanMaterialSchema.parse(material);
|
|
68
|
+
}
|
|
69
|
+
const parsed = agentCandidateProfilePlanMaterialSchema.safeParse(material);
|
|
70
|
+
if (!parsed.success) {
|
|
71
|
+
throw new Error("Agent-root candidate evidence requires @tangle-network/agent-interface >=2.1.1; " +
|
|
72
|
+
'the installed peer schema cannot preserve root:"agent". Upgrade the peer before applying this candidate plan.');
|
|
73
|
+
}
|
|
74
|
+
return parsed.data;
|
|
59
75
|
}
|
|
60
76
|
/**
|
|
61
77
|
* Return the RFC 8785 UTF-8 bytes and SHA-256 identity of a candidate profile
|
package/dist/plan-normalize.d.ts
CHANGED
|
@@ -2,12 +2,17 @@ export declare function mergeJsonFiles(files: PlanFile[]): PlanFile[];
|
|
|
2
2
|
export interface NormalizedPlanFile {
|
|
3
3
|
relPath: string;
|
|
4
4
|
content: string;
|
|
5
|
+
root: PlanFileRoot;
|
|
5
6
|
mode: number;
|
|
6
7
|
ownership: NonNullable<PlanFile["ownership"]>;
|
|
7
8
|
secretSlots: PlanFileSecretSlot[];
|
|
8
9
|
}
|
|
9
|
-
export
|
|
10
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
11
|
+
[key: string]: JsonValue;
|
|
12
|
+
};
|
|
13
|
+
export declare function normalizePlanFileRoot(root: PlanFileRoot | undefined): PlanFileRoot;
|
|
14
|
+
export declare function valueAtJsonPath(root: JsonValue, path: readonly (string | number)[]): JsonValue | undefined;
|
|
10
15
|
export declare function normalizePlanFileSecretSlots(file: PlanFile): PlanFileSecretSlot[];
|
|
11
16
|
export declare function normalizePlanFiles(files: PlanFile[]): NormalizedPlanFile[];
|
|
12
17
|
export declare function normalizeWorkspacePlanArgument(value: WorkspacePlanArgument): WorkspacePlanArgument;
|
|
13
|
-
import type { PlanFile, PlanFileSecretSlot, WorkspacePlanArgument } from "./plan-types.js";
|
|
18
|
+
import type { PlanFile, PlanFileRoot, PlanFileSecretSlot, WorkspacePlanArgument } from "./plan-types.js";
|
package/dist/plan-normalize.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export function mergeJsonFiles(files) {
|
|
2
2
|
const byPath = new Map();
|
|
3
3
|
for (const f of files) {
|
|
4
|
-
const
|
|
4
|
+
const root = normalizePlanFileRoot(f.root);
|
|
5
|
+
const identity = `${root}\0${f.relPath}`;
|
|
6
|
+
const prev = byPath.get(identity);
|
|
5
7
|
if (!prev) {
|
|
6
|
-
byPath.set(
|
|
8
|
+
byPath.set(identity, f);
|
|
7
9
|
continue;
|
|
8
10
|
}
|
|
9
11
|
if (f.relPath.endsWith(".json")) {
|
|
@@ -20,7 +22,7 @@ export function mergeJsonFiles(files) {
|
|
|
20
22
|
...(f.secretSlots ?? []),
|
|
21
23
|
];
|
|
22
24
|
const { secretSlots: _previousSecretSlots, ...previousFile } = prev;
|
|
23
|
-
byPath.set(
|
|
25
|
+
byPath.set(identity, {
|
|
24
26
|
...previousFile,
|
|
25
27
|
content: JSON.stringify(merged, null, 2),
|
|
26
28
|
...(secretSlots.length > 0 ? { mode: PRIVATE_FILE_MODE } : {}),
|
|
@@ -32,10 +34,17 @@ export function mergeJsonFiles(files) {
|
|
|
32
34
|
/* fall through to last-wins */
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
|
-
byPath.set(
|
|
37
|
+
byPath.set(identity, f); // non-JSON or parse-fail: last wins
|
|
36
38
|
}
|
|
37
39
|
return [...byPath.values()];
|
|
38
40
|
}
|
|
41
|
+
export function normalizePlanFileRoot(root) {
|
|
42
|
+
if (root === undefined || root === "workspace")
|
|
43
|
+
return "workspace";
|
|
44
|
+
if (root === "agent")
|
|
45
|
+
return root;
|
|
46
|
+
throw new Error(`Unsupported workspace plan file root: ${String(root)}`);
|
|
47
|
+
}
|
|
39
48
|
export function valueAtJsonPath(root, path) {
|
|
40
49
|
let current = root;
|
|
41
50
|
for (const segment of path) {
|
|
@@ -94,11 +103,13 @@ export function normalizePlanFiles(files) {
|
|
|
94
103
|
const seen = new Set();
|
|
95
104
|
return files
|
|
96
105
|
.map((file) => {
|
|
106
|
+
const root = normalizePlanFileRoot(file.root);
|
|
97
107
|
const relPath = normalizeWorkspaceRelativePath(file.relPath);
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
const identity = `${root}\0${relPath}`;
|
|
109
|
+
if (seen.has(identity)) {
|
|
110
|
+
throw new Error(`Duplicate ${root} plan file: ${relPath}`);
|
|
100
111
|
}
|
|
101
|
-
seen.add(
|
|
112
|
+
seen.add(identity);
|
|
102
113
|
if (typeof file.content !== "string") {
|
|
103
114
|
throw new Error(`Workspace file content must be a string: ${relPath}`);
|
|
104
115
|
}
|
|
@@ -110,12 +121,13 @@ export function normalizePlanFiles(files) {
|
|
|
110
121
|
return {
|
|
111
122
|
relPath,
|
|
112
123
|
content: file.content,
|
|
124
|
+
root,
|
|
113
125
|
mode,
|
|
114
126
|
ownership: file.ownership ?? "shared",
|
|
115
127
|
secretSlots,
|
|
116
128
|
};
|
|
117
129
|
})
|
|
118
|
-
.sort((a, b) => compareStrings(a.relPath
|
|
130
|
+
.sort((a, b) => compareStrings(`${a.root}\0${a.relPath}`, `${b.root}\0${b.relPath}`));
|
|
119
131
|
}
|
|
120
132
|
export function normalizeWorkspacePlanArgument(value) {
|
|
121
133
|
if (typeof value === "string")
|
package/dist/plan-types.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export type { HarnessId } from "./harness.js";
|
|
2
|
+
/** The executor-owned root that receives a materialized file. */
|
|
3
|
+
export type PlanFileRoot = "workspace" | "agent";
|
|
2
4
|
export interface PlanFile {
|
|
3
5
|
relPath: string;
|
|
4
6
|
content: string;
|
|
7
|
+
/** Defaults to the run workspace. `agent` is the harness's private agent directory. */
|
|
8
|
+
root?: PlanFileRoot;
|
|
5
9
|
/** chmod mode (e.g. 0o755 for hook scripts); default 0o644. */
|
|
6
10
|
mode?: number;
|
|
7
11
|
/**
|
|
@@ -114,6 +118,11 @@ export interface WorkspacePlanSystemPromptBinding {
|
|
|
114
118
|
/** Exact text the control receives. */
|
|
115
119
|
systemPrompt: string;
|
|
116
120
|
}
|
|
121
|
+
/** One file applied by a plan, with the executor root kept explicit. */
|
|
122
|
+
export interface WorkspacePlanWrittenFile {
|
|
123
|
+
root: PlanFileRoot;
|
|
124
|
+
relPath: string;
|
|
125
|
+
}
|
|
117
126
|
/**
|
|
118
127
|
* The dimensions `materializeProfile`'s `skip` option honors.
|
|
119
128
|
*
|
|
@@ -254,7 +263,14 @@ export interface WorkspacePlanReceipt {
|
|
|
254
263
|
/** The launcher-bound intent, so a receipt says which control owed the work. */
|
|
255
264
|
systemPromptBinding?: WorkspacePlanSystemPromptBinding;
|
|
256
265
|
unsupported: Unsupported[];
|
|
266
|
+
/**
|
|
267
|
+
* Legacy workspace-root paths for existing workspace-only consumers.
|
|
268
|
+
* Agent-root files are available in `writtenFiles` and are never encoded as
|
|
269
|
+
* an `agent:` path because that spelling is legal in the workspace root.
|
|
270
|
+
*/
|
|
257
271
|
written: string[];
|
|
272
|
+
/** Every applied file, including its executor-owned root. */
|
|
273
|
+
writtenFiles: WorkspacePlanWrittenFile[];
|
|
258
274
|
}
|
|
259
275
|
/** Private values handed directly to the provider spawn, never to receipts. */
|
|
260
276
|
export interface WorkspacePlanSpawnInputs {
|
|
@@ -281,6 +297,8 @@ export declare class WorkspacePlanCleanupError extends AggregateError {
|
|
|
281
297
|
export interface ApplyWorkspacePlanOptions {
|
|
282
298
|
/** Plan files consumed in memory by the runtime and never persisted to cwd. */
|
|
283
299
|
runtimeOnlyFiles?: readonly string[];
|
|
300
|
+
/** Absolute private agent directory required by files whose root is `agent`. */
|
|
301
|
+
agentDir?: string;
|
|
284
302
|
/** Own newly written workspace files as the process that will read them. */
|
|
285
303
|
ownership?: {
|
|
286
304
|
uid: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentProfile, AgentProfileSecretProvider } from "@tangle-network/agent-interface";
|
|
2
2
|
import { type HarnessId } from "./harness.js";
|
|
3
|
-
import { type BindableLaunchControl, type SkippableDimension, type WorkspacePlan } from "./plan-types.js";
|
|
3
|
+
import { type BindableLaunchControl, type PlanFileRoot, type SkippableDimension, type WorkspacePlan } from "./plan-types.js";
|
|
4
4
|
export declare const UNAVAILABLE_AGENT_PROFILE_SECRET_PROVIDER: AgentProfileSecretProvider;
|
|
5
5
|
/**
|
|
6
6
|
* Build a pure workspace plan for one canonical harness.
|
|
@@ -9,4 +9,5 @@ export declare const UNAVAILABLE_AGENT_PROFILE_SECRET_PROVIDER: AgentProfileSecr
|
|
|
9
9
|
export declare function materializeProfile(inputProfile: AgentProfile, harness: HarnessId, opts?: {
|
|
10
10
|
skip?: SkippableDimension[];
|
|
11
11
|
binds?: readonly BindableLaunchControl[];
|
|
12
|
+
instructionsRoot?: PlanFileRoot;
|
|
12
13
|
}): WorkspacePlan;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agentProfileSchema } from "@tangle-network/agent-interface";
|
|
2
2
|
import { materializeExecutionControls } from "./execution-controls.js";
|
|
3
|
-
import { assertMaterializerHarness } from "./harness.js";
|
|
3
|
+
import { assertMaterializerHarness, harnessAutoLoadsAgentContextFile, } from "./harness.js";
|
|
4
4
|
import { materializeHooks } from "./hooks.js";
|
|
5
5
|
import { materializeMcp } from "./mcp.js";
|
|
6
6
|
import { assertBindableLaunchControls, assertSkippableDimensions, } from "./plan-types.js";
|
|
@@ -23,9 +23,17 @@ export function materializeProfile(inputProfile, harness, opts = {}) {
|
|
|
23
23
|
const profile = agentProfileSchema.parse(inputProfile);
|
|
24
24
|
const skip = new Set(assertSkippableDimensions(opts.skip));
|
|
25
25
|
const binds = assertBindableLaunchControls(opts.binds);
|
|
26
|
+
const instructionsRoot = opts.instructionsRoot ?? "workspace";
|
|
27
|
+
if (instructionsRoot !== "workspace" && instructionsRoot !== "agent") {
|
|
28
|
+
throw new Error(`materializeProfile: unsupported instructions root ${String(instructionsRoot)}`);
|
|
29
|
+
}
|
|
30
|
+
if (instructionsRoot === "agent" &&
|
|
31
|
+
!harnessAutoLoadsAgentContextFile(harness)) {
|
|
32
|
+
throw new Error(`materializeProfile: harness "${harness}" does not load instructions from its agent directory`);
|
|
33
|
+
}
|
|
26
34
|
const builder = createProfilePlanBuilder(harness);
|
|
27
35
|
lowerProfileResources(profile, harness, builder);
|
|
28
|
-
lowerProfilePrompt(profile, harness, binds, builder);
|
|
36
|
+
lowerProfilePrompt(profile, harness, binds, instructionsRoot, builder);
|
|
29
37
|
materializeExecutionControls(harness, profile.tools, profile.permissions, profile.modes, builder.plan, builder.add, builder.unsupported);
|
|
30
38
|
if ((profile.connections?.length ?? 0) > 0) {
|
|
31
39
|
builder.unsupported("connections", `${harness}: Hub connections must be resolved to authorized MCP servers by the execution runtime`);
|
package/dist/profile-plan.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import type { AgentProfileConfigValue } from "@tangle-network/agent-interface";
|
|
2
2
|
import type { HarnessId } from "./harness.js";
|
|
3
|
-
import type { AddPlanFile } from "./plan-json.js";
|
|
4
|
-
import type { Unsupported, WorkspacePlan } from "./plan-types.js";
|
|
3
|
+
import type { AddPlanFile, SerializedPlanFileContent } from "./plan-json.js";
|
|
4
|
+
import type { PlanFileRoot, Unsupported, WorkspacePlan } from "./plan-types.js";
|
|
5
5
|
export interface ProfilePlanBuilder {
|
|
6
6
|
plan: WorkspacePlan;
|
|
7
7
|
add: AddPlanFile;
|
|
8
|
+
addAtRoot: AddRootedPlanFile;
|
|
8
9
|
addExplicitResource: AddExplicitResource;
|
|
9
10
|
unsupported: (dimension: Unsupported["dimension"], reason: string) => void;
|
|
10
11
|
finalize: (failOnError?: boolean) => WorkspacePlan;
|
|
11
12
|
}
|
|
13
|
+
export type AddRootedPlanFile = (root: PlanFileRoot, path: string, content: string | SerializedPlanFileContent, mode?: number) => void;
|
|
12
14
|
export type AddExplicitResource = (dimension: Unsupported["dimension"], path: string, content: string, mode?: number) => void;
|
|
13
15
|
export declare function createProfilePlanBuilder(harness: HarnessId): ProfilePlanBuilder;
|
|
14
16
|
export declare function setPlanEnvironment(plan: WorkspacePlan, name: string, value: string | AgentProfileConfigValue): void;
|
package/dist/profile-plan.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isMaterializerOwnedPath, MATERIALIZER_RESERVED_PATHS, } from "./harness.js";
|
|
2
2
|
import { workspacePlanConfigValue } from "./plan-json.js";
|
|
3
3
|
import { mergeJsonFiles } from "./plan-normalize.js";
|
|
4
4
|
import { formatUnsupported } from "./plan-support.js";
|
|
@@ -12,7 +12,7 @@ export function createProfilePlanBuilder(harness) {
|
|
|
12
12
|
unsupported: [],
|
|
13
13
|
requires: [],
|
|
14
14
|
};
|
|
15
|
-
const pushPlanFile = (relPath, serialized, source, mode) => {
|
|
15
|
+
const pushPlanFile = (relPath, serialized, source, mode, root = "workspace") => {
|
|
16
16
|
const normalizedPath = normalizeWorkspaceRelativePath(relPath);
|
|
17
17
|
const content = typeof serialized === "string" ? serialized : serialized.content;
|
|
18
18
|
const secretSlots = typeof serialized === "string" ? undefined : serialized.secretSlots;
|
|
@@ -24,7 +24,8 @@ export function createProfilePlanBuilder(harness) {
|
|
|
24
24
|
relPath: normalizedPath,
|
|
25
25
|
content,
|
|
26
26
|
source,
|
|
27
|
-
...(
|
|
27
|
+
...(root === "workspace" ? {} : { root }),
|
|
28
|
+
...(source === "generated" && isMaterializerOwnedPath(normalizedPath)
|
|
28
29
|
? { ownership: "materializer" }
|
|
29
30
|
: {}),
|
|
30
31
|
...(fileMode === undefined ? {} : { mode: validateFileMode(fileMode) }),
|
|
@@ -39,6 +40,7 @@ export function createProfilePlanBuilder(harness) {
|
|
|
39
40
|
});
|
|
40
41
|
};
|
|
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);
|
|
42
44
|
const unsupported = (dimension, reason) => plan.unsupported.push({ dimension, reason });
|
|
43
45
|
const explicitResourcePaths = new Map();
|
|
44
46
|
const addExplicitResource = (dimension, relPath, content, mode) => {
|
|
@@ -58,16 +60,18 @@ export function createProfilePlanBuilder(harness) {
|
|
|
58
60
|
return {
|
|
59
61
|
plan,
|
|
60
62
|
add,
|
|
63
|
+
addAtRoot,
|
|
61
64
|
addExplicitResource,
|
|
62
65
|
unsupported,
|
|
63
66
|
finalize(failOnError) {
|
|
64
67
|
for (const [relPath, dimension] of explicitResourcePaths) {
|
|
65
|
-
const writers = plan.files.filter((file) => file.
|
|
68
|
+
const writers = plan.files.filter((file) => (file.root ?? "workspace") === "workspace" &&
|
|
69
|
+
file.relPath === relPath).length;
|
|
66
70
|
if (writers > 1) {
|
|
67
71
|
throw new Error(`Profile ${dimension} resource conflicts with generated harness file: ${relPath}`);
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
|
-
plan.files = mergeJsonFiles(plan.files).sort((a, b) => compareStrings(a.relPath
|
|
74
|
+
plan.files = mergeJsonFiles(plan.files).sort((a, b) => compareStrings(`${a.root ?? "workspace"}\0${a.relPath}`, `${b.root ?? "workspace"}\0${b.relPath}`));
|
|
71
75
|
if (failOnError && plan.unsupported.length) {
|
|
72
76
|
throw new Error(formatUnsupported(plan.unsupported));
|
|
73
77
|
}
|
package/dist/profile-prompt.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { AgentProfile } from "@tangle-network/agent-interface";
|
|
2
|
-
import type { BindableLaunchControl, HarnessId } from "./plan-types.js";
|
|
2
|
+
import type { BindableLaunchControl, HarnessId, PlanFileRoot } from "./plan-types.js";
|
|
3
3
|
import type { ProfilePlanBuilder } from "./profile-plan.js";
|
|
4
|
-
export declare function lowerProfilePrompt(profile: AgentProfile, harness: HarnessId, binds: ReadonlySet<BindableLaunchControl>, builder: ProfilePlanBuilder): void;
|
|
4
|
+
export declare function lowerProfilePrompt(profile: AgentProfile, harness: HarnessId, binds: ReadonlySet<BindableLaunchControl>, instructionsRoot: PlanFileRoot, builder: ProfilePlanBuilder): void;
|
package/dist/profile-prompt.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { appendSystemPromptRefusal, CODEX_SYSTEM_PROMPT_FILE, CONTEXT_FILE, OPENCODE_APPEND_SYSTEM_PROMPT_FILE, OPENCODE_CONFIG_FILE, OPENCODE_CONFIG_SCHEMA, OPENCODE_PRIMARY_AGENT_PREFIX, systemPromptRefusal, } from "./harness.js";
|
|
1
|
+
import { appendSystemPromptRefusal, CODEX_SYSTEM_PROMPT_FILE, CONTEXT_FILE, markMaterializerSubagent, OPENCODE_APPEND_SYSTEM_PROMPT_FILE, OPENCODE_CONFIG_FILE, OPENCODE_CONFIG_SCHEMA, OPENCODE_PRIMARY_AGENT_PREFIX, systemPromptRefusal, } from "./harness.js";
|
|
2
2
|
import { sha256 } from "./plan-digest.js";
|
|
3
3
|
import { serializeWorkspacePlanJson } from "./plan-json.js";
|
|
4
|
-
export function lowerProfilePrompt(profile, harness, binds, builder) {
|
|
5
|
-
const { plan, add, unsupported } = builder;
|
|
4
|
+
export function lowerProfilePrompt(profile, harness, binds, instructionsRoot, builder) {
|
|
5
|
+
const { plan, add, addAtRoot, unsupported } = builder;
|
|
6
6
|
if (profile.prompt?.systemPrompt !== undefined) {
|
|
7
7
|
if (harness === "claude-code" || harness === "pi" || harness === "prime") {
|
|
8
8
|
plan.systemPrompt = profile.prompt.systemPrompt;
|
|
@@ -34,7 +34,7 @@ export function lowerProfilePrompt(profile, harness, binds, builder) {
|
|
|
34
34
|
throw new Error(`opencode: subagent name "${primaryAgentName}" is reserved for the profile primary agent`);
|
|
35
35
|
}
|
|
36
36
|
plan.systemPrompt = systemPrompt;
|
|
37
|
-
add(primaryAgentPath, ["---", "mode: primary", "---", systemPrompt, ""].join("\n"));
|
|
37
|
+
add(primaryAgentPath, markMaterializerSubagent(primaryAgentPath, ["---", "mode: primary", "---", systemPrompt, ""].join("\n")));
|
|
38
38
|
plan.flags.push("--agent", primaryAgentName);
|
|
39
39
|
}
|
|
40
40
|
else {
|
|
@@ -90,7 +90,7 @@ export function lowerProfilePrompt(profile, harness, binds, builder) {
|
|
|
90
90
|
}
|
|
91
91
|
else {
|
|
92
92
|
const instructionPath = CONTEXT_FILE[harness];
|
|
93
|
-
|
|
93
|
+
addAtRoot(instructionsRoot, instructionPath, `${instructions.join("\n\n")}\n`);
|
|
94
94
|
if (harness === "opencode") {
|
|
95
95
|
openCodeInstructionFiles.push(instructionPath);
|
|
96
96
|
}
|
package/dist/subagents.js
CHANGED
|
@@ -27,7 +27,8 @@ export function materializeSubagent(h, name, sa, plan, add, unsupported) {
|
|
|
27
27
|
fm.push(`maxTurns: ${sa.maxSteps}`);
|
|
28
28
|
reportUnsupportedSubagentFields(h, name, sa, new Set(["description", "prompt", "model", "tools", "maxSteps"]), unsupported);
|
|
29
29
|
fm.push("---", "", sa.prompt ?? sa.description ?? name);
|
|
30
|
-
|
|
30
|
+
const relPath = `.claude/agents/${name}.md`;
|
|
31
|
+
add(relPath, markMaterializerSubagent(relPath, `${fm.join("\n")}\n`));
|
|
31
32
|
break;
|
|
32
33
|
}
|
|
33
34
|
case "opencode": {
|
|
@@ -53,7 +54,8 @@ export function materializeSubagent(h, name, sa, plan, add, unsupported) {
|
|
|
53
54
|
"maxSteps",
|
|
54
55
|
]), unsupported);
|
|
55
56
|
fm.push("---", "", sa.prompt ?? "");
|
|
56
|
-
|
|
57
|
+
const relPath = `.opencode/agents/${name}.md`;
|
|
58
|
+
add(relPath, markMaterializerSubagent(relPath, `${fm.join("\n")}\n`));
|
|
57
59
|
break;
|
|
58
60
|
}
|
|
59
61
|
case "gemini": {
|
|
@@ -63,7 +65,8 @@ export function materializeSubagent(h, name, sa, plan, add, unsupported) {
|
|
|
63
65
|
.map(([tool]) => tool)
|
|
64
66
|
: undefined;
|
|
65
67
|
reportUnsupportedSubagentFields(h, name, sa, new Set(["description", "prompt", "model", "tools", "maxSteps"]), unsupported);
|
|
66
|
-
|
|
68
|
+
const relPath = `.gemini/agents/${name}.md`;
|
|
69
|
+
add(relPath, markMaterializerSubagent(relPath, [
|
|
67
70
|
"---",
|
|
68
71
|
`name: ${name}`,
|
|
69
72
|
`description: ${JSON.stringify(sa.description ?? name)}`,
|
|
@@ -75,7 +78,7 @@ export function materializeSubagent(h, name, sa, plan, add, unsupported) {
|
|
|
75
78
|
"",
|
|
76
79
|
sa.prompt ?? "",
|
|
77
80
|
"",
|
|
78
|
-
].join("\n"));
|
|
81
|
+
].join("\n")));
|
|
79
82
|
break;
|
|
80
83
|
}
|
|
81
84
|
case "codex": {
|
|
@@ -88,7 +91,7 @@ export function materializeSubagent(h, name, sa, plan, add, unsupported) {
|
|
|
88
91
|
if (sa.model)
|
|
89
92
|
lines.push(`model = ${tomlStr(sa.model)}`);
|
|
90
93
|
reportUnsupportedSubagentFields(h, name, sa, new Set(["description", "prompt", "model"]), unsupported);
|
|
91
|
-
add(relPath, `${lines.join("\n")}\n`);
|
|
94
|
+
add(relPath, markMaterializerSubagent(relPath, `${lines.join("\n")}\n`));
|
|
92
95
|
registerCodexAgent(plan, name, relPath, sa.description ?? name);
|
|
93
96
|
break;
|
|
94
97
|
}
|
|
@@ -130,4 +133,5 @@ function reportUnsupportedSubagentFields(harness, name, subagent, supported, uns
|
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
135
|
}
|
|
136
|
+
import { markMaterializerSubagent } from "./harness.js";
|
|
133
137
|
import { tomlStr } from "./plan-json.js";
|
|
@@ -47,7 +47,8 @@ function sha256Hex(content) {
|
|
|
47
47
|
* over.
|
|
48
48
|
*/
|
|
49
49
|
function workspaceFileConflictMessage(file, existingFiles, planDigest) {
|
|
50
|
-
const
|
|
50
|
+
const rootLabel = file.root === "workspace" ? "workspace" : "agent-directory";
|
|
51
|
+
const prefix = `Refusing to replace existing ${rootLabel} file: ${file.relPath}`;
|
|
51
52
|
if (existingFiles === "reject") {
|
|
52
53
|
return `${prefix} — the reject policy forbids any pre-existing plan path (plan ${planDigest})`;
|
|
53
54
|
}
|
|
@@ -7,6 +7,6 @@ export interface ResolvedWorkspacePlan {
|
|
|
7
7
|
appendSystemPrompt?: string;
|
|
8
8
|
systemPromptBinding?: WorkspacePlanSystemPromptBinding;
|
|
9
9
|
}
|
|
10
|
-
export declare function safeWorkspacePlanReceipt(plan: WorkspacePlan,
|
|
10
|
+
export declare function safeWorkspacePlanReceipt(plan: WorkspacePlan, writtenFiles: WorkspacePlanWrittenFile[], workspacePlanDigest?: string): WorkspacePlanReceipt;
|
|
11
11
|
import { type NormalizedPlanFile } from "./plan-normalize.js";
|
|
12
|
-
import type { ApplyWorkspacePlanOptions, WorkspacePlan, WorkspacePlanApplication, WorkspacePlanReceipt, WorkspacePlanSystemPromptBinding } from "./plan-types.js";
|
|
12
|
+
import type { ApplyWorkspacePlanOptions, WorkspacePlan, WorkspacePlanApplication, WorkspacePlanReceipt, WorkspacePlanSystemPromptBinding, WorkspacePlanWrittenFile } from "./plan-types.js";
|
package/dist/workspace-apply.js
CHANGED
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDir, options, publicPlanDigest = hashWorkspacePlan(publicPlan)) {
|
|
2
2
|
const workspaceRoot = resolve(workspaceDir);
|
|
3
|
+
if (options.agentDir !== undefined && !isAbsolute(options.agentDir)) {
|
|
4
|
+
throw new Error("ApplyWorkspacePlanOptions.agentDir must be absolute");
|
|
5
|
+
}
|
|
6
|
+
const agentRoot = options.agentDir === undefined ? undefined : resolve(options.agentDir);
|
|
7
|
+
const publicFiles = normalizePlanFiles(publicPlan.files);
|
|
8
|
+
if (publicFiles.some((file) => file.root === "agent") && !agentRoot) {
|
|
9
|
+
throw new Error("Workspace plan contains agent-root files but ApplyWorkspacePlanOptions.agentDir is missing");
|
|
10
|
+
}
|
|
11
|
+
const rootFor = (root) => root === "workspace" ? workspaceRoot : agentRoot;
|
|
12
|
+
const fileIdentity = (file) => `${file.root}\0${file.relPath}`;
|
|
13
|
+
const receiptFile = (file) => ({
|
|
14
|
+
root: file.root,
|
|
15
|
+
relPath: file.relPath,
|
|
16
|
+
});
|
|
3
17
|
const runtimeOnlyFiles = new Set((options.runtimeOnlyFiles ?? []).map(normalizeWorkspaceRelativePath));
|
|
4
18
|
const ownership = validateWorkspaceOwnership(options.ownership);
|
|
5
|
-
const privateFilePaths = new Set(
|
|
6
|
-
.filter((file) => file.secretSlots.length > 0)
|
|
7
|
-
.map((file) => file.relPath));
|
|
19
|
+
const privateFilePaths = new Set(publicFiles.filter((file) => file.secretSlots.length > 0).map(fileIdentity));
|
|
8
20
|
const existingFiles = options.existingFiles ?? "allow-identical";
|
|
9
21
|
if (existingFiles !== "allow-identical" && existingFiles !== "reject") {
|
|
10
22
|
throw new Error(`Unsupported existing workspace file policy: ${existingFiles}`);
|
|
11
23
|
}
|
|
12
24
|
const resolvedFiles = resolvedPlan.files;
|
|
13
25
|
const privateRuntimeFiles = Object.fromEntries(resolvedFiles
|
|
14
|
-
.filter((file) => runtimeOnlyFiles.has(file.relPath))
|
|
26
|
+
.filter((file) => file.root === "workspace" && runtimeOnlyFiles.has(file.relPath))
|
|
15
27
|
.map((file) => [file.relPath, file.content]));
|
|
16
28
|
const files = resolvedFiles
|
|
17
|
-
.filter((file) => !runtimeOnlyFiles.has(file.relPath))
|
|
29
|
+
.filter((file) => file.root !== "workspace" || !runtimeOnlyFiles.has(file.relPath))
|
|
18
30
|
.map((file) => ({
|
|
19
31
|
...file,
|
|
20
|
-
|
|
32
|
+
rootDir: rootFor(file.root),
|
|
33
|
+
absPath: resolveWorkspaceFilePath(rootFor(file.root), file.relPath),
|
|
21
34
|
}));
|
|
22
35
|
// A prior application of this exact plan makes the workspace live agent
|
|
23
36
|
// state. Preserve changed and removed files. Refresh private generated files
|
|
@@ -31,13 +44,13 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
31
44
|
? files.filter((file) => {
|
|
32
45
|
if (file.ownership !== "materializer")
|
|
33
46
|
return false;
|
|
34
|
-
assertNoSymlinkPathComponents(
|
|
47
|
+
assertNoSymlinkPathComponents(file.rootDir, file.absPath);
|
|
35
48
|
if (!workspaceFileExists(file.absPath))
|
|
36
49
|
return false;
|
|
37
50
|
const matches = workspaceFileMatches(file);
|
|
38
51
|
const hasCurrentOwnership = ownership === undefined ||
|
|
39
52
|
workspaceFileHasOwnership(file.absPath, ownership);
|
|
40
|
-
if (privateFilePaths.has(file
|
|
53
|
+
if (privateFilePaths.has(fileIdentity(file))) {
|
|
41
54
|
return !matches || !hasCurrentOwnership;
|
|
42
55
|
}
|
|
43
56
|
return matches && !hasCurrentOwnership;
|
|
@@ -45,7 +58,7 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
45
58
|
: files;
|
|
46
59
|
if (resumeSamePlan && filesToApply.length === 0) {
|
|
47
60
|
return {
|
|
48
|
-
receipt: safeWorkspacePlanReceipt(publicPlan, files.map(
|
|
61
|
+
receipt: safeWorkspacePlanReceipt(publicPlan, files.map(receiptFile), publicPlanDigest),
|
|
49
62
|
spawn: {
|
|
50
63
|
env: { ...resolvedPlan.env },
|
|
51
64
|
flags: [...resolvedPlan.flags],
|
|
@@ -62,11 +75,11 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
62
75
|
// the workspace, not authorship.
|
|
63
76
|
const mayReplace = (file) => existingFiles === "allow-identical" &&
|
|
64
77
|
file.ownership === "materializer" &&
|
|
65
|
-
isMaterializerOutput(file.relPath, () => regularFileContent(file.absPath));
|
|
78
|
+
isMaterializerOutput(file.relPath, () => regularFileContent(file.absPath), file.content);
|
|
66
79
|
// Validate the complete plan before the first write so a late traversal or
|
|
67
80
|
// existing symlink cannot leave a partially materialized workspace.
|
|
68
81
|
for (const file of filesToApply) {
|
|
69
|
-
assertNoSymlinkPathComponents(
|
|
82
|
+
assertNoSymlinkPathComponents(file.rootDir, file.absPath);
|
|
70
83
|
if (workspaceFileExists(file.absPath) &&
|
|
71
84
|
!mayReplace(file) &&
|
|
72
85
|
(existingFiles === "reject" || !workspaceFileMatches(file))) {
|
|
@@ -79,7 +92,7 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
79
92
|
mkdirSync(dirname(file.absPath), { recursive: true });
|
|
80
93
|
}
|
|
81
94
|
for (const file of filesToApply) {
|
|
82
|
-
assertNoSymlinkPathComponents(
|
|
95
|
+
assertNoSymlinkPathComponents(file.rootDir, file.absPath);
|
|
83
96
|
}
|
|
84
97
|
const prepared = [];
|
|
85
98
|
// Prepared temp links stay alive through publication and rollback. Their
|
|
@@ -94,10 +107,10 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
94
107
|
workspaceFileHasOwnership(file.absPath, ownership))) {
|
|
95
108
|
continue;
|
|
96
109
|
}
|
|
97
|
-
prepared.push(prepareWorkspaceFile(
|
|
110
|
+
prepared.push(prepareWorkspaceFile(file.rootDir, file, ownership));
|
|
98
111
|
}
|
|
99
112
|
for (const entry of prepared) {
|
|
100
|
-
assertNoSymlinkPathComponents(
|
|
113
|
+
assertNoSymlinkPathComponents(rootFor(entry.file.root), entry.file.absPath);
|
|
101
114
|
try {
|
|
102
115
|
linkSync(entry.temporaryPath, entry.file.absPath);
|
|
103
116
|
entry.published = true;
|
|
@@ -120,7 +133,7 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
120
133
|
// superseded bytes. That is the fail-closed direction: the harness
|
|
121
134
|
// whose flag names a missing file refuses to start, whereas silently
|
|
122
135
|
// restored previous-turn bytes would run the wrong prompt.
|
|
123
|
-
assertNoSymlinkPathComponents(
|
|
136
|
+
assertNoSymlinkPathComponents(rootFor(entry.file.root), entry.file.absPath);
|
|
124
137
|
renameSync(entry.temporaryPath, entry.file.absPath);
|
|
125
138
|
entry.published = true;
|
|
126
139
|
continue;
|
|
@@ -158,7 +171,7 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
158
171
|
}
|
|
159
172
|
throw error;
|
|
160
173
|
}
|
|
161
|
-
const receipt = safeWorkspacePlanReceipt(publicPlan, files.map(
|
|
174
|
+
const receipt = safeWorkspacePlanReceipt(publicPlan, files.map(receiptFile), publicPlanDigest);
|
|
162
175
|
const cleanupErrors = [];
|
|
163
176
|
const temporaryPaths = [];
|
|
164
177
|
for (const entry of prepared) {
|
|
@@ -186,7 +199,7 @@ export function applyResolvedWorkspacePlan(publicPlan, resolvedPlan, workspaceDi
|
|
|
186
199
|
runtimeOnlyFiles: privateRuntimeFiles,
|
|
187
200
|
};
|
|
188
201
|
}
|
|
189
|
-
export function safeWorkspacePlanReceipt(plan,
|
|
202
|
+
export function safeWorkspacePlanReceipt(plan, writtenFiles, workspacePlanDigest = hashWorkspacePlan(plan)) {
|
|
190
203
|
return {
|
|
191
204
|
workspacePlanDigest,
|
|
192
205
|
env: Object.fromEntries(Object.entries(plan.env).map(([name, value]) => [
|
|
@@ -197,11 +210,14 @@ export function safeWorkspacePlanReceipt(plan, written, workspacePlanDigest = ha
|
|
|
197
210
|
...promptIntentFields(plan, samePromptIntentValue),
|
|
198
211
|
...systemPromptBindingField(plan),
|
|
199
212
|
unsupported: plan.unsupported.map((entry) => ({ ...entry })),
|
|
200
|
-
written:
|
|
213
|
+
written: writtenFiles
|
|
214
|
+
.filter((file) => file.root === "workspace")
|
|
215
|
+
.map((file) => file.relPath),
|
|
216
|
+
writtenFiles: writtenFiles.map((file) => ({ ...file })),
|
|
201
217
|
};
|
|
202
218
|
}
|
|
203
219
|
import { linkSync, mkdirSync, renameSync, unlinkSync } from "node:fs";
|
|
204
|
-
import { dirname, resolve } from "node:path";
|
|
220
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
205
221
|
import { isMaterializerOutput } from "./harness.js";
|
|
206
222
|
import { hashWorkspacePlan } from "./plan-digest.js";
|
|
207
223
|
import { normalizePlanFiles, normalizeWorkspacePlanArgument, } from "./plan-normalize.js";
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-profile-materialize",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/tangle-network/agent-dev-container.git",
|
|
9
|
+
"directory": "packages/agent-profile-materialize"
|
|
10
|
+
},
|
|
6
11
|
"main": "./dist/index.js",
|
|
7
12
|
"types": "./dist/index.d.ts",
|
|
8
13
|
"exports": {
|
|
@@ -20,7 +25,8 @@
|
|
|
20
25
|
"dist"
|
|
21
26
|
],
|
|
22
27
|
"devDependencies": {
|
|
23
|
-
"@tangle-network/agent-interface": "2.
|
|
28
|
+
"@tangle-network/agent-interface": "2.1.1",
|
|
29
|
+
"@tangle-network/agent-interface-current": "npm:@tangle-network/agent-interface@2.1.1",
|
|
24
30
|
"@types/node": "25.6.0",
|
|
25
31
|
"typescript": "^6.0.3",
|
|
26
32
|
"vitest": "^4.1.10"
|
|
@@ -32,7 +38,9 @@
|
|
|
32
38
|
"build": "tsc -p tsconfig.json",
|
|
33
39
|
"check-types": "tsc --noEmit",
|
|
34
40
|
"clean": "rm -rf dist",
|
|
35
|
-
"test": "
|
|
41
|
+
"test": "pnpm run test:installed-peer && pnpm run test:current-peer",
|
|
42
|
+
"test:installed-peer": "vitest run",
|
|
43
|
+
"test:current-peer": "vitest run --config vitest.current-peer.config.ts",
|
|
36
44
|
"test:watch": "vitest"
|
|
37
45
|
}
|
|
38
46
|
}
|