Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 3 additions & 8 deletions packages/plugin-types/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,8 @@ npm install --save-dev @appos.space/plugin-types

## Usage

Either reference the types globally:

```ts
/// <reference types="@appos.space/plugin-types" />
```

Or import specific types:
Import the types you need (the package ships module exports only — no
ambient globals):

```ts
import type {
Expand All @@ -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

Expand Down
101 changes: 101 additions & 0 deletions packages/plugin-types/src/__tests__/actions.typetest.ts
Original file line number Diff line number Diff line change
@@ -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.)
4 changes: 1 addition & 3 deletions packages/plugin-types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
*
* Version: 3.0.0
*
* Usage:
* /// <reference types="@appos.space/plugin-types" />
* // or
* Usage (module imports only — the package ships no ambient globals):
* import type { PluginContext, ViewDescriptor } from "@appos.space/plugin-types";
*/

Expand Down
35 changes: 33 additions & 2 deletions packages/plugin-types/src/namespaces-core-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<AnyJSONValue>): Promise<string>;
/**
* 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<AnyJSONValue>): Promise<string>;
/** Projects an existing command into the action catalog. [actions.register] */
registerFromCommand(commandId: string, metadata: Partial<ActionDefinition>): Promise<string>;
/**
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
{ "path": "packages/view-builders" },
{ "path": "packages/plugin-utils" }
],
"exclude": ["node_modules", "dist"]
"exclude": ["node_modules", "dist", "docs-site"]
}
Loading