From 5cb7287b4010184f80eb4dbf0fc98310b1fd077a Mon Sep 17 00:00:00 2001 From: lick <2188718831@qq.com> Date: Tue, 3 Nov 2026 18:23:37 -0800 Subject: [PATCH] Sync keymap import plugin contract --- contract.lock.json | 12 ++- contract/v1/README.md | 97 ++++++++++++++++--- contract/v1/fixtures/command-plans.json | 64 ++++++++++++ .../keymap-import-provider.plugin.json | 18 ++++ contract/v1/plugin-manifest.schema.json | 44 ++++++++- 5 files changed, 216 insertions(+), 19 deletions(-) create mode 100644 contract/v1/fixtures/keymap-import-provider.plugin.json diff --git a/contract.lock.json b/contract.lock.json index d086f42..a042e62 100644 --- a/contract.lock.json +++ b/contract.lock.json @@ -3,12 +3,12 @@ "source": { "repository": "https://github.com/1lck/Lithe-IDEA", "directory": "shared/contracts/plugins/v1", - "revision": "f64d262c48f1ff5ad1778b301138596493a09ad5" + "revision": "ced042289b5fbf9b6562eacd8da1f15428b5849d" }, "files": [ { "path": "README.md", - "sha256": "4cfe0f5f6cb15980bf9db4b19fe5ed80082c417132ccad04dee5959d7efdef72" + "sha256": "b30695e1c297812ad009937b78daf12f23b74d4c111c602614e393c25d5b5693" }, { "path": "fixtures/backend-workspace.plugin.json", @@ -100,7 +100,7 @@ }, { "path": "fixtures/command-plans.json", - "sha256": "a188fd65f6cdb47f9e9ad4b0de3a7ddcd17e4e577b34f6e25b96d2c1f2bdca7c" + "sha256": "d268b4d3966c73031b1876c11cec9a2f6ee6dcc27b8bee687c9a349c147610f4" }, { "path": "fixtures/debug-adapter-language-server.plugin.json", @@ -138,6 +138,10 @@ "path": "fixtures/invalid-manifests.json", "sha256": "3859c99928158e44fc1f9f25caff5289ed3c2f8e3af200108ab17051915246c2" }, + { + "path": "fixtures/keymap-import-provider.plugin.json", + "sha256": "03d3843f30edd82fd02652a6f800eca9d7afcddc7b9d2a0bd1b3726c2dfb8eae" + }, { "path": "fixtures/language-grammar.plugin.json", "sha256": "9c55a3ea5f075328fca87c845f04f680e35fb283a39283c742fb5d435be05faa" @@ -256,7 +260,7 @@ }, { "path": "plugin-manifest.schema.json", - "sha256": "fba04d8c2c053cec877ab0419e1236810a75368fb3536b271b6a6301ba750339" + "sha256": "7ed0714e4d6120dc4098410db07ca5286f64677b05f03b7532f0ca73bdda2e5d" }, { "path": "proposals/toolchains/README.md", diff --git a/contract/v1/README.md b/contract/v1/README.md index 12b4059..e60e180 100644 --- a/contract/v1/README.md +++ b/contract/v1/README.md @@ -13,6 +13,7 @@ This directory is the cross-platform source of truth: | `plugin-manifest.schema.json` | JSON shape of `plugin.json` | | `fixtures/*.plugin.json` | Valid example manifests, named after the capability they exercise rather than a published plugin: `language-*` (packaged, system-command, and host-integration language servers; `node-runtime` for a discovered runtime), `hello-lithe` and `web-panel` (UI), `settings-v2`, and `build-*` (level 3 build systems and the two forms of a build provider) | | `fixtures/tool-definition.plugin.json`, `fixtures/toolchain-provider.plugin.json` | Production-shape Core fixtures for tool definitions and a packaged toolchain provider; require explicit new host capabilities, not yet supplied by native products | +| `fixtures/keymap-import-provider.plugin.json` | Two independent shortcut import providers sharing one packaged backend; native source access is not implemented yet | | `fixtures/invalid-manifests.json` | Edits to a valid manifest and the rejection code every host must report | | `fixtures/command-plans.json` | Golden run/test command expansions and language-server launch plans | | `fixtures/runtime-versions.json` | Golden outcomes of the launch runtime version check | @@ -56,13 +57,77 @@ admit new sources into existing Build candidate sets. Definitions are entirely plugin-owned, with reserved short IDs checked against host-verified official provenance when constructing the registry. -Neither native host advertises or integrates this foundation yet. Core consumes -captured host facts; it does not start a provider, run a probe, watch files, scan -the system, persist pins, or change current Java/Maven selection. The broader -proposal remains unsupported where it exceeds this implemented slice. In -particular, `toolchainConsumers`, generic runtime launches, settings pages, mise -and SDKMAN providers, and live backend activation are not implemented here. -Proposal envelopes remain separate from production manifests. +macOS advertises these capabilities for its read-only toolchain settings pages. +The native adapter scans declarations, probes checked executables, queries plugin +backends and watches their inputs; Core consumes captured facts and owns validation +and resolution. Windows does not advertise the capabilities. Saving pins, +`toolchainConsumers`, generic runtime launches and live backend activation remain +unsupported, and current Java/Maven/Build/Run selection is unchanged. Version-manager +implementations such as mise belong to their own plugin packages. Proposal +envelopes remain separate from production manifests. See [the implemented slice](toolchains.md). + +## Shortcut import providers (Core foundation) + +`keymapImportProviders` contains one to eight `{id, displayName, recognition, inputs}` entries. +IDs are unique within the plugin, start with a lowercase letter, contain only +lowercase ASCII letters, digits and hyphens, and are at most 64 bytes. The +plugin requires `keymapImportProviders: 1`, `backend: 2`, `serverProcess: 1`, +`backend.launch`, `keymaps.provide` and `keymaps.readSelectedSources`. Its backend starts from a packaged +executable or packaged runtime without discovered runtime dependencies. The +host starts it for an explicit import; it is not a startup or workspace-open +activation event. The permission permits offering mappings; it grants no file +access on its own. `keymaps.readSelectedSources` authorizes only the provider's +declared inputs under the source directory the user explicitly selected for this +import, and only for the lifetime of that import. + +`recognition` lists one to sixteen distinct metadata-only probes. A +`{kind: "file" | "directory", path}` probe matches an entry of that kind +beneath the selected root, using the input path restrictions and refusing +symlinks. A `{kind: "root", base: "userConfig", components}` probe matches +the selected directory's exact path beneath the platform's user configuration +directory (macOS Application Support, Windows roaming AppData). Components +contain one to eight safe literal names and may end in one `*`, matching +exactly one directory component; the first component must be literal. The +source matches any probe. Recognition never authorizes reading file contents +or discloses the absolute path to the provider. A host may use probes without +launching backends, and must recheck them on import. + +`inputs` lists one to sixteen unique selectors. `{kind: "file", path}` selects +one relative file; `{kind: "directory", path, extension}` selects immediate +children of that directory with the exact lowercase alphanumeric extension, +never recursive descendants. Paths have at most 256 characters, contain no +empty, `.` or `..` segments, backslashes, colons, wildcards, control characters, +or segments ending in a dot or space. Directory selectors have at most 64 +matches after scanning at most 4096 directory entries. The native reader must reject symlinks and paths escaping the +selected root, bound each file to 1 MiB and the snapshot to 8 MiB, and expose +only scoped opaque handles in chunks of at most 128 KiB. A manifest cannot +authorize arbitrary paths or access an unselected source. + +Core offers `experimental.litheKeymapImport: {versions: [1], providers}` only +to a plugin-owned session with declared providers. The backend selects a +nonempty duplicate-free subset as `{version: 1, providers}`. A request to an +unselected provider is refused. `lithe/keymap/import` carries only +`{providerID, snapshotID}`. The snapshot is an opaque host-owned reference; +the provider never interprets it as a path. Core bounds the operation and +validates normalized results against the host's action catalog. Providers own +source formats and must not silently promote conditional or multi-stroke rules +to global shortcuts. The exact Core commands, limits and fixtures are in the +[Rust Core API](https://github.com/1lck/Lithe-IDEA/blob/preview/shared/contracts/rust-core-api.md#normalized-shortcut-import-validation). + +When granted individually, protocol 3 methods `lithe/host/keymapImport/listInputs` +and `lithe/host/keymapImport/readChunk` take `snapshotID` and `providerID`. +Listing returns `{files: [{id, path, byteCount}]}`; a read additionally takes +`fileID`, a nonnegative `offset` and `maximumBytes` from 1 through 131072 and +returns `{data}` containing base64 bytes. Paths are relative to the selected +root and file IDs are opaque. The host binds snapshots to their plugin, session +and provider, revokes them when the import or session ends, and rejects files +replaced or changed after capture. A new snapshot for that provider revokes the +previous one. Errors never disclose absolute paths. + +This is a foundation, not an available product extension: macOS implements the +scoped reader, but provider discovery, the product import workflow and Windows +source access are not implemented. Hosts must not advertise this capability +until they implement the complete workflow. No existing SDK advertises it. ## Package layout @@ -96,8 +161,9 @@ symbolic links. A host treats an installed package as read-only. | `settings` | User-adjustable plugin options (level 2 and later). | | `ui` | Host-rendered tool windows and commands (level 2 and later). | | `buildSystems` | Project-level build tools (level 3). | -| `toolDefinitions` | Plugin-owned tool policy ([Core foundation](toolchains.md)); requires `toolDefinitions: 1` and `executableResolution: 2`; native adapters are not connected. | +| `toolDefinitions` | Plugin-owned tool policy ([Core foundation](toolchains.md)); requires `toolDefinitions: 1` and `executableResolution: 2`; macOS hosts read-only discovery. | | `toolchainProviders` | Backend fact-provider declarations ([Core foundation](toolchains.md)); requires `toolchainProviders: 1`, backend/server-process capabilities and `toolchains.provide`. | +| `keymapImportProviders` | Independent shortcut import providers; requires `keymapImportProviders: 1`, `backend: 2`, `serverProcess: 1` and `keymaps.provide`. macOS provides access to explicitly selected sources; Windows does not yet advertise this capability. | Unknown fields are refused (`plugin.manifest.invalid`) so an older host rejects a package instead of ignoring part of it. At levels 1 and 2 a new @@ -295,6 +361,7 @@ contains a machine path, and the host decides where state lives. | `${pluginDir}` | Root of the installed, read-only package. It must be used at most once per value and be followed by a `/`-rooted path inside the package; that path may vary only by `${arch}`. | | `${stateDir}` | Writable directory the host keeps for this plugin's server and removes when the plugin is uninstalled. Not available in `validationArguments`. | | `${workspaceStateDir}` | Writable directory for the server's state of the current workspace. Not available in `validationArguments`, nor in a [plugin backend](#plugin-backend), which belongs to no workspace. | +| `${logDir}` | Absolute current-session `plugins//files/` directory, explicitly supplied by the host as `logDirectory`. Available for language servers and plugin backends, never in `validationArguments`. The host creates it in writable log storage, outside the installed package; Core does not derive it from package resources. | | `${arch}` | Host CPU architecture: `arm64` or `x86_64`. | | `${setting:}` | Current value of a declared [plugin setting](#plugin-settings-api-2). Not available in `validationArguments`, and never inside a package path. | @@ -302,10 +369,10 @@ contains a machine path, and the host decides where state lives. host selects the process with exactly one of `languageID` (a language server), `buildSystemID` (a build provider), `vcsProviderID` (a VCS provider), and `backend: true` (the [plugin backend](#plugin-backend)), passes `{pluginDirectory, -stateDirectory, workspaceStateDirectory?, architecture, settings?}`, and +stateDirectory, workspaceStateDirectory?, logDirectory?, architecture, settings?}`, and receives. `workspaceStateDirectory` may be omitted only when the selected process does not use `${workspaceStateDir}`; otherwise the request is -`plugin.launch.invalidRequest`. +`plugin.launch.invalidRequest`. The same error applies when a template uses `${logDir}` but `logDirectory` is absent, empty, or not absolute. | Field | Meaning | | --- | --- | @@ -314,7 +381,7 @@ process does not use `${workspaceStateDir}`; otherwise the request is | `validationArguments` | Expanded probe arguments. | | `runtimeRequirements` | `{tool, minimumVersion?}` for every runtime the host must discover, a discovered launch tool included, sorted by tool. A packaged runtime is not listed. Only the launch tool can carry a minimum version. | | `initializationOptions` | Expanded options; absent when the server declares none. | -| `requiredDirectories` | `state` and/or `workspaceState`: the directories the host must create before starting the server. | +| `requiredDirectories` | `state`, `workspaceState`, and/or `log`: the directories the host must create before starting the server. | | `requiredPackagePaths` | Sorted package-relative paths the launch points at. The host verifies each is inside the package and not a symbolic link before starting the server. | | `settings` | Effective value of every declared setting that produced this plan; absent when the plugin declares none. | | `buildProvider` | Present only for a `buildSystemID` selection: `{buildSystemID, executesProjectCode, mayUseNetwork, languageServer?}`. `languageServer` names the language whose server is the provider; the rest of the plan is then that server's plan, and the host reuses its running session instead of starting a second process. | @@ -513,9 +580,9 @@ hosts. Level 3 versions each section on its own. A level 3 manifest lists in | `debugAdapters` | [`debugAdapters[]`](#api-level-4-debug-adapters); v2 adds target arguments/environment | `1`, `2` | | `projectGenerators` | [`projectGenerators[]`](#project-generators): entries of the New Project window | `1` | | `backend` | [`backend`](#plugin-backend): the plugin's own long-lived process | `1`, `2` (adds [start without a page and workspace contexts](#backend-2-workspace-contexts)) | -| `toolDefinitions` | [Tool identities, layouts, discovery and four-layer candidates](toolchains.md) | `1` (Core only) | -| `toolchainProviders` | [Provider declarations and permission-bound answer validation](toolchains.md) | `1` (Core only) | -| `executableResolution` | [Shared executable candidates](#shared-executable-candidates-executableresolution-1); v2 adds tool-definition candidates without changing existing Build candidate sets | `1`, `2` (v2 Core only) | +| `toolDefinitions` | [Tool identities, layouts, discovery and four-layer candidates](toolchains.md) | `1` (Core; macOS read-only settings) | +| `toolchainProviders` | [Provider declarations and permission-bound answer validation](toolchains.md) | `1` (Core; macOS read-only settings) | +| `executableResolution` | [Shared executable candidates](#shared-executable-candidates-executableresolution-1); v2 adds tool-definition candidates without changing existing Build candidate sets | `1`, `2` (v2 Core; macOS read-only settings) | Version 1 of every capability means what its section means at level 2 (runtime launches, launch templates, `initializationOptions`, and so on), so @@ -603,6 +670,8 @@ Rules for publishing across levels and capability versions: | `ui.contribute` | `ui` is declared | | `ui.web` | a tool window shows [web content](#web-content-ui-2) | | `backend.launch` | `backend` is declared | +| `keymaps.provide` | `keymapImportProviders` is nonempty; permits offering mappings, not reading files by itself | +| `keymaps.readSelectedSources` | `keymapImportProviders` is nonempty; permits reading only declared files in an explicitly selected import source | | `credentials.store` | the backend uses the [credentials host service](#host-services-protocol-version-2); refused without a `backend` | | `workspace.metadata` | the backend uses the [workspace host service](#workspace-contexts-and-capability-discovery-protocol-version-3); refused without `backend` 2 | | `editor.actions` | the backend registers independent editor actions; requires `backend` 2 and `documents.read`, grants only `editor/registerActions`, not document edits or domain execution | diff --git a/contract/v1/fixtures/command-plans.json b/contract/v1/fixtures/command-plans.json index 5c1994c..61c7316 100644 --- a/contract/v1/fixtures/command-plans.json +++ b/contract/v1/fixtures/command-plans.json @@ -855,6 +855,70 @@ "status": "rejected", "code": "plugin.launch.unavailable" } + }, + { + "name": "node runtime receives an explicit session plugin log directory", + "plugin": "node-runtime.plugin.json", + "languageID": "fixture", + "operation": "languageServerLaunchPlan", + "request": { + "pluginDirectory": "/plugins/node/versions/1.0.0", + "stateDirectory": "/state/node", + "workspaceStateDirectory": "/state/node/workspaces/abc", + "architecture": "x86_64", + "logDirectory": "/logs/sessions/current/plugins/io.example/files" + }, + "expected": { + "status": "ok", + "plan": { + "launch": { + "kind": "runtime", + "tool": "node", + "entry": "language-server/lib/server.js", + "minimumVersion": "18.17", + "versionArguments": [ + "--version" + ] + }, + "arguments": [ + "--max-old-space-size=2048", + "--require=/plugins/node/versions/1.0.0/language-server/lib/preload.js", + "/plugins/node/versions/1.0.0/language-server/lib/server.js", + "--stdio", + "--log=/logs/sessions/current/plugins/io.example/files/server.log" + ], + "validationArguments": [], + "runtimeRequirements": [ + { + "tool": "node", + "minimumVersion": "18.17" + } + ], + "initializationOptions": { + "stubs": [ + "/plugins/node/versions/1.0.0/language-server/stubs/x86_64" + ], + "telemetry": false + }, + "requiredDirectories": [ + "log" + ], + "requiredPackagePaths": [ + "language-server/lib/preload.js", + "language-server/lib/server.js", + "language-server/stubs/x86_64" + ], + "settings": { + "heapMB": 2048, + "logLevel": "info", + "telemetry": false + } + } + }, + "manifestArguments": [ + "--stdio", + "--log=${logDir}/server.log" + ] } ] } diff --git a/contract/v1/fixtures/keymap-import-provider.plugin.json b/contract/v1/fixtures/keymap-import-provider.plugin.json new file mode 100644 index 0000000..0cafa02 --- /dev/null +++ b/contract/v1/fixtures/keymap-import-provider.plugin.json @@ -0,0 +1,18 @@ +{ + "schemaVersion": 2, + "id": "dev.lithe.fixture.import", + "displayName": "Shortcut import fixture", + "version": "1.0.0", + "pluginAPIVersion": 3, + "requires": {"keymapImportProviders": 1, "backend": 2, "serverProcess": 1}, + "vendor": {"id": "org.example", "displayName": "Fixture publisher", "signatureRequirement": "publisherPackage"}, + "permissions": ["backend.launch", "keymaps.provide", "keymaps.readSelectedSources"], + "keymapImportProviders": [ + {"id": "first", "displayName": "First editor", "recognition": [{"kind": "file", "path": "bindings.json"}, {"kind": "root", "base": "userConfig", "components": ["FirstEditor", "User"]}, {"kind": "root", "base": "userConfig", "components": ["FirstEditor", "User", "profiles", "*"]}], "inputs": [{"kind": "file", "path": "bindings.json"}]}, + {"id": "second", "displayName": "Second editor", "recognition": [{"kind": "directory", "path": "keymaps"}], "inputs": [ + {"kind": "file", "path": "options/map.xml"}, + {"kind": "directory", "path": "keymaps", "extension": "xml"} + ]} + ], + "backend": {"launch": {"kind": "packaged", "path": "bin/import-provider"}, "arguments": ["--stdio"]} +} diff --git a/contract/v1/plugin-manifest.schema.json b/contract/v1/plugin-manifest.schema.json index a344452..6c6f572 100644 --- a/contract/v1/plugin-manifest.schema.json +++ b/contract/v1/plugin-manifest.schema.json @@ -20,6 +20,7 @@ }, {"if": {"required": ["toolDefinitions"]}, "then": {"properties": {"pluginAPIVersion": {"minimum": 3}, "requires": {"required": ["toolDefinitions", "executableResolution"], "properties": {"executableResolution": {"const": 2}}}}}}, {"if": {"required": ["toolchainProviders"]}, "then": {"required": ["backend"], "properties": {"pluginAPIVersion": {"minimum": 3}, "requires": {"required": ["toolchainProviders", "backend", "serverProcess"], "properties": {"backend": {"const": 2}}}, "permissions": {"allOf": [{"contains": {"const": "toolchains.provide"}}, {"contains": {"const": "backend.launch"}}]}}}}, + {"if": {"required": ["keymapImportProviders"]}, "then": {"required": ["backend", "permissions"], "properties": {"pluginAPIVersion": {"minimum": 3}, "requires": {"required": ["keymapImportProviders", "backend", "serverProcess"], "properties": {"backend": {"const": 2}}}, "permissions": {"allOf": [{"contains": {"const": "keymaps.provide"}}, {"contains": {"const": "keymaps.readSelectedSources"}}, {"contains": {"const": "backend.launch"}}]}}}}, {"if": {"required": ["requires"], "properties": {"requires": {"required": ["settings"], "properties": {"settings": {"const": 2}}}}}, "else": {"properties": {"settings": {"items": {"$ref": "#/$defs/settingVersion1"}}, "settingActions": {"maxItems": 0}}}}, {"if": {"required": ["requires"], "properties": {"requires": {"required": ["languageServers"], "properties": {"languageServers": {"const": 2}}}}}, "else": {"properties": {"languageServers": {"items": {"properties": {"workspaceConfiguration": false}}}}}} ], @@ -50,6 +51,7 @@ "settings": { "description": "Required exactly when settings or settingActions is non-empty. Version 1 is the API level 2 section: integer, enum, and boolean options. Version 2 adds scope, effect, page, section, the multiSelect, text, file, and directory types, and settingActions.", "enum": [1, 2] }, "toolDefinitions": { "const": 1 }, "toolchainProviders": { "const": 1 }, + "keymapImportProviders": { "const": 1 }, "ui": { "description": "Version 2 adds web content; version 3 adds native controls; version 4 adds backend-only commands.", "enum": [1, 2, 3, 4] }, "vcsProviders": { "description": "Required exactly when vcsProviders is non-empty. The provider protocol itself is negotiated in initialize (shared/contracts/vcs-provider-v1.md).", "enum": [1] } } @@ -80,6 +82,8 @@ "editor.open", "preview.open", "toolchains.provide", + "keymaps.provide", + "keymaps.readSelectedSources", "browser.open", "window.messages", "terminal.create"] } @@ -94,6 +98,7 @@ }, "toolDefinitions": { "type": "array", "minItems": 1, "maxItems": 32, "items": { "$ref": "#/$defs/toolchainToolDefinition" } }, "toolchainProviders": { "type": "array", "minItems": 1, "maxItems": 8, "items": { "$ref": "#/$defs/toolchainProvider" } }, + "keymapImportProviders": { "type": "array", "minItems": 1, "maxItems": 8, "items": { "$ref": "#/$defs/keymapImportProvider" } }, "languages": { "type": "array", "minItems": 0, @@ -263,6 +268,43 @@ } }, "$defs": { + "keymapImportProvider": { + "type": "object", + "additionalProperties": false, + "required": ["id", "displayName", "recognition", "inputs"], + "properties": { + "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]{0,63}$" }, + "displayName": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[^\\u0000-\\u001f\\u007f]+$" }, + "recognition": { "type": "array", "minItems": 1, "maxItems": 16, "uniqueItems": true, + "items": { "$ref": "#/$defs/keymapImportRecognition" } }, + "inputs": { "type": "array", "minItems": 1, "maxItems": 16, "uniqueItems": true, + "items": { "$ref": "#/$defs/keymapImportInput" } } + } + }, + "keymapImportRecognition": { + "oneOf": [ + { "type": "object", "additionalProperties": false, "required": ["kind", "path"], + "properties": { "kind": { "enum": ["file", "directory"] }, "path": { "$ref": "#/$defs/keymapImportInput/properties/path" } } }, + { "type": "object", "additionalProperties": false, "required": ["kind", "base", "components"], + "properties": { "kind": { "const": "root" }, "base": { "const": "userConfig" }, + "components": { "type": "array", "minItems": 1, "maxItems": 8, + "contains": { "const": "*" }, "minContains": 0, "maxContains": 1, + "items": { "type": "string", "maxLength": 256, "pattern": "^(?:\\*|(?!.*[. ]$)[^/\\\\:*?\\u0000-\\u001f\\u007f]+)$" } } } } + ] + }, + "keymapImportInput": { + "type": "object", "additionalProperties": false, "required": ["kind", "path"], + "properties": { + "kind": { "enum": ["file", "directory"] }, + "path": { "type": "string", "minLength": 1, "maxLength": 256, + "pattern": "^(?!/)(?!.*(?:^|/)(?:\\.|\\.\\.)(?:/|$))(?!.*(?:^|/)[^/]*[. ](?:/|$))[^/\\\\:*?\\u0000-\\u001f\\u007f]+(?:/[^/\\\\:*?\\u0000-\\u001f\\u007f]+)*$" }, + "extension": { "type": "string", "pattern": "^[a-z0-9]{1,16}$" } + }, + "allOf": [ + { "if": { "properties": { "kind": { "const": "directory" } } }, "then": { "required": ["extension"] } }, + { "if": { "properties": { "kind": { "const": "file" } } }, "then": { "not": { "required": ["extension"] } } } + ] + }, "debugLocalID": { "type": "string", "pattern": "^[a-z][a-z0-9._-]{0,63}$" @@ -1296,7 +1338,7 @@ } ] }, - "arguments": { "type": "array", "items": { "type": "string" } }, + "arguments": { "description": "API 2 launch templates include ${logDir}, supplied explicitly as absolute logDirectory by the host for the current session plugin files directory; never inferred from installation paths.", "type": "array", "items": { "type": "string" } }, "validationArguments": { "type": "array", "items": { "type": "string" } }, "runtimeDependencies": { "type": "array",