GenericTemplateGenerator and FreeDocumentGenerator now, when CaseFilesCore is
installed (soft-detected), set the generated Document's folderId to the case
"מסמכים" folder via CaseFolderService so it shows in the CaseDocs panel.
Existing Case link + NetworkStorage upload unchanged.
Verified on dev (P7_OK): php -l clean on both generators; relate/find junction
consistency confirmed; folder placement works.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stop self-naming in the document generators and defer to NetworkStorageIntegration's
single namer (rule N1):
- FreeDocumentGenerator and GenericTemplateGenerator no longer bake in a date prefix,
em-dash or contact name. They set the clean subject, upload explicitly to the case
folder (the AfterSave hook fires before the Case link exists), then realign the
Document + Attachment names to the canonical stored name returned by the upload.
- CaseFolderManager sanitizes every model-chosen name server-side: writeFile splits
dir/basename and de-duplicates via buildStoredFileName; createFolder, renameItem and
moveItem sanitize their final segment. The LLM can no longer pick a raw filename.
Refs Task Master #7
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AssistantRule had no clientDefs file — the same gap that broke CaseMemory
in 2.10.2. Console showed:
/client/custom/modules/smart-assistant/src/controllers/assistant-rule.js
Failed to load resource: 404
Adding clientDefs/AssistantRule.json with controller: controllers/record
lets EspoCRM use the default record controller and stops the frontend
from probing for a non-existent module-specific controller file.
(2.10.3 had a force-push race against the auto-built zip, hence the
extra patch version.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two metadata gaps spotted when the user couldn't see Shira entities in
Admin → Layout Manager and got console 404s on /#CaseMemory:
* AssistantRule.json (scopes/) was missing `customizable: true` and
`importable: false`. Without `customizable: true` EspoCRM hides the
entity from Admin → Entity Manager → Layouts. Other Shira entities
already had it; this one was the outlier.
* CaseMemory.json (clientDefs/) didn't exist at all. The Metadata
endpoint returned an empty object, so the frontend fell through to
default convention `client/custom/modules/smart-assistant/src/controllers/case-memory.js`
which doesn't exist → 404 in browser console. Adding clientDefs with
`controller: controllers/record` + icon + filters lets the standard
record controller handle the entity without any custom JS file.
Verified on prod after hot-patch:
- Metadata?key=scopes.AssistantRule.customizable → "true"
- Metadata?key=clientDefs.CaseMemory → full object
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
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.
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>
Without Controllers/AssistantRule.php, EspoCRM responds 404 "Controller
'AssistantRule' does not exist" on every POST/GET /api/v1/AssistantRule,
even though scopes/AssistantRule.json declares entity=true, object=true,
acl=true. The save_rule tool in shira-hermes calls the Record endpoint
directly, gets the 404, falls back to nothing (because the user_id branch
of the fallback rarely matches), and the LLM tells the user "✅ כלל נשמר"
without verification — yet another instance of the hallucination pattern.
A one-line Controller class that extends Espo\Core\Controllers\Record
exposes the standard CRUD verbs and unlocks the storage path that the
rest of the stack already expected. Verified in prod: GET, POST, DELETE
all return 200 after this is in place.
Paired with shira-hermes 06b27e9 which teaches the LLM to actually read
the tool result and never report success on a ❌ response.
Refs Task Master #6
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause of the Shira hallucination incident in production case 46 Friedman:
DocumentAnalyzer::extractFromDocx relied on PhpOffice\PhpWord, but on the
EspoCRM prod image the PHPWord files are present in vendor/ yet are NOT
registered in the composer PSR-4 autoload map — class_exists() silently
returned false, the method returned null, and Shira's read_document fell
back to OCR. OCR then only saw the signature image, the AI got "[signature]"
as document content and fabricated the entire CTS appeal as a knee injury.
This change rewrites extractFromDocx to use ZipArchive + a small regex
parser of word/document.xml. Independent of PHPWord, more robust on
tables/footnotes/hyperlinks (which PHPWord's element walker missed at depth
>1), and verified on prod against the same Friedman appeal: 80 paragraphs /
16633 clean chars extracted (vs 0 before).
The paired Python fix in shira-hermes (commit 7b517e1) makes the OCR
fallback also read document.xml, so even if this PHP fix regresses again,
the AI will not be fed "[signature]" as document content.
Refs Task Master #5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- FreeDocumentGenerator: build RTL Hebrew DOCX from lightweight markdown,
attach to case, copy to network folder.
- CaseFolderManager: writeFile / createFolder / renameItem / moveItem,
all sandboxed to the case's own root. Physical delete is never exposed;
markForDeletion moves the file to "<caseRoot>/מסמכים למחיקה/" with a
"למחיקה - " prefix.
- 6 new controller actions: createDocument, writeToFolder, createFolder,
renameItem, moveItem, markForDeletion.
- AlertCalculator: silence "X days without activity" alerts for cases
legitimately waiting on the court (PendingDecision always; PendingHearing
when cNextHearing is in the future).
- CaseContextBuilder: fix DocumentTemplate query — was filtering on the
wrong column (entityType vs targetEntityType), which is why Shira's
TEMPLATES section was always empty. Now respects isActive and surfaces
category/description.
- Document entity: new markedForDeletionAt + markedForDeletionBy audit fields.
Pairs with shira-hermes 0.4+ which exposes the 9 matching tools to Shira.
Refs Task Master #4
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shira can now create documents from scratch (not just from templates) and has
full sandboxed read/write access to the case's network folder, including a
soft-delete protocol so she cannot physically remove files.
New services:
- FreeDocumentGenerator: builds RTL Hebrew DOCX from lightweight markdown,
saves as Attachment + Document, links to Case, copies to network folder.
- CaseFolderManager: writeFile/createFolder/renameItem/moveItem operations
constrained to the case's own root via path normalization; never exposes
a physical delete. markForDeletion moves the file into
"<caseRoot>/מסמכים למחיקה/" with a "למחיקה - " prefix so the lawyer can
audit and delete manually.
New controller actions: createDocument, writeToFolder, createFolder,
renameItem, moveItem, markForDeletion.
Bug fix (root cause of the empty TEMPLATES section in Shira's case prompt):
CaseContextBuilder was querying DocumentTemplate with `entityType=Case` but
the real column is `targetEntityType`. Also added `isActive=true` filter and
surfaced category/description for better LLM selection.
Alert refinement: AlertCalculator no longer raises "X days without activity"
for cases that are legitimately waiting on the court. PendingDecision is
always silenced; PendingHearing is silenced when cNextHearing is in the
future (unscheduled PendingHearing cases still alert).
Document entity: new `markedForDeletionAt` + `markedForDeletionBy` audit
fields with Hebrew labels.
Refs Task Master #4
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The floating chat called Espo.Ajax.postRequest with timeout:180000, which is
shorter than the agentic loop's worst-case latency. On 2026-05-13 a prod
request to generate a direct-access report sat 227s in the first ai-gateway
iteration (large skill+context payload), so the browser fired the .catch()
handler at 180s and rendered "שגיאה בתקשורת" — even though the backend
completed the work 47s later (report attached to case 42).
Changes:
- floating-chat.js: timeout 180000 → 600000 (matches the PHP curl timeout
already set to 600s in SmartAssistantService.php:521).
- After 60s the spinner label swaps to a "still working" hint so the user
knows long requests are normal, not stuck.
- i18n: new SlowHint label in en_US + fa_IR (the project uses fa_IR as the
Hebrew slot).
Refs Task Master #3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CURLOPT_TIMEOUT was 180s. Generating a consultation summary requires
up to 10 AI iterations at ~20s each, regularly hitting the limit and
causing a "שגיאה בתקשורת" error in the UI before Shira could respond.
Raised to 600s. The shira-hermes agent runner now also enforces a 570s
total budget so it returns a clean Hebrew message if the limit is hit,
rather than the PHP connection being cut mid-response.
Refs Task Master #2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New POST /api/v1/SmartAssistant/action/getDocumentBytes returns
{success, fileName, mimeType, sizeBytes, base64} for use by
shira-hermes Claude Vision OCR fallback.
- DocumentAnalyzer: new getDocumentBytes() method + guessMimeType helper.
- CaseContextBuilder: expose signatureRequests in case context (for
DigitalSignature integration — gracefully empty if entity absent).
- README: sync version badge.
- Version bump 2.7.3 → 2.8.0 (new public endpoint).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tasks with parentType='Contact' were invisible to AlertCalculator,
CaseContextBuilder, and OfficeContextBuilder — all queries only checked
parentType='Case'. This caused false "no preparation task" alerts in
the daily standup even when preparation tasks existed.
Broadens getSummary, findOverdueTasks, findUpcomingTasks, getWorkloadByUser
to query parentType=['Case','Contact']. Adds caseHasPreparation() helper
that checks Case tasks, Contact tasks, and NhActivity-linked tasks.
Adds getCaseContactIds() to CaseContextBuilder and OfficeContextBuilder.
Refs Task Master #1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tasks created with parentType='Contact' (e.g. preparation calls linked
to a case's contact) were invisible to AlertCalculator, CaseContextBuilder,
and OfficeContextBuilder — all of which only queried parentType='Case'.
This caused false "no preparation task" alerts in Shira's daily standup
even when preparation tasks existed, because they were linked to the
case's Contact instead of the Case entity directly.
Changes:
- AlertCalculator: getSummary, findOverdueTasks, findUpcomingTasks,
getWorkloadByUser now query parentType=['Case','Contact']
- findUpcomingHearingsWithoutPrep: new caseHasPreparation() helper
checks Case tasks, Contact tasks, and NhActivity-linked tasks
- CaseContextBuilder.getOpenTasks: OR query across Case + Contact IDs
- OfficeContextBuilder.buildCaseDrillDown: same OR query pattern
- All three classes gain a getCaseContactIds() helper
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the AI Gateway sends nested JSON objects (like legalAnalysis), PHP
decodes them as stdClass. The previous (array) cast only converted the
top level, leaving nested stdClass that caused "Cannot use object as
array" fatal errors. Now uses json_decode(json_encode()) for recursive
array conversion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the AI Gateway returns a non-200 (e.g. tool validation failure),
callWebhook throws an unhandled Error that bubbled up to Slim as a raw
500 page. Now caught gracefully with a Hebrew error message returned
to the user instead of "Slim Application Error".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bidi-table is a Claude Code skill that was accidentally committed here.
It doesn't belong in the SmartAssistant extension.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ZIP release artifacts were tracked in git, bloating the repo and causing
oversized release uploads on Gitea. Build output now goes to build/ which
is gitignored along with *.zip.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add GenericTemplateGenerator that downloads templates from WebDAV,
fills entity placeholders automatically (case/contact/account), and
supports custom placeholders from AI conversation. New API endpoint
generateFromTemplate exposed for the AI Gateway.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds POST /SmartAssistant/action/executeTool that accepts {tool, params, caseId}
and routes directly to the plugin tool handler via ActionExecutor. This allows
the AI Gateway to call plugin tools (like generate_direct_access_report) without
going through the conversation approval flow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enable the AI assistant to read full documents from network storage (PDF/DOCX/TXT),
summarize multiple documents, and batch-rename files. Key changes:
- Raise text extraction limit from 2,000 to 100,000 chars (configurable)
- Add agentic loop in SmartAssistantService for multi-turn tool execution
- New tools: read_document, read_multiple_documents, batch_rename_documents
- New API endpoints: readDocument, readMultipleDocuments, batchRename
- New integration settings: maxDocumentChars, agenticLoopEnabled, maxAgenticLoops
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add getLegalAidData() to CaseContextBuilder so Shira can see existing
legal aid information when working on a case.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Long operations like generate_initial_report were completing successfully
but the frontend showed "communication error" due to AJAX timeout. Increased
CURL timeout to 180s and frontend AJAX timeout to 180s (3 minutes).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shira can now delete tasks, meetings, and calls when they're no longer needed.
delete_meeting and delete_call handlers already existed but weren't exposed to the AI.
SmartAssistant v2.5.2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move "needs attention" tab to the right (first in RTL), make it active
by default, and move "open cases" to the left (last).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add delete_meeting, delete_call, delete_note tools for Shira.
Add expand/collapse toggle button to the floating chat window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add create_call tool: records phone calls as Call entity (not Meeting)
- Add AssistantRule entity: persistent behavioral rules the AI follows
- Add save_rule tool: AI can save new rules when user requests them
- Add systemInstructions to webhook: call vs meeting guidance, open question tracking
- Add recentCalls to case context for AI awareness
- Add AssistantRuleContextProvider: injects active rules per mode into context
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ActionExecutor now checks app.smartAssistant.tools.{name}.handler
metadata for unknown tools before throwing. This allows extensions
to register their own tool handlers without modifying SmartAssistant.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- EspoCRM sends url + apiKey in every request to the gateway
- New Integration field: espocrmApiKey
- Gateway no longer needs ESPOCRM_URL/ESPOCRM_API_KEY env vars
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Inset crop to avoid icon's rounded corners, pure blue background fills entire square.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Square avatar image (no circle mask) — CSS border-radius handles clipping
- Badge moved outside .sa-fab to separate wrapper, not clipped by overflow:hidden
- object-fit: cover ensures image fills entire circle
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Avatar image pre-cropped as circle with transparency
- Removed overflow:hidden from FAB so badge is not clipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Shira executes all actions inline (create task, meeting, note) without approval buttons
- Actions work from both case and office mode (auto-resolves case from drill-down)
- Tasks/meetings can be created without a case parent
- Chat input is now multi-line textarea with auto-grow (Shift+Enter for new line)
- Markdown tables render as proper HTML tables with LRM marks for BiDi safety
- Robot icon replaced with Shira's avatar in FAB, chat header, and message bubbles
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Shira speaks in feminine form (חושבת instead of חושב)
- Neutral verb forms for buttons (הוספת/חיפוש instead of הוסף/חפש)
- Updated fallback texts to reference שירה by name
- Description updated: AI Gateway integration replaces n8n
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Shira creates a task, meeting, or other entity, the current
detail view panels now refresh automatically via model.trigger('update-all').
For tasks and meetings, also refreshes the activities/tasks side panels.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EspoCRM returns JSON data as stdClass objects, not arrays. Using
array access ($data[$i]['role']) on stdClass causes fatal error.
Fixed by using json_decode(json_encode(...), true) to ensure deep
conversion to associative arrays.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SmartRequest and SmartAction note views were missing data() method,
so {{post}} in the template resolved to empty string. Added proper
data() with escapeString formatting, consistent with SmartResponse.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
self was resolving to window.self instead of the View instance
inside the forEach callback, causing self.escapeString to fail.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EspoCRM resolves note type views via clientDefs.Note.itemViews metadata.
Without this mapping, it looked for SmartRequest/SmartResponse/SmartAction
in the core views path instead of the module path, causing 404 errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>