@rubytech/create-maxy-code 0.1.189 → 0.1.191
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/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +16 -1
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +3 -4
- package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-authoring-skill-gate.test.d.ts +2 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-authoring-skill-gate.test.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-authoring-skill-gate.test.js +79 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-authoring-skill-gate.test.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +21 -95
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts +9 -0
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js +47 -0
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js.map +1 -1
- package/payload/platform/plugins/admin/skills/file-presentation/SKILL.md +6 -12
- package/payload/platform/plugins/admin/skills/professional-document/SKILL.md +1 -1
- package/payload/platform/plugins/docs/references/admin-ui.md +17 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.d.ts +9 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.js +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +9 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +2 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +0 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +21 -0
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +113 -2
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
- package/payload/platform/templates/specialists/agents/content-producer.md +2 -2
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/public/assets/{admin-Cst6NkUE.js → admin-DFxXLXfg.js} +53 -53
- package/payload/server/public/assets/{data-COwlCdrl.js → data-DtYcqL1G.js} +1 -1
- package/payload/server/public/assets/{graph-CkUZEqdW.js → graph-C-XPyGsO.js} +1 -1
- package/payload/server/public/assets/graph-labels-CZin3myX.js +1 -0
- package/payload/server/public/assets/page-CKlvbjKn.js +1 -0
- package/payload/server/public/assets/{page-DC2hsMPj.js → page-DSj21hpG.js} +3 -3
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/server.js +750 -312
- package/payload/server/public/assets/graph-labels-Dt1bmq6W.js +0 -1
- package/payload/server/public/assets/page-CGXxYYRR.js +0 -1
package/package.json
CHANGED
|
@@ -459,7 +459,8 @@ FOR (n:LocalBusiness|Service|PriceSpecification|OpeningHoursSpecification|Organi
|
|
|
459
459
|
|EmailAccount
|
|
460
460
|
|Position|Credential|Agent|Idea|Concept
|
|
461
461
|
|VoiceProfile|VoiceEdit|SocialPost|AEOAudit|Page
|
|
462
|
-
|TimelineEvent|CompiledTruthRevision|Report|OrphanCandidate|CitationProposal
|
|
462
|
+
|TimelineEvent|CompiledTruthRevision|Report|OrphanCandidate|CitationProposal
|
|
463
|
+
|FileArtifact)
|
|
463
464
|
ON EACH [n.name, n.firstName, n.lastName, n.givenName, n.familyName,
|
|
464
465
|
n.title, n.currentTitle, n.summary, n.body, n.content, n.text, n.description, n.headline, n.abstract,
|
|
465
466
|
n.email, n.note, n.label, n.value, n.message, n.preview, n.tagline,
|
|
@@ -1724,6 +1725,20 @@ OPTIONS {
|
|
|
1724
1725
|
}
|
|
1725
1726
|
};
|
|
1726
1727
|
|
|
1728
|
+
// Task 514: physical files on disk indexed as :FileArtifact so the /data
|
|
1729
|
+
// search box runs the same hybrid (vector + BM25) retrieval over files that
|
|
1730
|
+
// the agent runs over the graph. discoverIndexes() enrols this at boot; the
|
|
1731
|
+
// file-index reconciler populates f.embedding (768-d nomic-embed-text over
|
|
1732
|
+
// name + extracted content).
|
|
1733
|
+
CREATE VECTOR INDEX file_artifact_embedding IF NOT EXISTS
|
|
1734
|
+
FOR (f:FileArtifact) ON (f.embedding)
|
|
1735
|
+
OPTIONS {
|
|
1736
|
+
indexConfig: {
|
|
1737
|
+
`vector.dimensions`: 768,
|
|
1738
|
+
`vector.similarity_function`: 'cosine'
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
|
|
1727
1742
|
CREATE CONSTRAINT orphan_candidate_id_unique IF NOT EXISTS
|
|
1728
1743
|
FOR (o:OrphanCandidate) REQUIRE o.orphanCandidateId IS UNIQUE;
|
|
1729
1744
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "admin",
|
|
3
|
-
"description": "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume,
|
|
3
|
+
"description": "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, action-approval tools (action-pending, action-approve, action-reject, action-edit), and session-retrospective-mark-complete (the deterministic sentinel the session-end Stop hook reads via JSONL grep to release the retrospective gate; admin calls it once per session after running the three-pass retrospective) for managing the Maxy platform.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: admin
|
|
3
3
|
surface: platform
|
|
4
|
-
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume,
|
|
4
|
+
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, action-approval tools (action-pending, action-approve, action-reject, action-edit), and session-retrospective-mark-complete (the deterministic sentinel the session-end Stop hook reads via JSONL grep to release the retrospective gate; admin calls it once per session after running the three-pass retrospective) for managing the Maxy platform."
|
|
5
5
|
tools:
|
|
6
6
|
- name: system-status
|
|
7
7
|
publicAllowlist: false
|
|
@@ -57,9 +57,6 @@ tools:
|
|
|
57
57
|
- name: session-resume
|
|
58
58
|
publicAllowlist: false
|
|
59
59
|
adminAllowlist: true
|
|
60
|
-
- name: file-attach
|
|
61
|
-
publicAllowlist: false
|
|
62
|
-
adminAllowlist: false
|
|
63
60
|
- name: action-pending
|
|
64
61
|
publicAllowlist: false
|
|
65
62
|
adminAllowlist: true
|
|
@@ -104,6 +101,8 @@ Platform management tools for both the admin and public agents. The `plugin-read
|
|
|
104
101
|
|
|
105
102
|
Tools are available via the `admin` MCP server.
|
|
106
103
|
|
|
104
|
+
**Admin-seat authoring-skill gate (Task 516).** `skill-load` and `plugin-read` refuse to return the body of a content-producer-owned authoring skill — `professional-document`, `a4-print-documents`, `publish-site` — when the caller is the admin seat (`MAXY_SESSION_ROLE=admin` with no `MAXY_SPECIALIST`). The refusal relays one line — "This deliverable is owned by content-producer. Dispatch it with the Agent tool and subagent_type content-producer." — so the admin's next action is the dispatch, not inline authoring. `plugin-read` extracts the skill slug from a `skills/<slug>/…` file path (or a slug buried in `pluginName`), so the raw-SKILL.md read path can't bypass the gate; `PLUGIN.md` and plain `references/*` reads are untouched. content-producer's own spawns carry `MAXY_SPECIALIST=content-producer` and resolve these skills normally (it reaches them via the native `Skill` tool regardless), and the public seat is out of scope. The gate is a pure name+env check before any filesystem read and fails open for any non-admin or unidentified seat — it never bricks a legitimate load. Each block emits `[admin-skill-gate] role=admin skill=<name> decision=block reason=content-producer-owned`. This is the enforcement layer; the IDENTITY.md delegation paragraph documents the intent but is not the gate ([[feedback_deterministic_means_remove_llm]], [[feedback_doctrine_paragraph_is_not_a_gate]]). Skills the admin invokes directly are deliberately excluded: `unzip-attachment` (its SKILL.md says "Invoked by the admin agent directly — admin owns all unzipping", then routes the extracted tree to a specialist) and `deck-pages` (admin-owned, a content-producer refusal target). The set is each skill's own SKILL.md "Invoked by" declaration, not the admin plugin's skill catalogue (which lists these admin-hosted skills for discovery — the catalogue is the inline path this gate closes).
|
|
105
|
+
|
|
107
106
|
**Three-store admin auth invariant.** `admin-add` writes to all three identity stores (`users.json` PIN auth at the persistent `~/{configDir}/users.json` location , `account.json` `admins[]` role, Neo4j `:AdminUser`/`:Person` graph identity) with per-leg `[admin-auth-store]` log lines plus the `[admins-write]` chokepoint line, and returns `is_error: true` on any leg failure naming what's already written. `admin-update-pin` writes `users.json` only and emits the same `[admin-auth-store]` line. **Single chokepoint:** every `users.json` + `account.json admins[]` mutation routes through `platform/lib/admins-write` (`writeAdminEntry` for admin-add/set-pin, `removeAdminFromAccount` for admin-remove); the static check `grep -rnE 'admins\.push\|config\.admins\s*=' platform/ \| grep -v lib/admins-write` returns 0. Direct `Edit`/`Write` on `account.json` is blocked at the `pre-tool-use` hook — mutations go through `account-update`, `plugin-toggle-enabled`, or the `admin-*` tools. **Install + boot invariants:** the installer and admin-server boot walk every account.json admins[] vs users.json; `[install-invariant]` / `[admin-invariant] direction=… userId=<id8> source=<file>` fires per divergence with a `check complete divergences=<n>` summary. Log-only — does not block. See `.docs/agents.md` § "Three-store admin auth invariant" for the full contract.
|
|
108
107
|
|
|
109
108
|
**AdminUser graph stamp + boot self-heal.** The seed cypher in `platform/scripts/seed-neo4j.sh` stamps `:AdminUser.accountId` + `:AdminUser.role='owner'` ON CREATE so the graph-write gate's account-scoped MATCH succeeds on the first user-domain write. Admin-server boot additionally runs `platform/ui/app/lib/adminuser-self-heal.ts` to repair legacy nodes whose `accountId` or `role` is null — emits `[adminuser-self-heal] healed=<N> userId=<8> accountId=<8>` once per boot, idempotent. The `graph-write-gate` reject log gains a `subReason=` field (`no-admin-user-node` / `admin-user-no-accountid` / `no-business-or-personal-profile`) for operator diagnosis. See `.docs/agents.md` § "AdminUser graph stamp contract" for the full contract.
|
package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-authoring-skill-gate.test.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-authoring-skill-gate.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/admin-authoring-skill-gate.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Admin-seat authoring-skill gate (Task 516).
|
|
2
|
+
//
|
|
3
|
+
// Locks the pure decision helpers that make content-producer-owned
|
|
4
|
+
// authoring skills unreachable from the admin seat via `skill-load` and
|
|
5
|
+
// `plugin-read`. The MCP handlers in index.ts are thin glue over these —
|
|
6
|
+
// they emit the dispatch-hint text and the `[admin-skill-gate]` line — so
|
|
7
|
+
// the decision logic is tested here in isolation, mirroring the
|
|
8
|
+
// loadSkill / findSkillOwners test convention (no stdio transport).
|
|
9
|
+
import { describe, it, expect } from "vitest";
|
|
10
|
+
import { CONTENT_PRODUCER_AUTHORING_SKILLS, isAdminSeat, skillSlugFromReadArgs, adminAuthoringSkillBlock, } from "../skill-resolution.js";
|
|
11
|
+
const ADMIN = { MAXY_SESSION_ROLE: "admin", MAXY_SPECIALIST: "" };
|
|
12
|
+
const CONTENT_PRODUCER = { MAXY_SESSION_ROLE: "admin", MAXY_SPECIALIST: "content-producer" };
|
|
13
|
+
const PUBLIC = { MAXY_SESSION_ROLE: "public", MAXY_SPECIALIST: "" };
|
|
14
|
+
describe("isAdminSeat", () => {
|
|
15
|
+
it("true only when role is admin and no specialist is set", () => {
|
|
16
|
+
expect(isAdminSeat(ADMIN)).toBe(true);
|
|
17
|
+
expect(isAdminSeat({ MAXY_SESSION_ROLE: "admin" })).toBe(true); // specialist undefined
|
|
18
|
+
});
|
|
19
|
+
it("false for a specialist seat even when role is admin", () => {
|
|
20
|
+
expect(isAdminSeat(CONTENT_PRODUCER)).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
it("false for the public seat", () => {
|
|
23
|
+
expect(isAdminSeat(PUBLIC)).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
it("false when role is unknown (fail-open — never block an unidentified seat)", () => {
|
|
26
|
+
expect(isAdminSeat({})).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("CONTENT_PRODUCER_AUTHORING_SKILLS", () => {
|
|
30
|
+
it("is exactly the three content-producer-invoked authoring/render/publish skills", () => {
|
|
31
|
+
expect([...CONTENT_PRODUCER_AUTHORING_SKILLS].sort()).toEqual(["a4-print-documents", "professional-document", "publish-site"].sort());
|
|
32
|
+
});
|
|
33
|
+
it("excludes admin-invoked skills (unzip-attachment, deck-pages)", () => {
|
|
34
|
+
// unzip-attachment SKILL.md: "Invoked by the admin agent directly".
|
|
35
|
+
// deck-pages is admin-owned (a content-producer refusal target).
|
|
36
|
+
expect(CONTENT_PRODUCER_AUTHORING_SKILLS.has("unzip-attachment")).toBe(false);
|
|
37
|
+
expect(CONTENT_PRODUCER_AUTHORING_SKILLS.has("deck-pages")).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe("skillSlugFromReadArgs", () => {
|
|
41
|
+
it("extracts the slug from a skills/<slug>/SKILL.md file path", () => {
|
|
42
|
+
expect(skillSlugFromReadArgs("admin", "skills/professional-document/SKILL.md")).toBe("professional-document");
|
|
43
|
+
});
|
|
44
|
+
it("extracts the slug from a reference under the skill dir", () => {
|
|
45
|
+
expect(skillSlugFromReadArgs("admin", "skills/professional-document/references/x.md")).toBe("professional-document");
|
|
46
|
+
});
|
|
47
|
+
it("extracts the slug when buried in pluginName", () => {
|
|
48
|
+
expect(skillSlugFromReadArgs("admin/skills/publish-site", "PLUGIN.md")).toBe("publish-site");
|
|
49
|
+
});
|
|
50
|
+
it("returns undefined for PLUGIN.md and plain reference reads", () => {
|
|
51
|
+
expect(skillSlugFromReadArgs("admin", "PLUGIN.md")).toBeUndefined();
|
|
52
|
+
expect(skillSlugFromReadArgs("sales", "references/pricing.md")).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe("adminAuthoringSkillBlock", () => {
|
|
56
|
+
it("blocks every content-producer-owned authoring skill from the admin seat", () => {
|
|
57
|
+
for (const slug of CONTENT_PRODUCER_AUTHORING_SKILLS) {
|
|
58
|
+
expect(adminAuthoringSkillBlock(slug, ADMIN)).toBe(true);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
it("does not block the admin's own / admin-invoked skills", () => {
|
|
62
|
+
// unzip-attachment is admin-invoked (admin unzips then routes); deck-pages
|
|
63
|
+
// is admin-owned. plainly/voice-mirror/datetime are admin operational skills.
|
|
64
|
+
for (const slug of ["plainly", "voice-mirror", "datetime", "deck-pages", "unzip-attachment"]) {
|
|
65
|
+
expect(adminAuthoringSkillBlock(slug, ADMIN)).toBe(false);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
it("never blocks the content-producer seat (it owns these skills)", () => {
|
|
69
|
+
expect(adminAuthoringSkillBlock("professional-document", CONTENT_PRODUCER)).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
it("never blocks the public seat (out of scope)", () => {
|
|
72
|
+
expect(adminAuthoringSkillBlock("professional-document", PUBLIC)).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
it("fail-open on an undefined slug or unidentified seat", () => {
|
|
75
|
+
expect(adminAuthoringSkillBlock(undefined, ADMIN)).toBe(false);
|
|
76
|
+
expect(adminAuthoringSkillBlock("professional-document", {})).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
//# sourceMappingURL=admin-authoring-skill-gate.test.js.map
|
package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-authoring-skill-gate.test.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-authoring-skill-gate.test.js","sourceRoot":"","sources":["../../src/__tests__/admin-authoring-skill-gate.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,7 +18,7 @@ import { createConnection } from "node:net";
|
|
|
18
18
|
import { homedir, hostname as osHostname } from "node:os";
|
|
19
19
|
import QRCode from "qrcode";
|
|
20
20
|
import { getSession, closeDriver } from "./lib/neo4j.js";
|
|
21
|
-
import { findSkillOwners, computePluginReadHint, loadSkill, parseRequiredInputs } from "./skill-resolution.js";
|
|
21
|
+
import { findSkillOwners, computePluginReadHint, loadSkill, parseRequiredInputs, adminAuthoringSkillBlock, skillSlugFromReadArgs, AUTHORING_DISPATCH_HINT } from "./skill-resolution.js";
|
|
22
22
|
import { resolvePublicHostname } from "./lib/public-hostname.js";
|
|
23
23
|
import { publishSite } from "./tools/publish-site.js";
|
|
24
24
|
function refusalGuidance(kind) {
|
|
@@ -1668,6 +1668,16 @@ eagerTool(server, "plugin-read", "Read a plugin definition (PLUGIN.md) or one of
|
|
|
1668
1668
|
return refuseNoAccount("plugin-read");
|
|
1669
1669
|
try {
|
|
1670
1670
|
const resolvedFile = file ?? "PLUGIN.md";
|
|
1671
|
+
// Task 516 — plugin-read is the raw-SKILL.md read path that would
|
|
1672
|
+
// otherwise bypass the skill-load authoring gate. Block the admin seat
|
|
1673
|
+
// from reading any content-producer-owned authoring skill's files
|
|
1674
|
+
// (SKILL.md or its references); the slug is extracted from the file
|
|
1675
|
+
// path or pluginName. Same fail-open contract as skill-load.
|
|
1676
|
+
const authoringSlug = skillSlugFromReadArgs(pluginName, resolvedFile);
|
|
1677
|
+
if (adminAuthoringSkillBlock(authoringSlug, process.env)) {
|
|
1678
|
+
console.log(`[admin-skill-gate] role=admin skill=${authoringSlug} decision=block reason=content-producer-owned`);
|
|
1679
|
+
return { content: [{ type: "text", text: AUTHORING_DISPATCH_HINT }], isError: true };
|
|
1680
|
+
}
|
|
1671
1681
|
const pluginPath = resolve(PLATFORM_ROOT, "plugins", pluginName, resolvedFile);
|
|
1672
1682
|
if (!existsSync(pluginPath)) {
|
|
1673
1683
|
// when the agent's pluginName/file combination misses
|
|
@@ -1796,6 +1806,15 @@ eagerTool(server, "skill-load", "Load a plugin skill's SKILL.md body by skill na
|
|
|
1796
1806
|
}, async ({ skillName }) => {
|
|
1797
1807
|
if (!ACCOUNT_ID)
|
|
1798
1808
|
return refuseNoAccount("skill-load");
|
|
1809
|
+
// Task 516 — the admin seat may not load content-producer-owned
|
|
1810
|
+
// authoring skills inline; it must dispatch content-producer instead.
|
|
1811
|
+
// The gate is a pure name+env check before any filesystem read, and
|
|
1812
|
+
// fails open for non-admin / unidentified seats (content-producer's own
|
|
1813
|
+
// spawns carry MAXY_SPECIALIST and resolve normally).
|
|
1814
|
+
if (adminAuthoringSkillBlock(skillName, process.env)) {
|
|
1815
|
+
console.log(`[admin-skill-gate] role=admin skill=${skillName} decision=block reason=content-producer-owned`);
|
|
1816
|
+
return { content: [{ type: "text", text: AUTHORING_DISPATCH_HINT }], isError: true };
|
|
1817
|
+
}
|
|
1799
1818
|
const start = Date.now();
|
|
1800
1819
|
try {
|
|
1801
1820
|
const outcome = await loadSkill(PLATFORM_ROOT, skillName);
|
|
@@ -1855,7 +1874,7 @@ eagerTool(server, "skill-load", "Load a plugin skill's SKILL.md body by skill na
|
|
|
1855
1874
|
// sentinel that read like a successful render to the agent while the
|
|
1856
1875
|
// chat surface saw nothing. Admin flows now use plain markdown for
|
|
1857
1876
|
// reviewable artefacts and the `file-presentation` skill for downloadable
|
|
1858
|
-
// artefacts (
|
|
1877
|
+
// artefacts (delivered via `SendUserFile`, the native Claude Code surface).
|
|
1859
1878
|
// The :Component / :KnowledgeDocument
|
|
1860
1879
|
// persistence pipeline is preserved for historical conversations that
|
|
1861
1880
|
// already carry the rows; no new conversations write to it.
|
|
@@ -2551,99 +2570,6 @@ server.tool("premium-deliver", "Deliver a purchased premium plugin. Copies plugi
|
|
|
2551
2570
|
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
2552
2571
|
};
|
|
2553
2572
|
});
|
|
2554
|
-
// ---------------------------------------------------------------------------
|
|
2555
|
-
// file-attach — copy a generated file into the attachment store for download
|
|
2556
|
-
// ---------------------------------------------------------------------------
|
|
2557
|
-
eagerTool(server, "file-attach", "Attach a file from the account directory for delivery to the user on non-PTY chat " +
|
|
2558
|
-
"surfaces (web chat, WhatsApp) where the platform UI mediates attachment rendering. " +
|
|
2559
|
-
"On the native Claude Code PTY surface, `SendUserFile` is the delivery — `file-attach` " +
|
|
2560
|
-
"returns an `attachmentId` envelope only and renders no chip there. Tool selection is " +
|
|
2561
|
-
"by inventory: see the `file-presentation` skill. Copies the file into the attachment " +
|
|
2562
|
-
"store and returns metadata the chat surface uses to present the download. The file " +
|
|
2563
|
-
"must be within the account directory tree. The returned metadata is the operator-visible " +
|
|
2564
|
-
"record of the attachment; quote it back to the user in plain text so they have the " +
|
|
2565
|
-
"filename and size on screen.", {
|
|
2566
|
-
filePath: z.string().describe("Absolute path to the file to attach"),
|
|
2567
|
-
}, async ({ filePath }) => {
|
|
2568
|
-
if (!ACCOUNT_ID)
|
|
2569
|
-
return refuseNoAccount("file-attach");
|
|
2570
|
-
const TAG = "[file-attach]";
|
|
2571
|
-
if (!filePath || !filePath.trim()) {
|
|
2572
|
-
return {
|
|
2573
|
-
content: [{ type: "text", text: `${TAG} filePath is required` }],
|
|
2574
|
-
isError: true,
|
|
2575
|
-
};
|
|
2576
|
-
}
|
|
2577
|
-
const accountDir = getAccountDir();
|
|
2578
|
-
// --- Path validation: must be within the account directory ---
|
|
2579
|
-
let resolvedPath;
|
|
2580
|
-
try {
|
|
2581
|
-
const { realpathSync: rp } = await import("node:fs");
|
|
2582
|
-
resolvedPath = rp(filePath);
|
|
2583
|
-
const accountResolved = rp(accountDir);
|
|
2584
|
-
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
2585
|
-
const sanitised = filePath.replace(accountDir, "<account>/");
|
|
2586
|
-
console.error(`${TAG} REJECTED path=${sanitised} reason=outside_account_directory`);
|
|
2587
|
-
return {
|
|
2588
|
-
content: [{ type: "text", text: `${TAG} Access denied: file is outside the account directory.` }],
|
|
2589
|
-
isError: true,
|
|
2590
|
-
};
|
|
2591
|
-
}
|
|
2592
|
-
}
|
|
2593
|
-
catch (err) {
|
|
2594
|
-
const code = err.code;
|
|
2595
|
-
if (code === "ENOENT") {
|
|
2596
|
-
console.error(`${TAG} ENOENT path=${filePath}`);
|
|
2597
|
-
return {
|
|
2598
|
-
content: [{ type: "text", text: `${TAG} File not found: ${filePath}` }],
|
|
2599
|
-
isError: true,
|
|
2600
|
-
};
|
|
2601
|
-
}
|
|
2602
|
-
console.error(`${TAG} path validation error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2603
|
-
return {
|
|
2604
|
-
content: [{ type: "text", text: `${TAG} Failed to validate file path: ${err instanceof Error ? err.message : String(err)}` }],
|
|
2605
|
-
isError: true,
|
|
2606
|
-
};
|
|
2607
|
-
}
|
|
2608
|
-
// --- Store in attachment store ---
|
|
2609
|
-
const targetUrl = `http://127.0.0.1:${PLATFORM_PORT}/api/admin/file-attach`;
|
|
2610
|
-
try {
|
|
2611
|
-
const res = await fetch(targetUrl, {
|
|
2612
|
-
method: "POST",
|
|
2613
|
-
headers: { "Content-Type": "application/json" },
|
|
2614
|
-
body: JSON.stringify({ filePath: resolvedPath, accountId: ACCOUNT_ID }),
|
|
2615
|
-
});
|
|
2616
|
-
const result = await res.json();
|
|
2617
|
-
if (!res.ok || result.error) {
|
|
2618
|
-
const msg = result.error || `HTTP ${res.status}`;
|
|
2619
|
-
console.error(`${TAG} store failed url=${targetUrl} status=${res.status} error=${msg}`);
|
|
2620
|
-
return {
|
|
2621
|
-
content: [{ type: "text", text: `${TAG} Failed to attach file: ${msg} (url=${targetUrl})` }],
|
|
2622
|
-
isError: true,
|
|
2623
|
-
};
|
|
2624
|
-
}
|
|
2625
|
-
console.error(`${TAG} attached path=${resolvedPath} size=${result.sizeBytes} mime=${result.mimeType} id=${result.attachmentId}`);
|
|
2626
|
-
return {
|
|
2627
|
-
content: [{
|
|
2628
|
-
type: "text",
|
|
2629
|
-
text: JSON.stringify({
|
|
2630
|
-
attachmentId: result.attachmentId,
|
|
2631
|
-
filename: result.filename,
|
|
2632
|
-
sizeBytes: result.sizeBytes,
|
|
2633
|
-
mimeType: result.mimeType,
|
|
2634
|
-
}),
|
|
2635
|
-
}],
|
|
2636
|
-
};
|
|
2637
|
-
}
|
|
2638
|
-
catch (err) {
|
|
2639
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2640
|
-
console.error(`${TAG} fetch failed url=${targetUrl} error=${errMsg}`);
|
|
2641
|
-
return {
|
|
2642
|
-
content: [{ type: "text", text: `${TAG} Failed to attach file: could not reach platform server at ${targetUrl} (${errMsg})` }],
|
|
2643
|
-
isError: true,
|
|
2644
|
-
};
|
|
2645
|
-
}
|
|
2646
|
-
});
|
|
2647
2573
|
// ===================================================================
|
|
2648
2574
|
// Action approval tools (EU AI Act Article 14 — human oversight)
|
|
2649
2575
|
//
|