Compare commits

...

3 Commits

Author SHA1 Message Date
chaim bd1d484e74 fix(2.10.2): AfterInstall must be a no-namespace class
EspoCRM's extension installer (ExtensionManager.php → Base.php:397) does
require_once on scripts/AfterInstall.php and then `new AfterInstall()`
without any namespace prefix. The 2.10.0/2.10.1 version had
`namespace Espo\Modules\SmartAssistant\Scripts;` which resolved at
install time to "Class AfterInstall not found" and aborted the install
with HTTP 500 from KlearBrandingExtension/action/install.

Match the pattern used by GoogleIntegration/scripts/AfterInstall.php:
plain top-level class, no namespace, optional `use` for the Container
type hint. Same behavior (idempotent seed of 7 AssistantPrompt rows
with skipAll), just loadable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 09:36:42 +00:00
chaim d266380e6d fix(2.10.1): CaseMemory Controller + correct layout path
Two UI-breaking issues discovered after 2.10.0 hot-patch:

* Hitting /#CaseMemory in the navbar returned a 404 because CaseMemory
  was missing a Controller class. The entity used to be accessed only
  through Case relation panels and SmartAssistant action endpoints, so
  the bare REST surface was never wired. Same one-liner fix pattern as
  the AssistantRule controller in 2.9.2.

* Layouts were placed under Resources/metadata/layouts/... but EspoCRM
  expects them at Resources/layouts/... (without the metadata/ prefix).
  Result: every new entity rendered only one column / one row in list
  views because EspoCRM fell back to a generic default layout. Moved
  AssistantPrompt, AssistantRule, AssistantSkill, CaseMemory, UserProfile
  layouts to the canonical path. Verified all five list endpoints now
  serve the configured columns.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 09:12:48 +00:00
chaim 1d200fc3f6 fix(installer): seed AssistantPrompt rows with skipAll to bypass beforeSave user-service requirement
The first prod install with v2.10.0 failed silently because saveEntity
invokes beforeSave hooks that require a 'user' service which isn't bound
during the script. Pass skipAll=true so the seed is purely a data insert.

Verified on prod 2026-05-27: all 7 default prompts seeded successfully.
2026-05-27 08:52:55 +00:00
15 changed files with 38 additions and 25 deletions
@@ -0,0 +1,17 @@
<?php
namespace Espo\Modules\SmartAssistant\Controllers;
/**
* Standard CRUD over /api/v1/CaseMemory.
*
* Until SmartAssistant 2.10.0 the CaseMemory entity was created via
* CaseMemoryService::createMemory and listed via Case relation panels —
* never via the bare REST endpoint. Flipping `tab: true` exposes the
* navbar list view, which uses GET /api/v1/CaseMemory and needs this
* Controller class to exist (otherwise EspoCRM returns 404 "Controller
* does not exist"). Same pattern as the AssistantRule fix shipped in 2.9.2.
*/
class CaseMemory extends \Espo\Core\Controllers\Record
{
}
+1 -1
View File
@@ -3,7 +3,7 @@
"module": "SmartAssistant", "module": "SmartAssistant",
"description": "Unified AI Assistant for Legal CRM — floating chat with case memory, office alerts, and AI Gateway integration", "description": "Unified AI Assistant for Legal CRM — floating chat with case memory, office alerts, and AI Gateway integration",
"author": "klear", "author": "klear",
"version": "2.10.0", "version": "2.10.2",
"acceptableVersions": [ "acceptableVersions": [
">=8.0.0" ">=8.0.0"
], ],
+19 -23
View File
@@ -1,16 +1,19 @@
<?php <?php
/**
namespace Espo\Modules\SmartAssistant\Scripts; * AfterInstall script for SmartAssistant extension 2.10.x.
*
* Idempotently seeds the AssistantPrompt entity with 7 default prompt
* sections (tool_rules, writing_style, legal_assistance, personality_case,
* personality_office, other_rules_case, other_rules_office). If a row with
* the same `key` already exists it is left alone — admin edits win.
*
* Class must be named `AfterInstall` with NO namespace — that's what the
* EspoCRM extension installer looks for in scripts/AfterInstall.php.
* See application/Espo/Core/Upgrades/ExtensionManager.php scriptNames map.
*/
use Espo\Core\Container; use Espo\Core\Container;
/**
* Runs after the extension is installed/upgraded.
*
* Idempotent: only inserts AssistantPrompt rows whose `key` doesn't exist yet.
* If the row exists (even with edits), we leave it alone — the user's
* customizations win.
*/
class AfterInstall class AfterInstall
{ {
public function run(Container $container, $params = null): void public function run(Container $container, $params = null): void
@@ -18,21 +21,11 @@ class AfterInstall
$entityManager = $container->get('entityManager'); $entityManager = $container->get('entityManager');
$log = $container->get('log'); $log = $container->get('log');
$resourceFile = __DIR__ . '/../files/custom/Espo/Modules/SmartAssistant/Resources/data/default-prompts.json'; // The JSON file lives inside the installed module resources at runtime.
if (!is_file($resourceFile)) {
// Fall back to the module Resources path when the extension is
// installed (Resources/ is the canonical location at runtime).
$resourceFile = dirname(__DIR__) . '/files/custom/Espo/Modules/SmartAssistant/Resources/data/default-prompts.json';
}
if (!is_file($resourceFile)) {
// Final fallback: the module's installed Resources path.
$resourceFile = 'custom/Espo/Modules/SmartAssistant/Resources/data/default-prompts.json'; $resourceFile = 'custom/Espo/Modules/SmartAssistant/Resources/data/default-prompts.json';
}
if (!is_file($resourceFile)) { if (!is_file($resourceFile)) {
$log->warning("[SmartAssistant] AfterInstall: default-prompts.json not found, skipping seed"); $log->warning("[SmartAssistant] AfterInstall: $resourceFile not found, skipping seed");
return; return;
} }
@@ -40,7 +33,7 @@ class AfterInstall
$defaults = json_decode($json, true); $defaults = json_decode($json, true);
if (!is_array($defaults)) { if (!is_array($defaults)) {
$log->warning("[SmartAssistant] AfterInstall: default-prompts.json invalid JSON"); $log->warning("[SmartAssistant] AfterInstall: invalid JSON in $resourceFile");
return; return;
} }
@@ -72,7 +65,10 @@ class AfterInstall
'content' => $row['content'] ?? '', 'content' => $row['content'] ?? '',
'isActive' => true, 'isActive' => true,
]); ]);
$entityManager->saveEntity($entity); // skipAll bypasses beforeSave hooks that require a logged-in user
// service (installer runs without an HTTP session) and ACL checks
// (we're seeding firm-wide defaults; no user is implicated).
$entityManager->saveEntity($entity, ['skipAll' => true]);
$created++; $created++;
} }