@tangle-network/agent-profile-materialize 0.5.1 → 0.6.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/index.d.ts CHANGED
@@ -74,6 +74,15 @@ export interface ApplyWorkspacePlanOptions {
74
74
  }
75
75
  /** Fail when a profile asks for controls the chosen harness cannot execute. */
76
76
  export declare function assertWorkspacePlanSupported(plan: Pick<WorkspacePlan, "unsupported">): void;
77
+ /**
78
+ * Enforce plan supportability under the profile's `resources.failOnError`
79
+ * policy. Strict by default: `failOnError` absent or `true` throws on any
80
+ * unsupported dimension. An explicitly best-effort profile
81
+ * (`failOnError: false`) materializes the supported subset instead; the
82
+ * returned `"dimension: reason"` entries are what the caller must surface
83
+ * as a warning so the skipped dimensions stay visible to operators.
84
+ */
85
+ export declare function enforceWorkspacePlanSupport(plan: Pick<WorkspacePlan, "unsupported">, failOnError: boolean | undefined): string[];
77
86
  /**
78
87
  * Candidate-safe workspace plan. Spawn values remain typed until the runtime
79
88
  * resolves them; the materializer never joins argv into a shell command.
@@ -99,6 +108,42 @@ export interface MaterializeCandidateProfileOptions {
99
108
  /** Exact text fetched from pinned resource refs, keyed by declared SHA-256. */
100
109
  resolvedResources?: ReadonlyMap<Sha256Digest, string>;
101
110
  }
