the last piece of docs/plugin-capabilities.md. skills, affordances and
notification catalogues already loaded from each installed and enabled
plugin's own directory; tasks did not, so the transcribe plugin shipped a
task nothing could see — right-clicking an .m4a offered no Run Task at all.
it turned out to be one function rather than several. every path that runs
a task resolves it through getTaskByDirName and then reads implementation
off the record, so the sources go in there and in listTasks and a plugin's
task is runnable everywhere at once, identity unchanged: still a directory
name, still what pipeline_jobs stores.
the record carries `plugin` and that one field does the rest. updateTask
and deleteTask throw on it, DELETE /api/tasks/:name answers 409 naming the
plugin, and the capability screen shows whose it is instead of an edit and
a delete that cannot work. without the delete guard it was quietly a no-op:
rm -rf on a path in the owner's store that does not exist, then ok: true,
and the task still in the menu.
owner's store first, so a plugin cannot take over a name already in use.
getTaskByDirName now refuses a name containing a separator. it always
joined a caller-supplied string onto a path — from a websocket message and
a job row — and searching several roots is the moment that stops being one
directory nobody could escape.
capabilityRootsFrom is gone; it was dead. capabilitySources replaces it and
carries the provenance, with capabilityRoots the thin map over it that the
loaders that only want paths still use.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
capabilities/ is meant to be forked — core's work plus the owner's, and nothing
else. Four publishers copy plugin files into it at install, so a fork
accumulates files it did not author with nothing on disk saying which. On this
machine skills/email-db/ and extensions/ are sitting untracked in that
repository right now, and there is no good answer: commit them and the fork
carries another plugin's work, leave them and git status is permanently noisy.
Copying is wrong a second way that has nothing to do with git. A copy goes
stale — plugins:update pulls new plugin code while the copied capability keeps
describing the old one — and a copy survives plugins:disable, so a disabled
plugin's skill stays in the agent's index.
So the platform reads them where they are: the capabilities directory, plus each
installed and enabled plugin's own. Nothing is copied, and nothing needs a
namespace to stay apart because separate directories already do that
structurally rather than by convention.
Four bugs stop existing: forks stay clean, capabilities update with the plugin,
disable actually disables, and uninstall needs no cleanup — so there is no list
of filenames to get wrong and delete the owner's work with.
Deletes all four publishers when built, including api/tasks/publish.ts from
earlier today, superseded before it shipped anywhere. app-store/assets stays —
those are images served over HTTP, and the copy is what makes them reachable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043