From cc67410a4979cab48aabf8cf1d5fa775f20956b8 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 4 Sep 2026 00:24:21 +0000 Subject: [PATCH] feat(webmcp): expose EPAC selection display --- .../skill-registry.last-known-good.json | 12 +++++++-- src/assets/js/webmcp-registry.js | 9 ++++--- src/assets/js/webmcp.js | 2 +- src/webmcp/index.njk | 26 ++++++++++++++++--- tests/webmcp.test.mjs | 23 ++++++++++++---- 5 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/_data/snapshots/skill-registry.last-known-good.json b/src/_data/snapshots/skill-registry.last-known-good.json index 3c8d8f1..77c7755 100644 --- a/src/_data/snapshots/skill-registry.last-known-good.json +++ b/src/_data/snapshots/skill-registry.last-known-good.json @@ -2,9 +2,9 @@ "version": 1, "source": { "repository": "The-Interdependency/skill-lib", - "commit": "9353288221c29891860f0cd4568d5749de97ff78", + "commit": "22741dbad85c630e5e3239317a3ff47a630f0289", "path": "skills.json", - "sha256": "da69c1a92e74ced99687726bf8dcdcc90286b1bed081a096c0561f6b65de60b1" + "sha256": "9b1ccc7079c5d1af03230e6d63dced93a01a819a42bfb70ececf95589d6162b0" }, "install_path": ".agents/skills//", "superseded_skills": [ @@ -207,6 +207,14 @@ "description": "Fail-closed rubric for comparing, retaining, rejecting, deprecating, and selecting UCNS options within an explicit scope. Load this when an agent asks which UCNS candidate should win, whether evidence authorizes selection, how an option moves from registered or implemented to selected, how to compare competing gonol constructors, carriers, geometries, policies, projections, or measurement candidates, or how to issue a scoped UCNS decision receipt. Do not load merely to register options, execute one already-selected option, or choose ordinary UI preferences. Never select universal UCNS canon by score, familiarity, implementation order, or EDCM-local evidence.", "human_title": "Fail-closed rubric for comparing, retaining, rejecting, deprecating, and selecting UCNS options within an explicit scope" }, + { + "name": "epac-selection-display", + "path": "epac-selection-display/SKILL.md", + "kind": "procedural", + "depends_on": [], + "description": "Evidence-bound EPAC target selection and display for WebMCP handoffs and other human-facing surfaces. Load this when choosing an EPAC element, molecule, construction receipt, comparison result, or available visualization to present; when preparing a receipt-backed EPAC display packet; or when exposing the EPAC workflow as a selectable WebMCP skill. Do not load to select EPAC or a UCNS candidate as canon, invent missing geometry, or make unrelated WebMCP catalogue changes.", + "human_title": "Evidence-bound EPAC target selection and display for WebMCP handoffs and other human-facing surfaces" + }, { "name": "the-interdependency", "path": "the-interdependency/SKILL.md", diff --git a/src/assets/js/webmcp-registry.js b/src/assets/js/webmcp-registry.js index 8dff900..4388df4 100644 --- a/src/assets/js/webmcp-registry.js +++ b/src/assets/js/webmcp-registry.js @@ -14,7 +14,7 @@ // === CONTRACTS === // id: webmcp_public_catalogue_matches_human_catalogue // given: the source registry contains public-facing and internal/specialist skills -// then: public list/find/inspect/closure expose msdmd metadata-block applications, the exact meta skill, and fresh-making, matching the human card catalogue +// then: public list/find/inspect/closure expose msdmd metadata-block applications, the exact meta skill, fresh-making, and epac-selection-display, matching the human card catalogue // class: correctness // // id: webmcp_registry_smallest_dependency_closure @@ -29,7 +29,10 @@ function normalizeText(value) { } function isPresentedSkill(skill) { - return skill?.kind === 'metadata-block' || skill?.name === 'meta' || skill?.name === 'fresh-making'; + return skill?.kind === 'metadata-block' + || skill?.name === 'meta' + || skill?.name === 'fresh-making' + || skill?.name === 'epac-selection-display'; } export function humanTitle(description) { @@ -139,7 +142,7 @@ export function createSkillRegistry(registryData) { registry_version: registryData.version, skill_count: skills.length, source_skill_count: registryData.skills.length, - public_scope: 'metadata-block plus meta plus fresh-making', + public_scope: 'metadata-block plus meta plus fresh-making plus epac-selection-display', source: { ...source }, fallback: Boolean(registryData.fallback), hmmm: Array.isArray(registryData.hmmm) ? [...registryData.hmmm] : [] diff --git a/src/assets/js/webmcp.js b/src/assets/js/webmcp.js index 210b099..973ea99 100644 --- a/src/assets/js/webmcp.js +++ b/src/assets/js/webmcp.js @@ -419,7 +419,7 @@ export async function registerInterdependencyWebMCP() { await registerTool({ name: 'tiw_list_skills', title: 'List public Interdependency skills', - description: 'List the same curated public skill set shown to the human: msdmd metadata-block applications, METAPAT meta, and fresh-making.', + description: 'List the same curated public skill set shown to the human: msdmd metadata-block applications, METAPAT meta, fresh-making, and evidence-bound EPAC selection/display.', inputSchema: { type: 'object', properties: { kind: { type: 'string' } }, additionalProperties: false }, annotations: { readOnlyHint: true, untrustedContentHint: false }, execute: async input => jsonResult(registry.listSkills(input)) diff --git a/src/webmcp/index.njk b/src/webmcp/index.njk index 7709ba0..10e069c 100644 --- a/src/webmcp/index.njk +++ b/src/webmcp/index.njk @@ -50,11 +50,11 @@ webmcp: true

Shared human + agent skill material

2. Choose how the agent should work

Choose a repository first to enable its agent-work choices.

-

The public skill surface is deliberately narrow: the metadata skills built on the module self-declared metadata convention, the METAPAT consultation router, and deterministic refresh of derived artifacts. The remaining skills stay canonical in the source library rather than becoming competing definitions here.

+

The public skill surface is deliberately narrow: the metadata skills built on the module self-declared metadata convention, the METAPAT consultation router, deterministic refresh of derived artifacts, and receipt-backed EPAC selection/display. The remaining skills stay canonical in the source library rather than becoming competing definitions here.

- + Presented skills shown
@@ -112,6 +112,24 @@ webmcp: true {% endfor %} +

EPAC display

+
+ {% for skill in generated.skillRegistry.skills %} + {% if skill.name == "epac-selection-display" %} + + {% endif %} + {% endfor %} +
+
Selected skill No skill selected. @@ -146,7 +164,7 @@ human chooses repository + reads the applicable skill agent receives the exact repository, skill set, and human request ↓ agent's separately authorized change tools -

For the refresh skill, choosing it automatically supplies the standard make-fresh request; the human may edit it. The browser handoff itself remains read-only and page-session-only. Actual repository changes require the agent's separately authorized repository, filesystem, deployment, or other tools.

+

For the refresh skill, choosing it automatically supplies the standard make-fresh request; the human may edit it. For EPAC selection/display, the human names the target and representation; the server passes that request but does not construct or render the research artifact. The browser handoff itself remains read-only and page-session-only. Actual repository changes require the agent's separately authorized repository, filesystem, deployment, or other tools.

-
hmmmRepository observation identifies the target; it does not authenticate the target, grant write authority, or prove that every declared freshness executor is available to the agent.
+
hmmmRepository observation identifies the target; it does not authenticate the target, grant write authority, prove that every declared freshness executor is available, or prove that a selected EPAC source contains a committed renderer.
diff --git a/tests/webmcp.test.mjs b/tests/webmcp.test.mjs index 7f62793..131a498 100644 --- a/tests/webmcp.test.mjs +++ b/tests/webmcp.test.mjs @@ -17,6 +17,7 @@ const sourceRegistry = JSON.stringify({ { name: 'cap-build', path: 'cap-build/SKILL.md', kind: 'metadata-block', depends_on: ['msdmd'], description: 'capability inventory' }, { name: 'meta', path: 'meta/SKILL.md', kind: 'procedural', description: 'METAPAT consultation router' }, { name: 'fresh-making', path: 'fresh-making/SKILL.md', kind: 'procedural', description: 'deterministic restoration of derived artifact consistency' }, + { name: 'epac-selection-display', path: 'epac-selection-display/SKILL.md', kind: 'procedural', description: 'receipt-backed EPAC element and molecule selection and display' }, { name: 'repo-audit-repair', path: 'repo-audit-repair/SKILL.md', kind: 'procedural', description: 'audit and repair a repository' } ] }); @@ -63,20 +64,28 @@ test('committed or refreshed fallback snapshot is exact, usable, and retains sou assert.ok(snapshot.skills.some(skill => skill.name === 'repo-audit-repair')); }); -test('public registry presents msdmd, meta, and fresh-making while hiding unrelated specialist skills', () => { +test('public registry presents msdmd, meta, fresh-making, and EPAC display while hiding unrelated specialist skills', () => { const registry = createSkillRegistry(sampleProjection()); - assert.deepEqual(registry.listSkills().map(skill => skill.name), ['msdmd', 'cap-build', 'meta', 'fresh-making']); + assert.deepEqual( + registry.listSkills().map(skill => skill.name), + ['msdmd', 'cap-build', 'meta', 'fresh-making', 'epac-selection-display'] + ); assert.equal(registry.findSkills({ query: 'audit repository' }).length, 0); assert.equal(registry.findSkills({ query: 'capability' })[0].name, 'cap-build'); assert.equal(registry.findSkills({ query: 'derived artifact' })[0].name, 'fresh-making'); + assert.equal(registry.findSkills({ query: 'EPAC molecule' })[0].name, 'epac-selection-display'); assert.throws(() => registry.inspectSkill({ name: 'repo-audit-repair' }), /unknown public skill/); assert.deepEqual(registry.resolveSkillClosure({ name: 'cap-build' }).map(skill => skill.name), ['msdmd', 'cap-build']); assert.deepEqual(registry.resolveSkillClosure({ name: 'fresh-making' }).map(skill => skill.name), ['fresh-making']); + assert.deepEqual( + registry.resolveSkillClosure({ name: 'epac-selection-display' }).map(skill => skill.name), + ['epac-selection-display'] + ); const status = registry.getRegistryStatus(); - assert.equal(status.skill_count, 4); - assert.equal(status.source_skill_count, 5); - assert.equal(status.public_scope, 'metadata-block plus meta plus fresh-making'); + assert.equal(status.skill_count, 5); + assert.equal(status.source_skill_count, 6); + assert.equal(status.public_scope, 'metadata-block plus meta plus fresh-making plus epac-selection-display'); }); test('WebMCP provider registers five base registry tools and one explicit dynamic browser handoff', async () => { @@ -98,6 +107,7 @@ test('WebMCP provider registers five base registry tools and one explicit dynami assert.match(source, /globalThis\.document\?\.modelContext/); assert.doesNotMatch(source, /navigator\?\.modelContext|provideContext/); assert.doesNotMatch(source, /\/handoff\/|remote_session|writeKey|install_skill|propagate_skill|update_file|create_file/); + assert.doesNotMatch(source, /construct_element_gonol|construct_molecule|epac_public_gonol/); }); test('human selection carries exact skill and repository identity without typed machine identifiers', async () => { @@ -145,6 +155,9 @@ test('dedicated WebMCP route presents collapsible skill cards, every generated r assert.match(page, /skill\.kind == "metadata-block"/); assert.match(page, /skill\.name == "meta"/); assert.match(page, /skill\.name == "fresh-making"/); + assert.match(page, /skill\.name == "epac-selection-display"/); + assert.match(page, /data-skill-group="epac"/); + assert.match(page, /Select and display EPAC/); assert.match(page, /
/); assert.match(page, /Description<\/summary>/); assert.match(page, /generated\.repos\.repositories/);