Implements the compile-in plugin system designed in spec 019:
- internal/plugin/ (~1,300 LOC):
* Tiny Plugin interface + 10 optional HasX capability sub-interfaces
* Host struct with Logger / DB / Messenger / Channels / Attachments /
Search / Secrets / Events / Config / DataDir / Tracer / Metrics /
DefaultOwner / BaseURL
* Registry with panic-on-duplicate, stability-level tracking, capability
indexing (MCP tools, actions, panels, channel types, routes, event
subscribers, CLI commands)
* Migrator: per-plugin SHA-256-checksum'd migration chain, namespaced
plugin_<name>_* table enforcement, idempotent re-apply
* Three-phase lifecycle (Migrate → Init → Start) with panic-safe
wrappers around every plugin call; failure per plugin isolated,
core continues
* YAML config loader that preserves unknown top-level keys on round-trip
* Status store exposing /api/plugins/status JSON
* Restart helpers (Noop + SignalRestarter); graceful reload is
in-process for the demo
- internal/plugin/plugintest/ (~345 LOC):
* NopHost(t) with in-memory modernc.org/sqlite
* Run(t, plugin) full-lifecycle smoke helper
* Assertions: HasTool, HasAction, HasPanel, HasChannelType,
HasMigration, PluginStarted, PluginFailed
* ScopedSecrets that returns ErrSecretNotFound for cross-plugin
reads (satisfies SC-006)
- internal/plugins/demo/ (canonical showcase):
* Plugin that exercises every HasX capability (migrations, actions,
HTTP routes, web panel, lifecycle, config schema, stability)
* Own SQL migration creating plugin_demo_notes
* Embedded HTML panel that fetches notes via JS
* 4 unit tests covering smoke, full capability registration, action
handlers, and config-driven max_notes limit
- cmd/plugindemo/ (~290 LOC):
* Demo HTTP server wiring registry to chi
* Mounts /api/plugins/status, /api/admin/plugins/{name}/{enable,disable},
/api/actions/{name}, /api/plugins/<name>/* (per-plugin REST),
/ui/plugins/<name>/ (per-plugin UI)
* SIGHUP-triggered config reload + registry rebuild + mux swap
* SIGTERM/SIGINT graceful shutdown
- test/integration/ (~357 LOC, build-tag "integration"):
* 6 end-to-end tests against a spawned plugindemo binary
* Enable/disable round-trip with data preservation
* SIGHUP reload timing (measured 41 ms — SC-008 target is 2 s)
* Action-404 on disabled plugin, panel-404 on disabled plugin
* REST endpoints + UI panel reachable
Contract deviation: admin toggle endpoints moved from
/api/plugins/{name}/{enable,disable} to /api/admin/plugins/{name}/{...}
to avoid URL collision with chi per-plugin route mounts. rest.md updated.
Scope deferred to next session (mechanical follow-ups):
- Port internal/wiki/ to internal/plugins/wiki/
- Squash 26 migrations to schema/000_initial.sql
- Backup scripts for live kubic instance
- Remaining 9 plugin extractions
- Boundary-lint static analyzer
- Wire into cmd/synapbus/main.go
All unit + integration tests green. Chrome UI smoke test passes.
autonomous_summary.md carries the full verification record.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
35 lines
1.3 KiB
HTML
35 lines
1.3 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8"/>
|
|
<title>Demo Plugin — Notes</title>
|
|
<style>
|
|
body{font-family:-apple-system,sans-serif;margin:2rem;max-width:760px;background:#0b0f17;color:#e6edf3}
|
|
h1{color:#c4b5fd}
|
|
.note{border:1px solid #1f2937;border-radius:10px;padding:12px 16px;margin:10px 0;background:#111827}
|
|
.note h3{margin:0 0 6px;color:#67e8f9}
|
|
.note small{color:#9aa4b2;font-family:monospace}
|
|
button{background:#7c5cff;color:white;border:0;border-radius:8px;padding:8px 14px;cursor:pointer}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Demo Plugin · Notes</h1>
|
|
<p>Minimal panel served from <code>/ui/plugins/demo/</code>. The HTML is embedded
|
|
into the binary via <code>go:embed</code>; the list below is fetched from the
|
|
plugin's own REST route <code>/api/plugins/demo/notes</code>.</p>
|
|
<div id="notes"></div>
|
|
<button onclick="refresh()">refresh</button>
|
|
<script>
|
|
async function refresh(){
|
|
const r = await fetch('/api/plugins/demo/notes');
|
|
const data = await r.json();
|
|
const el = document.getElementById('notes');
|
|
el.innerHTML = (data.notes||[]).map(n =>
|
|
'<div class="note"><h3>'+n.title+'</h3><small>'+n.slug+' · '+n.created_at+'</small><p>'+(n.body||'')+'</p></div>'
|
|
).join('') || '<em>(no notes yet — POST /api/plugins/demo/notes)</em>';
|
|
}
|
|
refresh();
|
|
</script>
|
|
</body>
|
|
</html>
|