This repository has been archived on 2026-07-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
SmartAssistant/scripts/AfterInstall.php
T
chaim 65faf99e11 feat(2.10.0): Shira management UI — prompts, rules, memory, skills in EspoCRM
Make every text artifact that goes into Shira's system prompt editable from
the EspoCRM admin UI, with code-level defaults kept as a safety floor.

* AssistantRule + CaseMemory: flip `tab: true` so admins can browse/edit from
  the navbar. No longer invisible.

* New entity AssistantPrompt — one row per prompt section (tool_rules,
  writing_style, legal_assistance, personality_case, personality_office,
  other_rules_case, other_rules_office). Edits in the UI override the
  in-code defaults; an inactive or missing row falls back to code.
  Seeded on install with the current Python defaults via AfterInstall.

* New entity UserProfile — replaces /opt/data/profiles/{uid}.md. ACL is
  own/all/no so each lawyer sees their own, admins see all. Injected into
  the prompt's '=== ABOUT THIS USER ===' block.

* New entity AssistantSkill — replaces /opt/data/skills/. Tab-visible.
  Context provider returns metadata only; full body fetched on demand.

* 3 new ContextProviders (AssistantPrompt, UserProfile, AssistantSkill)
  injected into context by SmartAssistantService::chat at the same point
  AssistantRule has been injecting since 2026-05-27.

* 3 new Controller classes (one-liner extends Record) — without these
  EspoCRM returns 404 on the REST endpoint even when scopes/entityDefs
  are correct. Same pattern as the AssistantRule fix in 2.9.2.

* New custom view smart-assistant:views/fields/prompt-editor — monospace
  font, dir=auto for mixed RTL/LTR, taller textarea. Used by all three
  long-text content fields.

* New admin panel 'ניהול שירה' that groups all five surfaces (prompts,
  rules, skills, user profiles, case memory) under one Admin section.

* Hebrew labels for all new scopes added to fa_IR/Global.json.

Paired with shira-hermes (separate commit): prompt_builder.py refactored
to read each section from context['assistantPrompts'] with code defaults
as fallback; save_memory tool now goes through SmartAssistant/action/executeTool
so CaseMemory actually gets populated instead of dropping 'm Notes.

Migration script scripts/migrate_legacy_memory_notes.py in shira-hermes
moves existing 'm Notes into CaseMemory rows (one-shot, idempotent).

Refs Task Master none — schema additions, no destructive changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 08:33:49 +00:00

82 lines
2.7 KiB
PHP

<?php
namespace Espo\Modules\SmartAssistant\Scripts;
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
{
public function run(Container $container, $params = null): void
{
$entityManager = $container->get('entityManager');
$log = $container->get('log');
$resourceFile = __DIR__ . '/../files/custom/Espo/Modules/SmartAssistant/Resources/data/default-prompts.json';
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';
}
if (!is_file($resourceFile)) {
$log->warning("[SmartAssistant] AfterInstall: default-prompts.json not found, skipping seed");
return;
}
$json = file_get_contents($resourceFile);
$defaults = json_decode($json, true);
if (!is_array($defaults)) {
$log->warning("[SmartAssistant] AfterInstall: default-prompts.json invalid JSON");
return;
}
$created = 0;
$skipped = 0;
foreach ($defaults as $row) {
$key = $row['key'] ?? null;
if (!$key) {
continue;
}
$existing = $entityManager->getRDBRepository('AssistantPrompt')
->where(['key' => $key, 'deleted' => false])
->findOne();
if ($existing) {
$skipped++;
continue;
}
$entity = $entityManager->getNewEntity('AssistantPrompt');
$entity->set([
'key' => $key,
'name' => $row['name'] ?? $key,
'description' => $row['description'] ?? null,
'mode' => $row['mode'] ?? 'both',
'displayOrder' => $row['displayOrder'] ?? 100,
'content' => $row['content'] ?? '',
'isActive' => true,
]);
$entityManager->saveEntity($entity);
$created++;
}
$log->info("[SmartAssistant] AfterInstall: seeded $created AssistantPrompt rows, $skipped already existed");
}
}