@tangle-network/agent-profile-materialize 1.1.6 → 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 -112
- 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 -37
- package/dist/managed-workspace.js +0 -290
- 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 -291
- package/dist/secret-session-view.d.ts +0 -36
- package/dist/secret-session-view.js +0 -179
- 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
package/dist/harness.d.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
export declare const MATERIALIZER_HARNESSES: readonly ["claude-code", "nanoclaw", "codex", "opencode", "kimi-code", "pi", "prime", "gemini", "hermes", "openclaw"];
|
|
2
|
-
export type HarnessId = (typeof MATERIALIZER_HARNESSES)[number];
|
|
3
|
-
export declare function isMaterializerHarness(harness: string): harness is HarnessId;
|
|
4
|
-
export declare function assertMaterializerHarness(harness: string): asserts harness is HarnessId;
|
|
5
|
-
export declare const CODEX_SYSTEM_PROMPT_FILE = ".codex/system-prompt.md";
|
|
6
|
-
export declare const OPENCODE_APPEND_SYSTEM_PROMPT_FILE = ".opencode/agent-system-prompt.md";
|
|
7
|
-
export declare const OPENCODE_CONFIG_FILE = "opencode.json";
|
|
8
|
-
export declare const OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
|
|
9
|
-
export declare const OPENCODE_PRIMARY_AGENT_PREFIX = "tangle-profile-";
|
|
10
|
-
export declare const CLAUDE_SETTINGS_PATH = ".tangle/claude-settings.json";
|
|
11
|
-
/** Whether the shared materializer may place its context file in the harness agent directory. */
|
|
12
|
-
export declare function harnessAutoLoadsAgentContextFile(harness: HarnessId): boolean;
|
|
13
|
-
/**
|
|
14
|
-
* Why a harness refuses a system-prompt REPLACEMENT.
|
|
15
|
-
*
|
|
16
|
-
* The reason names the control that does exist, so the refusal is actionable
|
|
17
|
-
* without a capability-matrix lookup — a bare "unsupported" sends the caller
|
|
18
|
-
* looking for a flag that was never there.
|
|
19
|
-
*/
|
|
20
|
-
export declare function systemPromptRefusal(harness: HarnessId): string;
|
|
21
|
-
/**
|
|
22
|
-
* Why a harness refuses an ADDITION to the system prompt.
|
|
23
|
-
*
|
|
24
|
-
* gemini puts its context file in the system-prompt position, so the temptation
|
|
25
|
-
* is to lower an addition there. That is refused because GEMINI.md is the ONE
|
|
26
|
-
* file it auto-loads: an addition written there is byte-identical to the same
|
|
27
|
-
* text under `prompt.instructions`, and no consumer could tell which intent the
|
|
28
|
-
* caller expressed. opencode is not refused for the same situation, because it
|
|
29
|
-
* loads exactly the files its config names and can therefore carry the two
|
|
30
|
-
* intents in separate, separately-ordered files.
|
|
31
|
-
*/
|
|
32
|
-
export declare function appendSystemPromptRefusal(harness: HarnessId): string;
|
|
33
|
-
/** Project context file each harness actually reads from cwd. NEVER a constant. */
|
|
34
|
-
export declare const CONTEXT_FILE: Record<HarnessId, string>;
|
|
35
|
-
/**
|
|
36
|
-
* The paths a profile resource may never mount.
|
|
37
|
-
*
|
|
38
|
-
* Codex names no system-prompt file of its own — `model_instructions_file`
|
|
39
|
-
* takes whatever path it is given — and opencode names none either, since it
|
|
40
|
-
* reads exactly the files its config lists. All three names below were chosen
|
|
41
|
-
* here, so a file already at one of them is this materializer's own output from
|
|
42
|
-
* an earlier application into the same workspace, and a mount there could not
|
|
43
|
-
* survive the next application.
|
|
44
|
-
*
|
|
45
|
-
* That is an inference from the path name, not verified provenance: nothing
|
|
46
|
-
* checks the existing bytes are prior materializer output, so a workspace that
|
|
47
|
-
* carried a foreign file at one of these invented names is overwritten on the
|
|
48
|
-
* first application. Accepted cost of stateless multi-turn re-application,
|
|
49
|
-
* bounded by the names being ones no harness or user convention uses.
|
|
50
|
-
*/
|
|
51
|
-
export declare const MATERIALIZER_RESERVED_PATHS: ReadonlySet<string>;
|
|
52
|
-
/**
|
|
53
|
-
* The context files this package owns by MARKER rather than by name:
|
|
54
|
-
* `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `HERMES.md`.
|
|
55
|
-
*
|
|
56
|
-
* Each is a documented name a repository can already carry, so the name proves
|
|
57
|
-
* nothing. A generated one carries the line {@link markMaterializerContextFile}
|
|
58
|
-
* writes, and only that line lets the next materialization replace it.
|
|
59
|
-
*
|
|
60
|
-
* opencode's context file is absent because it is a RESERVED name this package
|
|
61
|
-
* invented, already owned by the name alone.
|
|
62
|
-
*/
|
|
63
|
-
export declare const MATERIALIZER_CONTEXT_PATHS: ReadonlySet<string>;
|
|
64
|
-
/** Mark one generated native agent file without changing its harness meaning. */
|
|
65
|
-
export declare function markMaterializerSubagent(relPath: string, content: string): string;
|
|
66
|
-
/**
|
|
67
|
-
* Mark one generated context file so the NEXT materialization can prove this
|
|
68
|
-
* package wrote the bytes it is about to replace.
|
|
69
|
-
*
|
|
70
|
-
* The marker is an HTML comment on line 1. CommonMark closes an HTML block at
|
|
71
|
-
* the line holding `-->`, so every instruction below it parses exactly as it
|
|
72
|
-
* did without the line, and the line itself states no instruction. It is one
|
|
73
|
-
* more line this package composes into a file it already composes: it goes on
|
|
74
|
-
* a context file, which carries `prompt.instructions`, and NEVER on a
|
|
75
|
-
* system-prompt REPLACEMENT, whose bytes are the caller's exact text
|
|
76
|
-
* (`.gemini/system.md`; `.codex/system-prompt.md` is a reserved name this
|
|
77
|
-
* package invented and needs no marker to be owned).
|
|
78
|
-
*
|
|
79
|
-
* It carries the digest of the generated body below it, which the subagent
|
|
80
|
-
* marker does not need. `CLAUDE.md` is claude-code's own memory file and the
|
|
81
|
-
* agent appends to it mid-session, so a bare marker would still sit on line 1
|
|
82
|
-
* of a file the agent has since rewritten. The digest tells which leading bytes
|
|
83
|
-
* are still this package's own: the next materialization replaces exactly
|
|
84
|
-
* those and keeps what the agent appended below them
|
|
85
|
-
* ({@link generatedContextAppendix}). The marker's wording is part of every
|
|
86
|
-
* marker already written into live workspaces, so it stays as it is.
|
|
87
|
-
*
|
|
88
|
-
* A reserved context path — opencode's invented instruction file — is returned
|
|
89
|
-
* unchanged: its NAME already proves the provenance, and nothing else may mount
|
|
90
|
-
* there.
|
|
91
|
-
*/
|
|
92
|
-
export declare function markMaterializerContextFile(relPath: string, content: string): string;
|
|
93
|
-
/**
|
|
94
|
-
* Whether a generated file occupies a path that the profile materializer owns.
|
|
95
|
-
*
|
|
96
|
-
* Generated native agent files and generated context files come from the active
|
|
97
|
-
* profile.
|
|
98
|
-
* The active profile must replace the prior profile version at the same path.
|
|
99
|
-
* Otherwise, a profile update stops every resumed session before launch.
|
|
100
|
-
* Explicit resources at these paths stay shared.
|
|
101
|
-
* The plan builder applies ownership only when the source is `generated`.
|
|
102
|
-
* An output marker proves ownership before replacement.
|
|
103
|
-
*/
|
|
104
|
-
export declare function isMaterializerOwnedPath(relPath: string): boolean;
|
|
105
|
-
/**
|
|
106
|
-
* The bytes to publish at owned path `relPath` over the file already there, or
|
|
107
|
-
* `undefined` when that file is not this package's earlier output and must be
|
|
108
|
-
* refused (`undefined` content: no regular file there). For a context file the
|
|
109
|
-
* replacement is the planned file followed by whatever the agent appended
|
|
110
|
-
* below the generated body (claude-code appends its memory to `CLAUDE.md`). A
|
|
111
|
-
* body written before the marker existed is adopted once, only when it is
|
|
112
|
-
* byte-identical to the body this plan writes.
|
|
113
|
-
*/
|
|
114
|
-
export declare function materializerReplacement(relPath: string, content: () => string | undefined, plannedContent: string): string | undefined;
|
|
115
|
-
/**
|
|
116
|
-
* cwd-relative native skill dir per harness, or null if the harness has no
|
|
117
|
-
* cwd skill primitive (skills live in a user/global dir the workspace plan
|
|
118
|
-
* cannot reach). Public so consumers composing a profile can (a) render the
|
|
119
|
-
* exact path the platform will mount a skill at into their prompts instead
|
|
120
|
-
* of hardcoding `~/.claude/skills/...`, and (b) branch on skill support
|
|
121
|
-
* ahead of materialization instead of catching the materialize-time throw.
|
|
122
|
-
*/
|
|
123
|
-
export declare const HARNESS_SKILL_DIRS: Readonly<Record<HarnessId, string | null>>;
|
|
124
|
-
/**
|
|
125
|
-
* Resolve the cwd-relative directory `resources.skills` refs materialize
|
|
126
|
-
* into on `harness`. `null` means the harness cannot receive cwd skills, the
|
|
127
|
-
* same condition that surfaces as an `unsupported`
|
|
128
|
-
* "skills" entry (and an {@link assertWorkspacePlanSupported} throw) at
|
|
129
|
-
* materialize time, exposed here as branchable data.
|
|
130
|
-
*/
|
|
131
|
-
export declare function skillDirForHarness(harness: HarnessId): string | null;
|
package/dist/harness.js
DELETED
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
export const MATERIALIZER_HARNESSES = [
|
|
2
|
-
"claude-code",
|
|
3
|
-
"nanoclaw",
|
|
4
|
-
"codex",
|
|
5
|
-
"opencode",
|
|
6
|
-
"kimi-code",
|
|
7
|
-
"pi",
|
|
8
|
-
"prime",
|
|
9
|
-
"gemini",
|
|
10
|
-
"hermes",
|
|
11
|
-
"openclaw",
|
|
12
|
-
];
|
|
13
|
-
const MATERIALIZER_HARNESS_SET = new Set(MATERIALIZER_HARNESSES);
|
|
14
|
-
export function isMaterializerHarness(harness) {
|
|
15
|
-
return MATERIALIZER_HARNESS_SET.has(harness);
|
|
16
|
-
}
|
|
17
|
-
export function assertMaterializerHarness(harness) {
|
|
18
|
-
if (!isMaterializerHarness(harness)) {
|
|
19
|
-
throw new Error(`Unsupported profile materialization harness "${harness}"`);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
export const CODEX_SYSTEM_PROMPT_FILE = ".codex/system-prompt.md";
|
|
23
|
-
export const OPENCODE_APPEND_SYSTEM_PROMPT_FILE = ".opencode/agent-system-prompt.md";
|
|
24
|
-
export const OPENCODE_CONFIG_FILE = "opencode.json";
|
|
25
|
-
export const OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
|
|
26
|
-
export const OPENCODE_PRIMARY_AGENT_PREFIX = "tangle-profile-";
|
|
27
|
-
export const CLAUDE_SETTINGS_PATH = ".tangle/claude-settings.json";
|
|
28
|
-
/** Whether the shared materializer may place its context file in the harness agent directory. */
|
|
29
|
-
export function harnessAutoLoadsAgentContextFile(harness) {
|
|
30
|
-
assertMaterializerHarness(harness);
|
|
31
|
-
// Prime uses the fork-specific materializePrimeProfileControls path. It
|
|
32
|
-
// does not consume the shared agent-root plan produced by this predicate.
|
|
33
|
-
return harness === "pi";
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Why a harness refuses a system-prompt REPLACEMENT.
|
|
37
|
-
*
|
|
38
|
-
* The reason names the control that does exist, so the refusal is actionable
|
|
39
|
-
* without a capability-matrix lookup — a bare "unsupported" sends the caller
|
|
40
|
-
* looking for a flag that was never there.
|
|
41
|
-
*/
|
|
42
|
-
export function systemPromptRefusal(harness) {
|
|
43
|
-
switch (harness) {
|
|
44
|
-
case "opencode":
|
|
45
|
-
// The control exists, so the reason names the two declarations that reach
|
|
46
|
-
// it rather than claiming opencode has none.
|
|
47
|
-
return 'opencode: the only system-prompt replacement is per-agent (`agent.<name>.prompt`), which binds to the agent selected at launch and a workspace plan cannot guarantee; an executor that applies the plan\'s "--agent" flag to its own spawn declares binds: ["systemPrompt"], and one that composes opencode\'s config itself declares binds: ["opencode.agent-prompt"]';
|
|
48
|
-
default:
|
|
49
|
-
return `${harness}: no verified exact system-prompt replacement control`;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Why a harness refuses an ADDITION to the system prompt.
|
|
54
|
-
*
|
|
55
|
-
* gemini puts its context file in the system-prompt position, so the temptation
|
|
56
|
-
* is to lower an addition there. That is refused because GEMINI.md is the ONE
|
|
57
|
-
* file it auto-loads: an addition written there is byte-identical to the same
|
|
58
|
-
* text under `prompt.instructions`, and no consumer could tell which intent the
|
|
59
|
-
* caller expressed. opencode is not refused for the same situation, because it
|
|
60
|
-
* loads exactly the files its config names and can therefore carry the two
|
|
61
|
-
* intents in separate, separately-ordered files.
|
|
62
|
-
*/
|
|
63
|
-
export function appendSystemPromptRefusal(harness) {
|
|
64
|
-
switch (harness) {
|
|
65
|
-
case "gemini":
|
|
66
|
-
return "gemini: the only additive system-prompt channel is GEMINI.md memory, the single context file it auto-loads, which `prompt.instructions` already materializes — use it instead";
|
|
67
|
-
case "codex":
|
|
68
|
-
return "codex: no verified additive system-prompt control — its AGENTS.md lands in a developer/user message, not the system channel";
|
|
69
|
-
default:
|
|
70
|
-
return `${harness}: no verified additive system-prompt control`;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
/** Project context file each harness actually reads from cwd. NEVER a constant. */
|
|
74
|
-
export const CONTEXT_FILE = {
|
|
75
|
-
"claude-code": "CLAUDE.md",
|
|
76
|
-
nanoclaw: "CLAUDE.md",
|
|
77
|
-
codex: "AGENTS.md",
|
|
78
|
-
opencode: ".opencode/profile-instructions.md",
|
|
79
|
-
"kimi-code": "AGENTS.md",
|
|
80
|
-
pi: "AGENTS.md",
|
|
81
|
-
// prime (PrimeIntellect-ai/prime-agent v0.7.0, be9e2fa0) walks cwd and its
|
|
82
|
-
// ancestors for AGENTS.md/CLAUDE.md, cwd first (fork resource-loader.ts:59
|
|
83
|
-
// candidates, :94-109 walk from cwd inside loadProjectContextFiles).
|
|
84
|
-
prime: "AGENTS.md",
|
|
85
|
-
gemini: "GEMINI.md", // current default; AGENTS.md only via .gemini/settings.json context.fileName
|
|
86
|
-
hermes: "HERMES.md", // highest-priority native name; only ONE context loads (first match)
|
|
87
|
-
openclaw: "AGENTS.md",
|
|
88
|
-
};
|
|
89
|
-
/**
|
|
90
|
-
* The paths a profile resource may never mount.
|
|
91
|
-
*
|
|
92
|
-
* Codex names no system-prompt file of its own — `model_instructions_file`
|
|
93
|
-
* takes whatever path it is given — and opencode names none either, since it
|
|
94
|
-
* reads exactly the files its config lists. All three names below were chosen
|
|
95
|
-
* here, so a file already at one of them is this materializer's own output from
|
|
96
|
-
* an earlier application into the same workspace, and a mount there could not
|
|
97
|
-
* survive the next application.
|
|
98
|
-
*
|
|
99
|
-
* That is an inference from the path name, not verified provenance: nothing
|
|
100
|
-
* checks the existing bytes are prior materializer output, so a workspace that
|
|
101
|
-
* carried a foreign file at one of these invented names is overwritten on the
|
|
102
|
-
* first application. Accepted cost of stateless multi-turn re-application,
|
|
103
|
-
* bounded by the names being ones no harness or user convention uses.
|
|
104
|
-
*/
|
|
105
|
-
export const MATERIALIZER_RESERVED_PATHS = new Set([
|
|
106
|
-
CODEX_SYSTEM_PROMPT_FILE,
|
|
107
|
-
OPENCODE_APPEND_SYSTEM_PROMPT_FILE,
|
|
108
|
-
CONTEXT_FILE.opencode,
|
|
109
|
-
]);
|
|
110
|
-
/** Every path any harness reads as its project context file. */
|
|
111
|
-
const CONTEXT_FILE_PATHS = new Set(Object.values(CONTEXT_FILE));
|
|
112
|
-
/**
|
|
113
|
-
* The context files this package owns by MARKER rather than by name:
|
|
114
|
-
* `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `HERMES.md`.
|
|
115
|
-
*
|
|
116
|
-
* Each is a documented name a repository can already carry, so the name proves
|
|
117
|
-
* nothing. A generated one carries the line {@link markMaterializerContextFile}
|
|
118
|
-
* writes, and only that line lets the next materialization replace it.
|
|
119
|
-
*
|
|
120
|
-
* opencode's context file is absent because it is a RESERVED name this package
|
|
121
|
-
* invented, already owned by the name alone.
|
|
122
|
-
*/
|
|
123
|
-
export const MATERIALIZER_CONTEXT_PATHS = new Set([...CONTEXT_FILE_PATHS].filter((path) => !MATERIALIZER_RESERVED_PATHS.has(path)));
|
|
124
|
-
/**
|
|
125
|
-
* The paths whose generated content is wholly this package's output, per
|
|
126
|
-
* {@link PlanFile.ownership}: the reserved names, the context files, and
|
|
127
|
-
* `opencode.json`, which carries only the registrations this package generates
|
|
128
|
-
* from the profile.
|
|
129
|
-
*
|
|
130
|
-
* `opencode.json` and every context file is a documented name a user repository
|
|
131
|
-
* can carry, so the name alone proves nothing and a profile resource may still
|
|
132
|
-
* mount one. An existing file there is replaced only when
|
|
133
|
-
* {@link materializerReplacement} recognizes its bytes as this package's own
|
|
134
|
-
* earlier output.
|
|
135
|
-
*
|
|
136
|
-
* Deliberately absent: `.gemini/system.md`, the file `prompt.systemPrompt`
|
|
137
|
-
* lowers to. The line a marker would have to add is the reason. A REPLACEMENT
|
|
138
|
-
* is the caller's exact bytes and this package may add none of its own, so that
|
|
139
|
-
* path keeps refusing. A context file carries `prompt.instructions`, which this
|
|
140
|
-
* package already composes — it joins the blocks and appends the final newline
|
|
141
|
-
* — so one more composed line is inside the same authority.
|
|
142
|
-
*
|
|
143
|
-
* That line, not "which channel the harness reads it on", is what separates the
|
|
144
|
-
* two. `GEMINI.md` is owned even though gemini lowers its context file into the
|
|
145
|
-
* system-prompt position as an ADDITION ({@link appendSystemPromptRefusal}),
|
|
146
|
-
* because the intent it carries is still instructions this package composed.
|
|
147
|
-
*/
|
|
148
|
-
const MATERIALIZER_OWNED_PATHS = new Set([
|
|
149
|
-
...MATERIALIZER_RESERVED_PATHS,
|
|
150
|
-
...MATERIALIZER_CONTEXT_PATHS,
|
|
151
|
-
OPENCODE_CONFIG_FILE,
|
|
152
|
-
]);
|
|
153
|
-
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;
|
|
154
|
-
/** The one provenance token every generated-file marker carries. */
|
|
155
|
-
const MATERIALIZER_MARKER = "tangle-agent-profile-materialize: generated";
|
|
156
|
-
const MATERIALIZER_SUBAGENT_MARKER = `# ${MATERIALIZER_MARKER}`;
|
|
157
|
-
const MATERIALIZER_CONTEXT_MARKER_NOTE = "do not edit; the next profile materialization replaces this file";
|
|
158
|
-
function materializerSubagentMarkerPrefix(relPath) {
|
|
159
|
-
return relPath.endsWith(".toml")
|
|
160
|
-
? `${MATERIALIZER_SUBAGENT_MARKER}\n`
|
|
161
|
-
: `---\n${MATERIALIZER_SUBAGENT_MARKER}\n`;
|
|
162
|
-
}
|
|
163
|
-
/** Mark one generated native agent file without changing its harness meaning. */
|
|
164
|
-
export function markMaterializerSubagent(relPath, content) {
|
|
165
|
-
if (!MATERIALIZER_SUBAGENT_PATH.test(relPath)) {
|
|
166
|
-
throw new Error(`Not a native agent path: ${relPath}`);
|
|
167
|
-
}
|
|
168
|
-
if (relPath.endsWith(".toml")) {
|
|
169
|
-
return `${materializerSubagentMarkerPrefix(relPath)}${content}`;
|
|
170
|
-
}
|
|
171
|
-
if (!content.startsWith("---\n")) {
|
|
172
|
-
throw new Error(`Native Markdown agent has no frontmatter: ${relPath}`);
|
|
173
|
-
}
|
|
174
|
-
return `${materializerSubagentMarkerPrefix(relPath)}${content.slice(4)}`;
|
|
175
|
-
}
|
|
176
|
-
/** The marker line a generated context file whose body is `body` carries. */
|
|
177
|
-
function materializerContextMarker(body) {
|
|
178
|
-
const digest = createHash("sha256").update(body, "utf8").digest("hex");
|
|
179
|
-
return `<!-- ${MATERIALIZER_MARKER} sha256:${digest}; ${MATERIALIZER_CONTEXT_MARKER_NOTE} -->`;
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Mark one generated context file so the NEXT materialization can prove this
|
|
183
|
-
* package wrote the bytes it is about to replace.
|
|
184
|
-
*
|
|
185
|
-
* The marker is an HTML comment on line 1. CommonMark closes an HTML block at
|
|
186
|
-
* the line holding `-->`, so every instruction below it parses exactly as it
|
|
187
|
-
* did without the line, and the line itself states no instruction. It is one
|
|
188
|
-
* more line this package composes into a file it already composes: it goes on
|
|
189
|
-
* a context file, which carries `prompt.instructions`, and NEVER on a
|
|
190
|
-
* system-prompt REPLACEMENT, whose bytes are the caller's exact text
|
|
191
|
-
* (`.gemini/system.md`; `.codex/system-prompt.md` is a reserved name this
|
|
192
|
-
* package invented and needs no marker to be owned).
|
|
193
|
-
*
|
|
194
|
-
* It carries the digest of the generated body below it, which the subagent
|
|
195
|
-
* marker does not need. `CLAUDE.md` is claude-code's own memory file and the
|
|
196
|
-
* agent appends to it mid-session, so a bare marker would still sit on line 1
|
|
197
|
-
* of a file the agent has since rewritten. The digest tells which leading bytes
|
|
198
|
-
* are still this package's own: the next materialization replaces exactly
|
|
199
|
-
* those and keeps what the agent appended below them
|
|
200
|
-
* ({@link generatedContextAppendix}). The marker's wording is part of every
|
|
201
|
-
* marker already written into live workspaces, so it stays as it is.
|
|
202
|
-
*
|
|
203
|
-
* A reserved context path — opencode's invented instruction file — is returned
|
|
204
|
-
* unchanged: its NAME already proves the provenance, and nothing else may mount
|
|
205
|
-
* there.
|
|
206
|
-
*/
|
|
207
|
-
export function markMaterializerContextFile(relPath, content) {
|
|
208
|
-
if (!CONTEXT_FILE_PATHS.has(relPath)) {
|
|
209
|
-
throw new Error(`Not a harness context file: ${relPath}`);
|
|
210
|
-
}
|
|
211
|
-
if (!MATERIALIZER_CONTEXT_PATHS.has(relPath))
|
|
212
|
-
return content;
|
|
213
|
-
return `${materializerContextMarker(content)}\n${content}`;
|
|
214
|
-
}
|
|
215
|
-
const MATERIALIZER_CONTEXT_MARKER_HEAD = `<!-- ${MATERIALIZER_MARKER} sha256:`;
|
|
216
|
-
const MATERIALIZER_CONTEXT_MARKER_TAIL = `; ${MATERIALIZER_CONTEXT_MARKER_NOTE} -->`;
|
|
217
|
-
const SHA256_HEX = /^[0-9a-f]{64}$/u;
|
|
218
|
-
/**
|
|
219
|
-
* Text an agent appended below this package's generated context body: `""`
|
|
220
|
-
* for exact output, `undefined` when no marker is on line 1 or no leading part
|
|
221
|
-
* of the body matches its digest (an edit inside the generated part). Each
|
|
222
|
-
* line boundary is a candidate end; one incremental hash walks the body once.
|
|
223
|
-
*/
|
|
224
|
-
function generatedContextAppendix(existing) {
|
|
225
|
-
const lineEnd = existing.indexOf("\n");
|
|
226
|
-
if (lineEnd === -1)
|
|
227
|
-
return undefined;
|
|
228
|
-
const marker = existing.slice(0, lineEnd);
|
|
229
|
-
if (!marker.startsWith(MATERIALIZER_CONTEXT_MARKER_HEAD) ||
|
|
230
|
-
!marker.endsWith(MATERIALIZER_CONTEXT_MARKER_TAIL)) {
|
|
231
|
-
return undefined;
|
|
232
|
-
}
|
|
233
|
-
const digest = marker.slice(MATERIALIZER_CONTEXT_MARKER_HEAD.length, marker.length - MATERIALIZER_CONTEXT_MARKER_TAIL.length);
|
|
234
|
-
if (!SHA256_HEX.test(digest))
|
|
235
|
-
return undefined;
|
|
236
|
-
const body = existing.slice(lineEnd + 1);
|
|
237
|
-
// No cut at 0: a generated body is never empty, so it never adopts a whole file.
|
|
238
|
-
const boundaries = [];
|
|
239
|
-
for (let newline = body.indexOf("\n"); newline !== -1; newline = body.indexOf("\n", newline + 1)) {
|
|
240
|
-
boundaries.push(newline, newline + 1);
|
|
241
|
-
}
|
|
242
|
-
if (boundaries.length === 0 ||
|
|
243
|
-
boundaries[boundaries.length - 1] !== body.length) {
|
|
244
|
-
boundaries.push(body.length);
|
|
245
|
-
}
|
|
246
|
-
const hash = createHash("sha256");
|
|
247
|
-
let hashed = 0;
|
|
248
|
-
for (const end of boundaries) {
|
|
249
|
-
hash.update(body.slice(hashed, end), "utf8");
|
|
250
|
-
hashed = end;
|
|
251
|
-
if (hash.copy().digest("hex") === digest)
|
|
252
|
-
return body.slice(end);
|
|
253
|
-
}
|
|
254
|
-
return undefined;
|
|
255
|
-
}
|
|
256
|
-
/** The planned context file, then the agent's appended text on its own line. */
|
|
257
|
-
function withContextAppendix(plannedContent, appendix) {
|
|
258
|
-
if (appendix === "" ||
|
|
259
|
-
plannedContent.endsWith("\n") ||
|
|
260
|
-
appendix.startsWith("\n")) {
|
|
261
|
-
return `${plannedContent}${appendix}`;
|
|
262
|
-
}
|
|
263
|
-
return `${plannedContent}\n${appendix}`;
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Whether a generated file occupies a path that the profile materializer owns.
|
|
267
|
-
*
|
|
268
|
-
* Generated native agent files and generated context files come from the active
|
|
269
|
-
* profile.
|
|
270
|
-
* The active profile must replace the prior profile version at the same path.
|
|
271
|
-
* Otherwise, a profile update stops every resumed session before launch.
|
|
272
|
-
* Explicit resources at these paths stay shared.
|
|
273
|
-
* The plan builder applies ownership only when the source is `generated`.
|
|
274
|
-
* An output marker proves ownership before replacement.
|
|
275
|
-
*/
|
|
276
|
-
export function isMaterializerOwnedPath(relPath) {
|
|
277
|
-
return (MATERIALIZER_OWNED_PATHS.has(relPath) ||
|
|
278
|
-
MATERIALIZER_SUBAGENT_PATH.test(relPath));
|
|
279
|
-
}
|
|
280
|
-
/** The top-level keys this package writes into `opencode.json`. */
|
|
281
|
-
const OPENCODE_CONFIG_KEYS = new Set([
|
|
282
|
-
"$schema",
|
|
283
|
-
"instructions",
|
|
284
|
-
"mcp",
|
|
285
|
-
"tools",
|
|
286
|
-
"permission",
|
|
287
|
-
]);
|
|
288
|
-
/**
|
|
289
|
-
* The bytes to publish at owned path `relPath` over the file already there, or
|
|
290
|
-
* `undefined` when that file is not this package's earlier output and must be
|
|
291
|
-
* refused (`undefined` content: no regular file there). For a context file the
|
|
292
|
-
* replacement is the planned file followed by whatever the agent appended
|
|
293
|
-
* below the generated body (claude-code appends its memory to `CLAUDE.md`). A
|
|
294
|
-
* body written before the marker existed is adopted once, only when it is
|
|
295
|
-
* byte-identical to the body this plan writes.
|
|
296
|
-
*/
|
|
297
|
-
export function materializerReplacement(relPath, content, plannedContent) {
|
|
298
|
-
if (!MATERIALIZER_CONTEXT_PATHS.has(relPath)) {
|
|
299
|
-
return isMaterializerOutput(relPath, content, plannedContent)
|
|
300
|
-
? plannedContent
|
|
301
|
-
: undefined;
|
|
302
|
-
}
|
|
303
|
-
const existing = content();
|
|
304
|
-
if (existing === undefined)
|
|
305
|
-
return undefined;
|
|
306
|
-
const appendix = generatedContextAppendix(existing);
|
|
307
|
-
if (appendix === "")
|
|
308
|
-
return plannedContent;
|
|
309
|
-
// Appended text rides only on a marked plan, which the next version recognizes.
|
|
310
|
-
if (generatedContextAppendix(plannedContent) !== "")
|
|
311
|
-
return undefined;
|
|
312
|
-
if (appendix !== undefined) {
|
|
313
|
-
return withContextAppendix(plannedContent, appendix);
|
|
314
|
-
}
|
|
315
|
-
return existing === plannedContent.slice(plannedContent.indexOf("\n") + 1)
|
|
316
|
-
? plannedContent
|
|
317
|
-
: undefined;
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Whether the file already at owned path `relPath`, other than a context file,
|
|
321
|
-
* is this package's own earlier output. A reserved name proves that by itself.
|
|
322
|
-
* Native agent files require the generated marker. An exact unmarked copy of
|
|
323
|
-
* the planned native agent file is adopted once for upgrades from materializer
|
|
324
|
-
* versions without that marker. `undefined` content means no regular file is
|
|
325
|
-
* there.
|
|
326
|
-
*
|
|
327
|
-
* An `opencode.json` this package wrote holds the opencode schema id, only the
|
|
328
|
-
* keys this package writes, and only instruction paths this package invented;
|
|
329
|
-
* a user config holding nothing else is replaced too, any other key or own
|
|
330
|
-
* instruction path is refused.
|
|
331
|
-
*/
|
|
332
|
-
function isMaterializerOutput(relPath, content, plannedContent) {
|
|
333
|
-
if (MATERIALIZER_RESERVED_PATHS.has(relPath))
|
|
334
|
-
return true;
|
|
335
|
-
if (relPath === ".pi/mcp.json" || relPath === ".kimi-code/mcp.json")
|
|
336
|
-
return isMaterializerMcpConfig(relPath, content);
|
|
337
|
-
if (MATERIALIZER_SUBAGENT_PATH.test(relPath)) {
|
|
338
|
-
const existing = content();
|
|
339
|
-
if (existing === undefined)
|
|
340
|
-
return false;
|
|
341
|
-
const markerPrefix = materializerSubagentMarkerPrefix(relPath);
|
|
342
|
-
if (existing.startsWith(markerPrefix))
|
|
343
|
-
return true;
|
|
344
|
-
if (!plannedContent.startsWith(markerPrefix))
|
|
345
|
-
return false;
|
|
346
|
-
const legacyContent = relPath.endsWith(".toml")
|
|
347
|
-
? plannedContent.slice(markerPrefix.length)
|
|
348
|
-
: `---\n${plannedContent.slice(markerPrefix.length)}`;
|
|
349
|
-
return existing === legacyContent;
|
|
350
|
-
}
|
|
351
|
-
if (relPath !== OPENCODE_CONFIG_FILE)
|
|
352
|
-
return false;
|
|
353
|
-
const bytes = content();
|
|
354
|
-
if (bytes === undefined)
|
|
355
|
-
return false;
|
|
356
|
-
let parsed;
|
|
357
|
-
try {
|
|
358
|
-
parsed = JSON.parse(bytes);
|
|
359
|
-
}
|
|
360
|
-
catch {
|
|
361
|
-
return false;
|
|
362
|
-
}
|
|
363
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
364
|
-
return false;
|
|
365
|
-
}
|
|
366
|
-
const config = parsed;
|
|
367
|
-
if (config.$schema !== OPENCODE_CONFIG_SCHEMA)
|
|
368
|
-
return false;
|
|
369
|
-
if (!Object.keys(config).every((key) => OPENCODE_CONFIG_KEYS.has(key))) {
|
|
370
|
-
return false;
|
|
371
|
-
}
|
|
372
|
-
const instructions = config.instructions;
|
|
373
|
-
if (instructions === undefined)
|
|
374
|
-
return true;
|
|
375
|
-
return (Array.isArray(instructions) &&
|
|
376
|
-
instructions.every((entry) => entry === OPENCODE_APPEND_SYSTEM_PROMPT_FILE ||
|
|
377
|
-
entry === CONTEXT_FILE.opencode));
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* cwd-relative native skill dir per harness, or null if the harness has no
|
|
381
|
-
* cwd skill primitive (skills live in a user/global dir the workspace plan
|
|
382
|
-
* cannot reach). Public so consumers composing a profile can (a) render the
|
|
383
|
-
* exact path the platform will mount a skill at into their prompts instead
|
|
384
|
-
* of hardcoding `~/.claude/skills/...`, and (b) branch on skill support
|
|
385
|
-
* ahead of materialization instead of catching the materialize-time throw.
|
|
386
|
-
*/
|
|
387
|
-
export const HARNESS_SKILL_DIRS = {
|
|
388
|
-
"claude-code": ".claude/skills",
|
|
389
|
-
nanoclaw: ".claude/skills",
|
|
390
|
-
codex: ".codex/skills",
|
|
391
|
-
opencode: ".opencode/skills",
|
|
392
|
-
"kimi-code": ".kimi/skills",
|
|
393
|
-
pi: ".pi/skills",
|
|
394
|
-
// prime auto-discovers project skills from <cwd>/<configDir>/skills where the
|
|
395
|
-
// fork ships configDir ".prime/agent" (fork package.json piConfig, config.ts:494;
|
|
396
|
-
// package-manager.ts:885 projectBaseDir, :2175-2180 projectDirs.skills,
|
|
397
|
-
// :2209-2213 collectAutoSkillEntries). The loader recurses into
|
|
398
|
-
// <name>/SKILL.md subdirs (skills.ts:328-372).
|
|
399
|
-
prime: ".prime/agent/skills",
|
|
400
|
-
gemini: ".gemini/skills",
|
|
401
|
-
hermes: null, // skills are ~/.hermes/skills (user) only — no cwd discovery
|
|
402
|
-
openclaw: "skills", // <workspace>/skills/<name>/SKILL.md
|
|
403
|
-
};
|
|
404
|
-
/**
|
|
405
|
-
* Resolve the cwd-relative directory `resources.skills` refs materialize
|
|
406
|
-
* into on `harness`. `null` means the harness cannot receive cwd skills, the
|
|
407
|
-
* same condition that surfaces as an `unsupported`
|
|
408
|
-
* "skills" entry (and an {@link assertWorkspacePlanSupported} throw) at
|
|
409
|
-
* materialize time, exposed here as branchable data.
|
|
410
|
-
*/
|
|
411
|
-
export function skillDirForHarness(harness) {
|
|
412
|
-
assertMaterializerHarness(harness);
|
|
413
|
-
return HARNESS_SKILL_DIRS[harness];
|
|
414
|
-
}
|
|
415
|
-
import { createHash } from "node:crypto";
|
|
416
|
-
import { isMaterializerMcpConfig } from "./mcp-config-ownership.js";
|
package/dist/hooks.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export declare function materializeHooks(h: HarnessId, hooks: Record<string, AgentProfileHookCommand[]>, add: AddPlanFile, unsupported: (d: Unsupported["dimension"], r: string) => void, plan: WorkspacePlan): void;
|
|
2
|
-
export declare function registerClaudeSettings(plan: WorkspacePlan): void;
|
|
3
|
-
export declare function hookWrapperSource(executable: string, args: string[], extraEnv: Record<string, string>, envSources?: Record<string, string>): string;
|
|
4
|
-
import type { AgentProfileHookCommand } from "@tangle-network/agent-interface";
|
|
5
|
-
import type { AddPlanFile } from "./plan-json.js";
|
|
6
|
-
import type { HarnessId, Unsupported, WorkspacePlan } from "./plan-types.js";
|