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"]
}