111
+ /**
112
+ * Whether the harness LOADS its {@link CONTEXT_FILE} from cwd on its own.
113
+ *
114
+ * True for every harness whose CLI discovers the file by name (CLAUDE.md,
115
+ * AGENTS.md, …). opencode is the exception: it reads only what its
116
+ * `opencode.json` `instructions[]` names, so a caller writing context for
117
+ * opencode must register the path as well as write it — see the `instructions`
118
+ * lowering below, and the interactive TUI's persona fold.
119
+ */
120
+ export declare function harnessAutoLoadsContextFile(harness: HarnessId): boolean;
121
+ /**
122
+ * The project context file this harness reads from cwd — its stable, documented
123
+ * ALWAYS-LOADED surface. Exported so a caller lowering additive context (e.g. a
124
+ * bare TUI's persona, which has no per-message system channel) targets the same
125
+ * file the materializer writes instructions to, instead of keeping its own copy
126
+ * of this per-harness map.
127
+ */
128
+ export declare function harnessContextFile(harness: HarnessId): string;
129
+ /**
130
+ * cwd-relative native skill dir per harness, or null if the harness has no
131
+ * cwd skill primitive (skills live in a user/global dir the workspace plan
132
+ * cannot reach). Public so consumers composing a profile can (a) render the
133
+ * exact path the platform will mount a skill at into their prompts instead
134
+ * of hardcoding `~/.claude/skills/...`, and (b) branch on skill support
135
+ * ahead of materialization instead of catching the materialize-time throw.
136
+ */
137
+ export declare const HARNESS_SKILL_DIRS: Readonly<Record<HarnessId, string | null>>;
138
+ /**
139
+ * Resolve the cwd-relative directory `resources.skills` refs materialize
140
+ * into on `harness` (after alias canonicalization: claude/claudish →
141
+ * claude-code, kimi → kimi-code). `null` means the harness cannot receive
142
+ * cwd skills — the same condition that surfaces as an `unsupported`
143
+ * "skills" entry (and an {@link assertWorkspacePlanSupported} throw) at
144
+ * materialize time, exposed here as branchable data.
145
+ */
146
+ export declare function skillDirForHarness(harness: HarnessId): string | null;
102
147
  export interface ResolveAgentProfileResourcesOptions {
103
148
  /** Repository used when a GitHub resource omits its own `repository`. */
104
149
  repository?: string;
@@ -154,6 +199,10 @@ export declare function applyAgentCandidateWorkspacePlan(plan: AgentCandidateWor
154
199
  * agent-dev-container box, and VB. Returns `unsupported` so the caller can log/fail.
155
200
  */
156
201
  export declare function applyWorkspacePlan(plan: WorkspacePlan, workspaceDir: string, options?: ApplyWorkspacePlanOptions): WorkspacePlanReceipt;
202
+ /** Tool IDs reported by the OpenCode binary shipped in the Sandbox image. */
203
+ export declare const OPENCODE_NATIVE_TOOL_IDS: readonly ["apply_patch", "bash", "codesearch", "edit", "glob", "grep", "question", "read", "skill", "task", "todowrite", "webfetch", "websearch", "write"];
204
+ /** Resolve portable and legacy profile names to current OpenCode tool IDs. */
205
+ export declare function openCodeNativeToolId(name: string): string | undefined;
157
206
  /** OpenCode-native MCP configuration used by workspace and startup paths. */
158
207
  export type OpenCodeMcpConfig = Record<string, {
159
208
  type: "remote";
package/dist/index.js CHANGED
@@ -54,7 +54,52 @@ export function assertWorkspacePlanSupported(plan) {
54
54
  throw new Error(formatUnsupported(plan.unsupported));
55
55
  }
56
56
  }
57
+ /**
58
+ * Enforce plan supportability under the profile's `resources.failOnError`
59
+ * policy. Strict by default: `failOnError` absent or `true` throws on any
60
+ * unsupported dimension. An explicitly best-effort profile
61
+ * (`failOnError: false`) materializes the supported subset instead; the
62
+ * returned `"dimension: reason"` entries are what the caller must surface
63
+ * as a warning so the skipped dimensions stay visible to operators.
64
+ */
65
+ export function enforceWorkspacePlanSupport(plan, failOnError) {
66
+ if (failOnError === false) {
67
+ return plan.unsupported.map(({ dimension, reason }) => `${dimension}: ${reason}`);
68
+ }
69
+ assertWorkspacePlanSupported(plan);
70
+ return [];
71
+ }
57
72
  // ─── Verified per-harness conventions (capability matrix) ───────────────────
73
+ /**
74
+ * Whether the harness LOADS its {@link CONTEXT_FILE} from cwd on its own.
75
+ *
76
+ * True for every harness whose CLI discovers the file by name (CLAUDE.md,
77
+ * AGENTS.md, …). opencode is the exception: it reads only what its
78
+ * `opencode.json` `instructions[]` names, so a caller writing context for
79
+ * opencode must register the path as well as write it — see the `instructions`
80
+ * lowering below, and the interactive TUI's persona fold.
81
+ */
82
+ export function harnessAutoLoadsContextFile(harness) {
83
+ return canonicalHarness(harness) !== "opencode";
84
+ }
85
+ /**
86
+ * The project context file this harness reads from cwd — its stable, documented
87
+ * ALWAYS-LOADED surface. Exported so a caller lowering additive context (e.g. a
88
+ * bare TUI's persona, which has no per-message system channel) targets the same
89
+ * file the materializer writes instructions to, instead of keeping its own copy
90
+ * of this per-harness map.
91
+ */
92
+ export function harnessContextFile(harness) {
93
+ // The map is keyed by HarnessId, but callers reach this from runtime strings
94
+ // (a registry spec name, a request field). A miss would otherwise return
95
+ // undefined behind a `string` return type and surface as an undefined write
96
+ // path much later.
97
+ const path = CONTEXT_FILE[canonicalHarness(harness)];
98
+ if (!path) {
99
+ throw new Error(`No project context file is known for harness "${harness}"`);
100
+ }
101
+ return path;
102
+ }
58
103
  /** Project context file each harness actually reads from cwd. NEVER a constant. */
59
104
  const CONTEXT_FILE = {
60
105
  "claude-code": "CLAUDE.md",
@@ -70,8 +115,15 @@ const CONTEXT_FILE = {
70
115
  hermes: "HERMES.md", // highest-priority native name; only ONE context loads (first match)
71
116
  openclaw: "AGENTS.md",
72
117
  };
73
- /** cwd-relative native skill dir, or null if the harness has no cwd skill primitive. */
74
- const SKILL_DIR = {
118
+ /**
119
+ * cwd-relative native skill dir per harness, or null if the harness has no
120
+ * cwd skill primitive (skills live in a user/global dir the workspace plan
121
+ * cannot reach). Public so consumers composing a profile can (a) render the
122
+ * exact path the platform will mount a skill at into their prompts instead
123
+ * of hardcoding `~/.claude/skills/...`, and (b) branch on skill support
124
+ * ahead of materialization instead of catching the materialize-time throw.
125
+ */
126
+ export const HARNESS_SKILL_DIRS = {
75
127
  "claude-code": ".claude/skills",
76
128
  claude: ".claude/skills",
77
129
  claudish: ".claude/skills",
@@ -85,6 +137,18 @@ const SKILL_DIR = {
85
137
  hermes: null, // skills are ~/.hermes/skills (user) only — no cwd discovery
86
138
  openclaw: "skills", // <workspace>/skills/<name>/SKILL.md
87
139
  };
140
+ const SKILL_DIR = HARNESS_SKILL_DIRS;
141
+ /**
142
+ * Resolve the cwd-relative directory `resources.skills` refs materialize
143
+ * into on `harness` (after alias canonicalization: claude/claudish →
144
+ * claude-code, kimi → kimi-code). `null` means the harness cannot receive
145
+ * cwd skills — the same condition that surfaces as an `unsupported`
146
+ * "skills" entry (and an {@link assertWorkspacePlanSupported} throw) at
147
+ * materialize time, exposed here as branchable data.
148
+ */
149
+ export function skillDirForHarness(harness) {
150
+ return HARNESS_SKILL_DIRS[canonicalHarness(harness)];
151
+ }
88
152
  function canonicalHarness(h) {
89
153
  if (h === "claude" || h === "claudish")
90
154
  return "claude-code";
@@ -1310,33 +1374,38 @@ function claudeToolName(name) {
1310
1374
  return "WebFetch";
1311
1375
  return CLAUDE_TOOL_NAMES[normalizedControlName(name)];
1312
1376
  }
1313
- const OPENCODE_TOOL_NAMES = new Set([
1377
+ /** Tool IDs reported by the OpenCode binary shipped in the Sandbox image. */
1378
+ export const OPENCODE_NATIVE_TOOL_IDS = [
1379
+ "apply_patch",
1314
1380
  "bash",
1315
- "batch",
1316
1381
  "codesearch",
1317
1382
  "edit",
1318
1383
  "glob",
1319
1384
  "grep",
1320
- "list",
1321
- "lsp",
1322
- "multiedit",
1323
- "patch",
1385
+ "question",
1324
1386
  "read",
1387
+ "skill",
1325
1388
  "task",
1326
- "todoread",
1327
1389
  "todowrite",
1328
1390
  "webfetch",
1329
1391
  "websearch",
1330
1392
  "write",
1331
- ]);
1332
- function openCodeToolName(name) {
1393
+ ];
1394
+ const OPENCODE_NATIVE_TOOL_ID_SET = new Set(OPENCODE_NATIVE_TOOL_IDS);
1395
+ /** Resolve portable and legacy profile names to current OpenCode tool IDs. */
1396
+ export function openCodeNativeToolId(name) {
1333
1397
  const web = nativeWebToolKind(name);
1334
1398
  if (web === "search")
1335
1399
  return "websearch";
1336
1400
  if (web === "fetch")
1337
1401
  return "webfetch";
1338
1402
  const normalized = normalizedControlName(name);
1339
- return OPENCODE_TOOL_NAMES.has(normalized) ? normalized : undefined;
1403
+ if (normalized === "patch")
1404
+ return "apply_patch";
1405
+ return OPENCODE_NATIVE_TOOL_ID_SET.has(normalized) ? normalized : undefined;
1406
+ }
1407
+ function openCodeToolName(name) {
1408
+ return openCodeNativeToolId(name);
1340
1409
  }
1341
1410
  function openCodePermissionName(name) {
1342
1411
  const normalized = normalizedControlName(name);
@@ -1350,7 +1419,7 @@ function openCodePermissionName(name) {
1350
1419
  if (["bash", "mcp", "doom_loop", "external_directory"].includes(normalized)) {
1351
1420
  return normalized;
1352
1421
  }
1353
- return normalized !== "read" && OPENCODE_TOOL_NAMES.has(normalized)
1422
+ return normalized !== "read" && OPENCODE_NATIVE_TOOL_ID_SET.has(normalized)
1354
1423
  ? normalized
1355
1424
  : undefined;
1356
1425
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-profile-materialize",
3
- "version": "0.5.1",
3
+ "version": "0.6.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
6
  "main": "./dist/index.js",
@@ -16,7 +16,7 @@
16
16
  "dist"
17
17
  ],
18
18
  "dependencies": {
19
- "@tangle-network/agent-interface": "0.30.0"
19
+ "@tangle-network/agent-interface": "0.31.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "25.6.0",