Plugin now mounts two React components inside Paperclip via the
SDK's UI slot mechanism. Both are read-only views over data the
plugin worker fetches from legal-ai on demand.
## Slots
* **LegalCaseTab** (type: detailTab, entityTypes: ["issue"])
Mounted as a "ערר" tab on every issue page. Shows case summary
(status / practice_area / appeal_subtype), legal_arguments
grouped by party (עוררים/ועדה/משיבה/מבקשי היתר), attached
precedents, and open missing_precedents.
* **LegalCasesWidget** (type: dashboardWidget)
Dashboard tile with case counts by status + 7-day activity.
## Worker handlers (ctx.data.register)
Five handlers added at the end of setup() — all read-only over the
existing legal-ai HTTP API, all wrapped in try/catch so a transient
failure shows a placeholder instead of crashing the host:
- legal-case-summary → /api/cases/{n}/details
- legal-case-arguments → /api/cases/{n}/legal-arguments
- legal-case-precedents → /api/cases/{n}/precedents
- legal-case-missing-precedents → /api/missing-precedents?case_number=&status=open
- legal-dashboard-stats → in-memory aggregation over /api/cases
case_number is resolved from plugin state (scopeKind=issue,
stateKey=legal-case-number) — populated by legal_case_create.
## Build pipeline
- esbuild.ui.config.mjs uses createPluginBundlerPresets from the SDK
to build src/ui/index.tsx → dist/ui/index.js (13.5kb, react +
@paperclipai/plugin-sdk/ui externalized)
- package.json: build = "build:worker" (tsc) + "build:ui" (esbuild)
- tsconfig.json: jsx=react-jsx, lib += DOM
- New deps: react@19, @types/react, esbuild
## Manifest
- capabilities += ui.detailTab.register, ui.dashboardWidget.register
- entrypoints.ui = "dist/ui"
- ui.slots declared with entityTypes (not "entities" — fixed against
PluginUiSlotDeclaration validator)
## Verified
- tsc + esbuild + biome clean
- Plugin re-installs (20 capabilities) and activates with worker
+ 8 tools + 3 jobs + 1 webhook + 2 event subs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two new event types accepted on the existing case-status webhook
(eventType discriminator added; legacy status_change still default):
* **missing_precedent_created** → ``ctx.issues.askUserQuestions``
with a single-choice question {upload | irrelevant | defer}.
continuationPolicy=wake_assignee_on_accept routes the chair's
answer straight back to the CEO heartbeat without an extra hop.
* **export_complete** → ``ctx.issues.documents.upsert`` with a
markdown "final-decision" doc that links back to the DOCX on
legal-ai. (The SDK's documents API stores text only — binary
attachment isn't natively supported here.)
Manifest: +issue.documents.write capability (issue.interactions.create
was already declared in the previous SDK upgrade).
Tested: plugin activates with all 18 capabilities, 8 tools + 3 jobs
+ 1 webhook + 2 event subs registered.
200-version bump from 2026.325.0 → 2026.525.0 (matches host).
**Why now**: host is already on 2026.525.0; staying on 325 was unnecessary
technical debt. Production logs showed real bugs ("missing, expired,
or unknown invocation scope") that newer SDK versions are known to
have fixed.
**Compatibility**:
- TS build clean with zero changes to worker.ts or legal-api.ts.
- apiVersion: 1 still the schema (no v2 yet).
- Plugin activated successfully — 8 tools + 3 jobs + 1 webhook + 2 event
subs all registered, worker reports "Legal AI plugin ready".
**Manifest gaps fixed**: added 3 capabilities that the worker actually
uses (one of them was the root cause of repeated host-side errors):
* `agents.invoke` — required by `ctx.agents.invoke()` calls (CEO wakeup
from event handlers + comment routing). Previously the plugin was
invoking agents without the declared capability, raising
"missing scope" errors on every issue.created event.
* `issue.comments.read` — required by `ctx.issues.listComments()`
calls in the comment routing path.
* `issue.interactions.create` — forward-looking capability for the
AskUserQuestion-like flows the SDK exposes via `createInteraction`.
**Backup**: full 5-layer snapshot at /tmp/plugin-legal-ai-backup-*.tar.gz
+ tag `pre-sdk-upgrade-2026-05-26` (pushed). Rollback: `git reset --hard
pre-sdk-upgrade-2026-05-26 && npm ci && npm run build && reinstall`.
Closes TaskMaster #26.
- stale-case-reminder: wrap http.fetch in try-catch so network errors
log and return instead of crashing the job silently
- weekly-feedback-analysis: wrap agents.invoke in try-catch so CEO
unavailability logs and returns instead of crashing the job
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace companies[0] with first company that has a CEO_AGENT_IDS entry,
so the job doesn't silently skip if the list order is non-deterministic
- Change reason to "weekly-feedback-job" to match the CEO routing condition
added in legal-ceo.md (was "weekly-feedback-analysis scheduled job")
- decision-lessons.md is shared between companies so one CEO invocation suffices
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevent duplicate comments on rapid Paperclip retries:
- Check plugin instance state for requestId before processing
- Skip if same requestId was seen within 5 minutes
- Store requestId with ISO timestamp after first successful delivery
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Declare two new cron jobs in plugin.json and manifest.ts, and implement
their handlers in worker.ts. stale-case-reminder runs daily at 08:00 and
posts a warning comment on any Paperclip issue linked to a legal-ai case
that has not been updated in 3+ days. weekly-feedback-analysis runs every
Sunday at 19:00, fetches the weekly chair-feedback summary from legal-ai,
and invokes the CEO agent to update decision-lessons.md with new lessons.
Add webhooks[] array to plugin.json and manifest.ts with the
'case-status' endpointKey. Without this declaration, Paperclip
registers 0 webhooks even when onWebhook() is implemented, so
POST /api/plugins/marcusgroup.legal-ai/webhooks/case-status would
return 501 and never reach the handler.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the onWebhook lifecycle hook to the definePlugin() call. When
legal-ai POSTs to /webhooks/case-status, the handler finds the linked
Paperclip issue (via plugin state scan), posts a Hebrew status comment,
and wakes the CEO agent on qa_failed. Hoists PluginContext and
CEO_AGENT_IDS to module scope so onWebhook can access them after setup().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add event handler that intercepts user comments on issues and wakes the
CEO agent (instead of only the assigned agent). The CEO reads the comment,
checks for attachments, and routes to the appropriate agent.
- Add issue.comment.created event subscription
- Add agents.read, agents.invoke, issue.comments.read capabilities
- CEO receives comment body + issue context in the invoke prompt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>