plugins: a plugin can ship a task
Assets, affordances, notification catalogues and skills each had a publisher.
Tasks did not, which is the one that matters for reaching the FILE BROWSER'S
CONTEXT MENU: a task carrying `{ type: 'file', extensions: [...] }` already
appears on a right-click with no platform change, because matchesTrigger in
useTasks.ts does that work. What was missing was only delivery — the capability
had to be installed by hand beside the plugin and removed by hand after.
Flat rather than namespaced by publisher, matching skills. A task's directory
name is its identity everywhere: the taskDirName in a job row, the path a script
runner materialises, the label in a run history. Wrapping that in a publisher
would put one in all of those for a collision nobody has had, and a task claims
no authority — it is a menu entry behind a permission that already exists. That
trade is NOT available to affordances, which stay namespaced, because one that
could claim `officer/core:` would be claiming the platform's voice.
The test that earns its keep is the uninstall one. `tasks/` is the owner's own
directory and nothing on disk marks which entries came from a plugin, so the
scope of that delete is the only thing standing between an uninstall and
somebody's hand-written work. Proven rather than reasoned about.
Written for the Whisper extraction, where the transcribe entry has to arrive
with the plugin instead of being seeded separately.
1429 pass, tsgo clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -20,6 +20,7 @@ import { publishAssets, unpublishAssets } from '../app-store/assets';
|
||||
import { publishAffordances, unpublishAffordances } from '../affordances/publish';
|
||||
import { publishNotifications, unpublishNotifications } from '../notifications/publish';
|
||||
import { publishSkills, unpublishSkills } from '../skills/publish';
|
||||
import { publishTasks, unpublishTasks } from '../api/tasks/publish';
|
||||
import { refreshPluginMounts, snapshotPlugins } from './mount';
|
||||
import { publishExtension, unpublishExtension, extensionFileName } from './zip';
|
||||
import { generatePluginSchemas, pushPluginSchema } from './schema';
|
||||
@@ -438,6 +439,17 @@ export async function installPlugin(
|
||||
await step(steps, onStep, `skills: NOT published — ${err instanceof Error ? err.message : err}`);
|
||||
}
|
||||
|
||||
// And the same for tasks, which is how a plugin reaches the FILE BROWSER'S CONTEXT MENU. A task with a
|
||||
// file trigger appears on a right-click with no platform change at all — `matchesTrigger` in
|
||||
// officerdev/src/apps/FileBrowser/useTasks.ts already does that work. What was missing was only the
|
||||
// delivery, so the capability had to be installed by hand beside the plugin and removed by hand after.
|
||||
try {
|
||||
const published = await publishTasks(plugin);
|
||||
if (published) await step(steps, onStep, `tasks: ${published} published to capabilities/tasks/`);
|
||||
} catch (err) {
|
||||
await step(steps, onStep, `tasks: NOT published — ${err instanceof Error ? err.message : err}`);
|
||||
}
|
||||
|
||||
// The tile, for the person who just installed it. `plugin.web` because `pluginDockManifests` only
|
||||
// offers a tile for a plugin that has a frontend — a backend-only plugin has no screen to pin.
|
||||
if (plugin.web && forUserId !== undefined) {
|
||||
@@ -540,7 +552,8 @@ export async function uninstallPlugin(appName: string, onStep?: OnStep, onLog?:
|
||||
// still holding a queued push renders the unknown-code fallback, which is a defined state.
|
||||
if (plugin) await unpublishNotifications(plugin).catch(() => {});
|
||||
if (plugin) await unpublishSkills(plugin).catch(() => {});
|
||||
await step(steps, onStep, 'tables and data: untouched (published assets, affordances and notifications removed)');
|
||||
if (plugin) await unpublishTasks(plugin).catch(() => {});
|
||||
await step(steps, onStep, 'tables and data: untouched (published assets, affordances, notifications, skills and tasks removed)');
|
||||
return { ok: true, appName, steps };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user