diff --git a/package-lock.json b/package-lock.json index 1e462b0..a3f49f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -138,7 +138,7 @@ }, "packages/plugin-types": { "name": "@appos.space/plugin-types", - "version": "2.4.0", + "version": "3.0.0", "license": "MIT", "devDependencies": { "typescript": "^5.4.0" diff --git a/packages/plugin-types/README.md b/packages/plugin-types/README.md index 16e2cdf..97e54c5 100644 --- a/packages/plugin-types/README.md +++ b/packages/plugin-types/README.md @@ -14,13 +14,8 @@ npm install --save-dev @appos.space/plugin-types ## Usage -Either reference the types globally: - -```ts -/// -``` - -Or import specific types: +Import the types you need (the package ships module exports only — no +ambient globals): ```ts import type { @@ -46,7 +41,7 @@ export async function activate(ctx: PluginContext) { ## Version -Tracks the plugin API version. `2.4.x` of this package ↔ plugin API `2.4.x`. +Tracks the plugin API version — the package's `major.minor` matches the plugin API's `major.minor` (e.g. `3.0.x` of this package ↔ plugin API `3.0.x`). ## Related packages diff --git a/packages/plugin-types/src/__tests__/actions.typetest.ts b/packages/plugin-types/src/__tests__/actions.typetest.ts new file mode 100644 index 0000000..0b97a27 --- /dev/null +++ b/packages/plugin-types/src/__tests__/actions.typetest.ts @@ -0,0 +1,101 @@ +/** + * Compile-time type tests for the fn-89 ActionsAPI exec-context handler + * signature (`ActionExecutionContext` / `InvocationSource`). + * + * This file is NOT executed — it only needs to compile (or fail to compile) + * to verify type correctness. Lines marked @ts-expect-error MUST produce + * a type error; if they don't, the build will fail. + * + * Imports resolve through the package public entrypoint (`../index`) on + * purpose: this proves the types are exported from the public surface of + * `@appos.space/plugin-types`, not merely declared in an internal module. + */ + +import type { + ActionsAPI, + ActionExecutionContext, + InvocationSource, + AnyJSONValue, +} from "../index"; + +declare const actions: ActionsAPI; + +// ── (a) ytdlp's pattern compiles ── +// Mirrors appos-plugin-ytdlp src/actions/register-actions.ts:215-216: +// async (exec) => { const input = exec.input as DownloadUrlInput; ... } +// with a def carrying `displayName` / `risk` / `tags` extras (admitted by +// ActionDefinition's index signature). +// +// NOTE (probed 2026-07-25, TS 5.9): `SomeShape` here MUST be a `type` alias. +// An `interface` target fails the `exec.input as SomeShape` assertion with +// TS2352, because interfaces get no implicit index signature and therefore +// are not comparable to `AnyJSONValue`'s object arm +// (`{ [key: string]: AnyJSONValue }`). ytdlp's real `DownloadUrlInput` is +// declared as an interface, so its unmodified source still needs either +// `interface` → `type` or `as unknown as` when it adopts this package — +// recorded in fn-176.1's Done summary and flagged to fn-176.3 (release). + +type DownloadUrlInput = { + url: string; + format?: string; + quality?: string; +}; + +void actions.register( + { + id: "downloadUrl", + displayName: "Download URL", + risk: "external", + tags: ["yt-dlp", "download", "media"], + inputSchema: { type: "object" }, + }, + async (exec) => { + const input = exec.input as DownloadUrlInput; + const url = input.url.trim(); + const enqueuedIds: string[] = [url]; + return { enqueuedIds }; + }, +); + +// ── (b) exec-context field reads compile with the right types ── + +declare const exec: ActionExecutionContext; + +const _invocationId: string = exec.invocationId; +const _source: InvocationSource = exec.source; +const _sourceId: string | undefined = exec.sourceId; +const _input: AnyJSONValue = exec.input; + +// ── (c) undeclared property access is a type error (no index signature) ── + +// @ts-expect-error `url` is not a declared property of ActionExecutionContext +const _url = exec.url; + +// ── (d) handler with an incompatible parameter type is rejected ── + +void actions.register( + { id: "badHandler" }, + // @ts-expect-error a `string` parameter is not compatible with ActionExecutionContext + (input: string) => input, +); + +// ── (e) comparing `source` against a non-member literal is a type error ── + +// @ts-expect-error "webhook" is not a member of InvocationSource +const _cmp: boolean = exec.source === "webhook"; + +// ── (f) legacy-compatibility probe — DROPPED (probed 2026-07-25, TS 5.9) ── +// Epic R5/R10 hoped a legacy-style handler `(input: AnyJSONValue) => +// AnyJSONValue` would still assign to `register`'s handler parameter via the +// contravariant check (`ActionExecutionContext` assignable to +// `AnyJSONValue`'s object arm). It does NOT: TS2345 — "Index signature for +// type 'string' is missing in type 'ActionExecutionContext'" (interfaces get +// no implicit index signature). Per the task's fallback rule the executable +// case is dropped; outcome recorded in fn-176.1's Done summary and flagged +// to fn-176.3 so the changelog language reads +// "source-breaking-but-runtime-correcting" for explicitly-typed legacy +// handlers. (Untyped legacy handlers `(input) => ...` contextually infer the +// new context parameter, so the registration itself keeps compiling — though +// bodies that read raw-input fields off the parameter will now error until +// switched to `exec.input`. Handlers that explicitly annotated the parameter +// as `AnyJSONValue` fail at the `register` call site.) diff --git a/packages/plugin-types/src/index.ts b/packages/plugin-types/src/index.ts index de55279..78995c0 100644 --- a/packages/plugin-types/src/index.ts +++ b/packages/plugin-types/src/index.ts @@ -3,9 +3,7 @@ * * Version: 3.0.0 * - * Usage: - * /// - * // or + * Usage (module imports only — the package ships no ambient globals): * import type { PluginContext, ViewDescriptor } from "@appos.space/plugin-types"; */ diff --git a/packages/plugin-types/src/namespaces-core-plugins.ts b/packages/plugin-types/src/namespaces-core-plugins.ts index a5da1b4..dc2b58c 100644 --- a/packages/plugin-types/src/namespaces-core-plugins.ts +++ b/packages/plugin-types/src/namespaces-core-plugins.ts @@ -195,6 +195,30 @@ export interface ActionReceipt { [key: string]: AnyJSONValue | undefined; } +/** + * Source that initiated an action invocation (fn-89). Mirrors the host's + * canonical `InvocationSource` union. + */ +export type InvocationSource = "user" | "plugin" | "agent" | "recipe" | "sequence" | "system"; + +/** + * Execution context passed as the SINGLE argument to an action handler + * registered via `actions.register(...)` (fn-89). Mirrors exactly the + * object the host constructs per invocation: the validated `input` plus + * invocation metadata. Deliberately has NO index signature — the host + * passes only these fields, so unknown-property access is a type error. + */ +export interface ActionExecutionContext { + /** Unique id (UUID string) for this invocation. */ + invocationId: string; + /** What initiated the invocation. */ + source: InvocationSource; + /** Validated action input (`{}` when the action was invoked with empty input). */ + input: AnyJSONValue; + /** Originating identity when known (e.g. calling plugin id); key absent otherwise. */ + sourceId?: string; +} + /** * Public Action Fabric (fn-89): validate → permission → approve → execute → * receipt. @@ -206,8 +230,15 @@ export interface ActionReceipt { * @since fn-89 */ export interface ActionsAPI { - /** Registers an executable action. Returns a handle token. [actions.register] */ - register(def: ActionDefinition, handler: (input: AnyJSONValue) => AnyJSONValue | Promise): Promise; + /** + * Registers an executable action. Returns a handle token. [actions.register] + * + * The handler is invoked with a single {@link ActionExecutionContext} + * argument carrying the validated `input` plus invocation metadata + * (`invocationId`, `source`, optional `sourceId`) — NOT the raw input + * value. Read the action input via `exec.input`. + */ + register(def: ActionDefinition, handler: (exec: ActionExecutionContext) => AnyJSONValue | Promise): Promise; /** Projects an existing command into the action catalog. [actions.register] */ registerFromCommand(commandId: string, metadata: Partial): Promise; /** diff --git a/tsconfig.json b/tsconfig.json index 7c82cea..02f81ad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,5 +20,5 @@ { "path": "packages/view-builders" }, { "path": "packages/plugin-utils" } ], - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "dist", "docs-site"] }