4a37cfd2ce006a65cf88b49493151df76f0b5377
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e4f5e03105 |
a plugin can offer a commander, and offering is not starting
first half: the discovery and the two columns. no routes and no UI yet. PRESENCE IS THE DECLARATION. a plugin with commanders/<name>/COMMANDER.md offers one, and manifest.ts says nothing — same as tasks/ and skills/, and for the same reason: the directory already says it, and a second place to say it is a second place for the two to disagree. read in place through capabilitySources, so only an installed AND ENABLED plugin offers anything and plugins:update changes a template without republishing. A TEMPLATE IS NOT A COMMANDER. what a plugin ships is an OFFER: nothing runs, nothing is scheduled, no directory exists until an owner creates one from it. that is the shape asked for — a plugin may propose recurring work, only a person may start it. `plugin` IS A COLUMN, not a reserved category name. provenance should be structural: a category called "av-club" is a string an owner can rename, merge or delete, and the day they do, a plugin's commanders quietly become ordinary ones with nothing on disk saying otherwise. it also survives an uninstall, which is the point — the commander is still there and the UI can say where it came from. `plugin_config` holds the owner's answers because the form has to be RE-OPENABLE. somebody changing which directory is watched should see the one it is watching, not an empty form. the watcher script is generated from this, so this is the source of truth rather than the script. inputs reuse TASK.md's vocabulary exactly, so the existing form renderer draws them and the existing validation applies. "which folder, which extensions, what output" is the same question a task asks. `missingInputs` is the configure-before-running rule, and it is checked at ARM time rather than at creation. a half-configured commander that exists and is visible is useful; an ARMED one is what would run somebody's disk against a blank setting. columns verified live after db:push: plugin text, plugin_config jsonb. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043 |
||
|
|
6d673a1321 |
plugins: a plugin's tasks are read where they live
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 |
||
|
|
8770f98dcd |
capabilities: read a plugin's where they live, stop copying them in
Four publishers copied a plugin's affordances, notifications and skills into capabilities/ at install. That directory is meant to be forked — core's work plus the owner's — so a fork accumulated files it did not author with nothing on disk saying which, and there was no good answer: commit them and carry another plugin's work, leave them and git status stays dirty. Copying was also wrong in two ways unrelated to git. A copy goes stale, because plugins:update pulls the plugin's directory and the copy keeps describing the old version. And a copy survived plugins:disable, so a disabled plugin's skill stayed in the agent's index — off, and still being announced. Now the loaders take a list of roots: the owner's store, plus each installed AND enabled plugin's own directory. capability-sources.ts resolves that list. No namespace is needed to keep them apart, because separate directories do it structurally rather than by convention. The owner's own win a skill name collision — their root is first and the merge keeps the first of each id — so a plugin cannot take over a name already in use. Affordances are deliberately NOT de-duplicated: several can be true at once. Notifications needed more than a root list, and it would have failed silently. Their codes were derived from the COPY'S PATH, <publisher>/<appName>/file.yaml. Read in place the path is just notifications/catalogue.yaml and carries no identity, so every plugin code would have vanished with nothing raised. loadPluginEntries takes the identity from the manifest instead, which is better provenance anyway — publisher and appName are validated at discovery, where a path was only ever a directory somebody chose. A yaml key still cannot claim another namespace, and that is pinned by test. createSession is async now, only because resolving enabled plugins reads the database. Once per session; its single caller already awaited. Deletes all four publishers including api/tasks/publish.ts from earlier today, superseded before it shipped. app-store/assets stays — images served over HTTP, where the copy is what makes them reachable. NOT done: tasks. That store is read-write, so reads must span sources while writes stay in the owner's directory, and a plugin's task has to be read-only in the UI. Next. Two things the design predicted as orphans turned out not to be, and both were checked before deleting rather than after. capabilities/extensions/tool-loader is written by the platform's own bootstrap every boot, not published by a plugin. And skills/email-db has no source to fall back on — the email plugin ships no skills directory, so that file exists in exactly one place and was hand-written. Neither was touched. 1422 pass, tsgo clean, officer restarts and serves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043 |