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>
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 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>
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>
Replace jQuery text escaping with EspoCRM's built-in View.escapeString()
to maintain consistency with the rest of the codebase.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Espo.Utils.escapeString does not exist in EspoCRM 8.x, causing
TypeError crash when clicking dashlet cards. Use jQuery .text().html()
for XSS-safe HTML escaping instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cards "תיקים פתוחים" and "דיונים השבוע" showed counts but clicking
revealed "אין פריטים להצגה" because card counts came from direct DB
queries while detail filtered from alerts with stricter criteria.
Added getOpenCaseItems() and getUpcomingHearingItems() to AlertCalculator,
included them in summary API response, and wired dashlet to use them.
Also: renamed extension to SmartAssistant, title to "שירה — עוזרת אישית".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move JS from client/custom/src/modules/{mod}/ to client/custom/modules/{mod}/src/
Move TPL to client/custom/modules/{mod}/res/templates/
Update all define(), createView(), and metadata references from
custom:modules/{mod}/... to modules/{mod}/...
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>