@tangle-network/agent-profile-materialize 0.20.6 → 0.20.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/harness-prompt.d.ts +18 -0
- package/dist/harness-prompt.js +44 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +70 -5
- package/package.json +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AgentProfile } from "@tangle-network/agent-interface";
|
|
2
|
+
import type { HarnessId } from "./harness.js";
|
|
3
|
+
/**
|
|
4
|
+
* Whether a run keeps the harness's own system prompt, so that a host's
|
|
5
|
+
* environment context joins the profile's addition instead of replacing the
|
|
6
|
+
* harness prompt.
|
|
7
|
+
*
|
|
8
|
+
* A profile that declares prompt intent (`prompt.instructions` or
|
|
9
|
+
* `prompt.appendSystemPrompt`) without `prompt.systemPrompt` asked for the
|
|
10
|
+
* harness prompt to stay in force: that is what `appendSystemPrompt` means.
|
|
11
|
+
* A host that replaced it with its own template would delete exactly what the
|
|
12
|
+
* profile kept. On 2026-10-03 every Discovery research director ran under the
|
|
13
|
+
* sandbox's app-builder template this way: claude-code through
|
|
14
|
+
* `--system-prompt`, opencode through a generated primary agent.
|
|
15
|
+
*/
|
|
16
|
+
export declare function keepsHarnessSystemPrompt(profile: AgentProfile | undefined, harness: HarnessId | string | undefined): boolean;
|
|
17
|
+
/** Join prompt additions in order, skipping empty ones. */
|
|
18
|
+
export declare function joinSystemPromptAdditions(...additions: Array<string | undefined>): string | undefined;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harnesses whose adapter can carry a host's environment context as an
|
|
3
|
+
* ADDITION to the harness's own system prompt:
|
|
4
|
+
*
|
|
5
|
+
* - claude-code and pi: `--append-system-prompt`.
|
|
6
|
+
* - opencode: the addition file that opencode.json names in `instructions`
|
|
7
|
+
* (OPENCODE_APPEND_SYSTEM_PROMPT_FILE). OpenCode's own prompt stays because
|
|
8
|
+
* no profile primary agent replaces it.
|
|
9
|
+
* - codex: `developer_instructions`, which codex sends after its own base
|
|
10
|
+
* instructions.
|
|
11
|
+
*
|
|
12
|
+
* Every other harness keeps the host's replacement, unchanged.
|
|
13
|
+
*/
|
|
14
|
+
const HARNESSES_WITH_ADDITIVE_HOST_PROMPT = new Set(["claude-code", "pi", "opencode", "codex"]);
|
|
15
|
+
/**
|
|
16
|
+
* Whether a run keeps the harness's own system prompt, so that a host's
|
|
17
|
+
* environment context joins the profile's addition instead of replacing the
|
|
18
|
+
* harness prompt.
|
|
19
|
+
*
|
|
20
|
+
* A profile that declares prompt intent (`prompt.instructions` or
|
|
21
|
+
* `prompt.appendSystemPrompt`) without `prompt.systemPrompt` asked for the
|
|
22
|
+
* harness prompt to stay in force: that is what `appendSystemPrompt` means.
|
|
23
|
+
* A host that replaced it with its own template would delete exactly what the
|
|
24
|
+
* profile kept. On 2026-10-03 every Discovery research director ran under the
|
|
25
|
+
* sandbox's app-builder template this way: claude-code through
|
|
26
|
+
* `--system-prompt`, opencode through a generated primary agent.
|
|
27
|
+
*/
|
|
28
|
+
export function keepsHarnessSystemPrompt(profile, harness) {
|
|
29
|
+
const prompt = profile?.prompt;
|
|
30
|
+
if (!prompt || prompt.systemPrompt !== undefined)
|
|
31
|
+
return false;
|
|
32
|
+
const declaresIntent = (prompt.instructions?.length ?? 0) > 0 ||
|
|
33
|
+
prompt.appendSystemPrompt !== undefined;
|
|
34
|
+
return (declaresIntent &&
|
|
35
|
+
harness !== undefined &&
|
|
36
|
+
HARNESSES_WITH_ADDITIVE_HOST_PROMPT.has(harness));
|
|
37
|
+
}
|
|
38
|
+
/** Join prompt additions in order, skipping empty ones. */
|
|
39
|
+
export function joinSystemPromptAdditions(...additions) {
|
|
40
|
+
const parts = additions
|
|
41
|
+
.map((addition) => addition?.trim())
|
|
42
|
+
.filter((addition) => Boolean(addition));
|
|
43
|
+
return parts.length > 0 ? parts.join("\n\n") : undefined;
|
|
44
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type { AgentCandidateWorkspacePlan, AgentCandidateWorkspacePlanReceipt, A
|
|
|
4
4
|
export { OPENCODE_NATIVE_TOOL_IDS, openCodeNativeToolId, } from "./control-maps.js";
|
|
5
5
|
export type { HarnessId } from "./harness.js";
|
|
6
6
|
export { HARNESS_SKILL_DIRS, harnessAutoLoadsAgentContextFile, harnessAutoLoadsContextFile, harnessContextFile, isMaterializerHarness, MATERIALIZER_CONTEXT_PATHS, MATERIALIZER_HARNESSES, markMaterializerContextFile, skillDirForHarness, } from "./harness.js";
|
|
7
|
+
export { joinSystemPromptAdditions, keepsHarnessSystemPrompt, } from "./harness-prompt.js";
|
|
7
8
|
export type { OpenCodeMcpConfig } from "./mcp.js";
|
|
8
9
|
export { openCodeMcpConfig } from "./mcp.js";
|
|
9
10
|
export { modelIdsMatch, qualifyModelId, unqualifyModelId, } from "./model-id.js";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export { applyAgentCandidateWorkspacePlan, materializeCandidateProfile, } from "./candidate.js";
|
|
3
3
|
export { OPENCODE_NATIVE_TOOL_IDS, openCodeNativeToolId, } from "./control-maps.js";
|
|
4
4
|
export { HARNESS_SKILL_DIRS, harnessAutoLoadsAgentContextFile, harnessAutoLoadsContextFile, harnessContextFile, isMaterializerHarness, MATERIALIZER_CONTEXT_PATHS, MATERIALIZER_HARNESSES, markMaterializerContextFile, skillDirForHarness, } from "./harness.js";
|
|
5
|
+
export { joinSystemPromptAdditions, keepsHarnessSystemPrompt, } from "./harness-prompt.js";
|
|
5
6
|
export { openCodeMcpConfig } from "./mcp.js";
|
|
6
7
|
export { modelIdsMatch, qualifyModelId, unqualifyModelId, } from "./model-id.js";
|
|
7
8
|
export { buildPiModelsConfig, buildPiModelsConfigFile, PI_MODELS_CONFIG_PATH, resolvePiRuntimePaths, } from "./pi-models-config.js";
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export declare function materializeMcp(h: HarnessId, mcp: Record<string, ActiveAgentProfileMcpServer>, plan: WorkspacePlan, add: AddPlanFile, unsupported: (d: Unsupported["dimension"], r: string) => void): void;
|
|
2
|
+
/**
|
|
3
|
+
* The header a harness sends to a remote MCP server whose tool-call timeout it
|
|
4
|
+
* raised, carrying the timeout it applied in milliseconds. A server may hold a
|
|
5
|
+
* call for nearly that long. A server that does not see the header must assume
|
|
6
|
+
* the harness default, 60 s for Claude Code and Codex.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MCP_TOOL_TIMEOUT_HEADER = "X-Mcp-Tool-Timeout-Ms";
|
|
2
9
|
/** OpenCode-native MCP configuration used by workspace and startup paths. */
|
|
3
10
|
export type OpenCodeMcpConfig = Record<string, {
|
|
4
11
|
type: "remote";
|
package/dist/mcp.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
export function materializeMcp(h, mcp, plan, add, unsupported) {
|
|
2
2
|
switch (h) {
|
|
3
3
|
case "claude-code": {
|
|
4
|
+
// Claude applies one MCP tool-call timeout to every server, so the
|
|
5
|
+
// longest declared one wins and is what each declaring server is told.
|
|
6
|
+
const timeouts = mcpToolTimeouts(mcp, h, unsupported);
|
|
7
|
+
const toolTimeoutMs = timeouts.size > 0 ? Math.max(...timeouts.values()) : undefined;
|
|
8
|
+
if (toolTimeoutMs !== undefined) {
|
|
9
|
+
setPlanEnvironment(plan, "MCP_TOOL_TIMEOUT", String(toolTimeoutMs));
|
|
10
|
+
}
|
|
4
11
|
const servers = {};
|
|
5
12
|
for (const [name, server] of Object.entries(mcp)) {
|
|
6
13
|
if (server.url) {
|
|
14
|
+
const headers = withAppliedToolTimeout(server.headers, timeouts.has(name) ? toolTimeoutMs : undefined);
|
|
7
15
|
servers[name] = {
|
|
8
16
|
type: server.transport ?? "http",
|
|
9
17
|
url: server.url,
|
|
10
|
-
...(
|
|
18
|
+
...(headers ? { headers } : {}),
|
|
11
19
|
};
|
|
12
20
|
continue;
|
|
13
21
|
}
|
|
@@ -30,13 +38,14 @@ export function materializeMcp(h, mcp, plan, add, unsupported) {
|
|
|
30
38
|
unsupported("mcp", "nanoclaw MCP must be injected through options.mcpServers by its SDK adapter");
|
|
31
39
|
break;
|
|
32
40
|
case "codex": {
|
|
41
|
+
const timeouts = mcpToolTimeouts(mcp, h, unsupported);
|
|
33
42
|
const servers = [];
|
|
34
43
|
for (const [name, server] of Object.entries(mcp).sort(([left], [right]) => compareStrings(left, right))) {
|
|
35
44
|
if (server.url && server.transport === "sse") {
|
|
36
45
|
unsupported("mcp", `codex: remote MCP server "${name}" uses unsupported SSE transport`);
|
|
37
46
|
continue;
|
|
38
47
|
}
|
|
39
|
-
servers.push(concatWorkspacePlanArguments(`${tomlStr(name)} = `, codexMcpServerToml(name, server, plan)));
|
|
48
|
+
servers.push(concatWorkspacePlanArguments(`${tomlStr(name)} = `, codexMcpServerToml(name, server, plan, timeouts.get(name))));
|
|
40
49
|
}
|
|
41
50
|
if (servers.length > 0) {
|
|
42
51
|
// Runtime overrides do not require persisted project trust. Header values
|
|
@@ -143,13 +152,69 @@ function tomlInlineStringTable(values) {
|
|
|
143
152
|
.map(([key, value]) => `${tomlStr(key)} = ${tomlStr(value)}`)
|
|
144
153
|
.join(", ")} }`;
|
|
145
154
|
}
|
|
146
|
-
|
|
155
|
+
/**
|
|
156
|
+
* The header a harness sends to a remote MCP server whose tool-call timeout it
|
|
157
|
+
* raised, carrying the timeout it applied in milliseconds. A server may hold a
|
|
158
|
+
* call for nearly that long. A server that does not see the header must assume
|
|
159
|
+
* the harness default, 60 s for Claude Code and Codex.
|
|
160
|
+
*/
|
|
161
|
+
export const MCP_TOOL_TIMEOUT_HEADER = "X-Mcp-Tool-Timeout-Ms";
|
|
162
|
+
const MIN_MCP_TOOL_TIMEOUT_MS = 1_000;
|
|
163
|
+
const MAX_MCP_TOOL_TIMEOUT_MS = 3_600_000;
|
|
164
|
+
/**
|
|
165
|
+
* The tool-call timeout a profile asks the harness to give one MCP server,
|
|
166
|
+
* from `metadata.toolTimeoutMs`. Profile metadata is open in every published
|
|
167
|
+
* schema, so a profile carrying it validates against older consumers, which
|
|
168
|
+
* ignore it. A malformed value is reported, never guessed.
|
|
169
|
+
*/
|
|
170
|
+
function mcpToolTimeoutMs(server, name, h, unsupported) {
|
|
171
|
+
const value = server.metadata?.toolTimeoutMs;
|
|
172
|
+
if (value === undefined)
|
|
173
|
+
return undefined;
|
|
174
|
+
if (typeof value !== "number" ||
|
|
175
|
+
!Number.isSafeInteger(value) ||
|
|
176
|
+
value < MIN_MCP_TOOL_TIMEOUT_MS ||
|
|
177
|
+
value > MAX_MCP_TOOL_TIMEOUT_MS) {
|
|
178
|
+
unsupported("mcp", `${h}: MCP server "${name}" metadata.toolTimeoutMs must be an integer from ${MIN_MCP_TOOL_TIMEOUT_MS} to ${MAX_MCP_TOOL_TIMEOUT_MS}`);
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
function mcpToolTimeouts(mcp, h, unsupported) {
|
|
184
|
+
const timeouts = new Map();
|
|
185
|
+
for (const [name, server] of Object.entries(mcp)) {
|
|
186
|
+
const value = mcpToolTimeoutMs(server, name, h, unsupported);
|
|
187
|
+
if (value !== undefined)
|
|
188
|
+
timeouts.set(name, value);
|
|
189
|
+
}
|
|
190
|
+
return timeouts;
|
|
191
|
+
}
|
|
192
|
+
function withAppliedToolTimeout(headers, appliedTimeoutMs) {
|
|
193
|
+
if (appliedTimeoutMs === undefined)
|
|
194
|
+
return headers;
|
|
195
|
+
return {
|
|
196
|
+
...headers,
|
|
197
|
+
[MCP_TOOL_TIMEOUT_HEADER]: {
|
|
198
|
+
kind: "public",
|
|
199
|
+
value: String(appliedTimeoutMs),
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function codexMcpServerToml(name, server, plan, toolTimeoutMs) {
|
|
147
204
|
const fields = [];
|
|
205
|
+
// Codex takes whole seconds per server; the server is told what was applied.
|
|
206
|
+
const appliedTimeoutMs = toolTimeoutMs === undefined
|
|
207
|
+
? undefined
|
|
208
|
+
: Math.ceil(toolTimeoutMs / 1000) * 1000;
|
|
209
|
+
if (appliedTimeoutMs !== undefined) {
|
|
210
|
+
fields.push(`tool_timeout_sec = ${appliedTimeoutMs / 1000}`);
|
|
211
|
+
}
|
|
148
212
|
if (server.url) {
|
|
149
213
|
fields.push(`url = ${tomlStr(server.url)}`);
|
|
150
|
-
|
|
214
|
+
const headers = withAppliedToolTimeout(server.headers, appliedTimeoutMs);
|
|
215
|
+
if (headers && Object.keys(headers).length > 0) {
|
|
151
216
|
const envHeaders = {};
|
|
152
|
-
for (const [header, value] of Object.entries(
|
|
217
|
+
for (const [header, value] of Object.entries(headers).sort(([left], [right]) => compareStrings(left, right))) {
|
|
153
218
|
const envName = `TANGLE_MCP_HEADER_${sha256(`${name}\0${header}`)
|
|
154
219
|
.slice(0, 24)
|
|
155
220
|
.toUpperCase()}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-profile-materialize",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.8",
|
|
4
4
|
"description": "Materialize an AgentProfile into the native per-harness workspace files (the one shared materializer for cli-bridge, agent-dev-container, and VB).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|