From f1f377a71e835aea6d45870d0523d63ccbe3bdb3 Mon Sep 17 00:00:00 2001 From: Bonanza Date: Sat, 25 Jul 2026 19:48:02 -0700 Subject: [PATCH 1/2] fix(plugin-types): ActionsAPI.register handler takes ActionExecutionContext, not raw input (fn-176.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The host invokes action handlers with a single exec-context object ({ invocationId, source, input, sourceId? } — PluginActionsBridge.swift), but the d.ts declared the handler as (input: AnyJSONValue). Known drift F1. - Export InvocationSource (host-canonical 6-member literal union) and ActionExecutionContext (exact host field set, no index signature) from namespaces-core-plugins.ts; register's handler param is now (exec: ActionExecutionContext); return type and def param unchanged; JSDoc updated to describe exec-context dispatch. - index.ts header + README: drop the '/// ' global usage suggestion (package ships module exports only — it produced TS2304); import-based guidance only. - New compile-only typetest src/__tests__/actions.typetest.ts importing via the public entrypoint (../index): cases (a)-(e) per fn-176.1. Probe outcomes (TS 5.x, recorded in-file): * (a) compiles with a type-alias SomeShape; an interface target fails 'exec.input as X' (no implicit index signature) — ytdlp's unmodified interface DownloadUrlInput still needs interface→type or 'as unknown as' (flagged to fn-176.3). * (f) legacy (input: AnyJSONValue) handler does NOT assign (ActionExecutionContext has no index signature) — case dropped per task fallback rule; changelog language for .3: source-breaking-but-runtime-correcting. - tsconfig.json: exclude docs-site from root lint project — root 'npm run lint' has been red since PR #4 (docs-site's astro:content virtual module + its node_modules get swept); docs-site has its own toolchain and CI. - package-lock.json: 1-line sync of the plugin-types workspace stanza to the pre-staged 3.0.0. Validation: npm run build RC=0; npm test RC=0 (typetest enforced — unused @ts-expect-error fails compile); npm run lint RC=0; dist verification: dist surface exports ActionExecutionContext via package entrypoint (probe RC=0), zero 'handler: (input: AnyJSONValue)' in dist; grep gates: zero '/// -``` - -Or import specific types: +Import the types you need (the package ships module exports only — no +ambient globals): ```ts import type { 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"] } From 8a95c78d2bc98834336345831b9711208c8a4f4b Mon Sep 17 00:00:00 2001 From: Bonanza Date: Sat, 25 Jul 2026 19:53:30 -0700 Subject: [PATCH 2/2] =?UTF-8?q?docs(plugin-types):=20README=20version=20ma?= =?UTF-8?q?pping=20=E2=80=94=20version-agnostic=20phrasing,=203.0.x=20exam?= =?UTF-8?q?ple=20(fn-176.1=20review=20r1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex impl-review round 1 Minor: README still mapped 2.4.x <-> plugin API 2.4.x while the package ships 3.0.0. Phrase the mapping version-agnostically so it survives future bumps (incl. a potential 3.0.1 under the epic's release fallback rule). Task: fn-176-sdk-fix-actionsapiregister-handler-dts.1 --- packages/plugin-types/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-types/README.md b/packages/plugin-types/README.md index 4eeaa4d..97e54c5 100644 --- a/packages/plugin-types/README.md +++ b/packages/plugin-types/README.md @@ -41,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