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
8 changes: 6 additions & 2 deletions contract.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
"source": {
"repository": "https://github.com/1lck/Lithe-IDEA",
"directory": "shared/contracts/plugins/v1",
"revision": "ced042289b5fbf9b6562eacd8da1f15428b5849d"
"revision": "7a24126eca3a26775daeb84db38ae04bec938d1a"
},
"files": [
{
"path": "README.md",
"sha256": "b30695e1c297812ad009937b78daf12f23b74d4c111c602614e393c25d5b5693"
"sha256": "ceb7d20d4c47fa48154813727c5c8e06563cf0fbdcd7d62ed45f36cb7c6913c3"
},
{
"path": "fixtures/backend-workspace.plugin.json",
Expand Down Expand Up @@ -194,6 +194,10 @@
"path": "fixtures/node-runtime.plugin.json",
"sha256": "3cbc80303dca796302653982d3f55fdc2072aea69a2517286a06e73fbc3c4499"
},
{
"path": "fixtures/plugin-logging.json",
"sha256": "ada9fe4b6a030466fc3ed8819683e645007720cf2310b709ff0c1873c80025a5"
},
{
"path": "fixtures/project-generator-templates/.gitattributes",
"sha256": "5ab88980b2cd4db0455bb2d5c3703db6668d28f254c41a92a6feff64604af9f7"
Expand Down
57 changes: 57 additions & 0 deletions contract/v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,63 @@ backend are `plugin.launch.unavailable`. Core never creates or
reads any of these paths. The command also serves API 1
manifests, whose arguments are returned unchanged.

#### Structured plugin diagnostics (logging protocol 1)

Every plugin-owned process may negotiate `capabilities.experimental.litheLogging`:
the host offers `{ "versions": [1] }` in `initialize`, and the SDK answers
`{ "version": 1 }`. The current official SDKs require that offer. This protocol
is independent of function, build, VCS, and toolchain protocol versions.

After successful initialization, the backend sends a JSON-RPC notification
`lithe/log`. Its params contain only these fields; unknown fields are rejected:

| Field | Meaning |
| --- | --- |
| `kind` | `operationFinished`, `operationFailed`, `degraded`, or `recovered`. These select INFO, ERROR, WARN, and WARN catalog events respectively. |
| `operation` | A registered function ID (up to 64 bytes), `function-call` for a longer registered ID, a supported `lithe/build/*` or `lithe/vcs/*` request method, `lithe/keymap/import`, or the SDK-owned stages `initialize`, `shutdown`, `notification`, `result-encoding`. Arbitrary display labels and user input are refused. |
| `durationMs` | Required for finished/failed, absent for degraded/recovered. Monotonic elapsed integer milliseconds, 0 through 9007199254740991. |
| `code` | Required except for finished. Stable category, 1–64 ASCII bytes from letters, digits, `._:/-$`; never an exception message, command line, query, or response body. |
| `count` | Optional affected item count, a nonnegative JSON safe integer. |
| `operationId` | Optional originating wire operation ID, 1–128 ASCII token bytes. The SDK obtains it from the call context. Core resolves a still-active wire request to its host operation; unknown or retired IDs provide no correlation. |

The notification must precede the corresponding RPC response so its wire ID
still belongs to the active host operation. Cancellation is an expected result
and does not produce an operation failure. The SDK owns automatic function and
domain request terminal facts; plugin handlers must not duplicate them. A
handler records a warning only when it actually recovers or returns a degraded
result. Frequently repeated read/preview/poll functions suppress successful
terminal facts; their failures remain observable.

Core supplies the installed plugin ID and routes these facts to
`plugins/<id>/host`. Plugins cannot provide an event ID, channel, severity,
message, arbitrary fields, error body, or writer path. The fixed catalog events
are `plugins.operation.finished`, `plugins.operation.failed`,
`plugins.operation.fallback_selected`, and `plugins.operation.recovery_finished`.
Malformed or unnegotiated input produces one category-only
`plugins.diagnostics.rejected` warning per process session, without retaining
the rejected payload. Ingress admits at most 600 facts per minute per session,
then uses the existing bounded Core handoff to the host's single writer;
overload increments its dropped counter. No polling timer or extra writer is
created. Fixture: `fixtures/plugin-logging.json`.

Severity follows the actual result. An operation that failed for the user is
ERROR, a recovery or fallback is WARN, and a useful successful user operation
is INFO. The source stream is not a severity: unclassified stderr is DEBUG
evidence, while standard `window/logMessage`/`window/showMessage` types retain
their explicit error/warning/info/detail semantics through separate catalog
events. Raw stdout is reserved for protocol frames. Source code, credentials,
SQL, HTTP headers/bodies, terminal history, protocol payloads, and command
output must never be included in diagnostic fields.

Native upstream files may use `${logDir}` only when the host supplies the
current local writable session directory. They remain separate files, not
structured catalog events. Keep metadata and caches in `${stateDir}` or
`${workspaceStateDir}`, and never infer a writable log path from `${pluginDir}`.
These runtime files do not modify installed resources or participate in build
resource reuse. Hosts without a local `logDirectory` reject launches requiring
`${logDir}`. The current macOS writer persists v2; Windows's writer migration
remains a separate platform task.

#### Plugin settings (API 2)

A plugin may declare options the user can change, one set per plugin and
Expand Down
28 changes: 28 additions & 0 deletions contract/v1/fixtures/plugin-logging.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"version": 1,
"offer": { "litheLogging": { "versions": [1] } },
"answer": { "litheLogging": { "version": 1 } },
"notifications": [
{ "kind": "operationFinished", "operation": "database.call", "durationMs": 18, "operationId": "7" },
{ "kind": "operationFailed", "operation": "database.call", "durationMs": 20, "code": "database.unavailable", "operationId": "8" },
{ "kind": "degraded", "operation": "database.call", "code": "result_size_limited", "count": 1000, "operationId": "9" },
{ "kind": "recovered", "operation": "database.call", "code": "connection_restored" }
],
"rejected": [
{ "kind": "operationFinished", "operation": "database.call", "durationMs": -1 },
{ "kind": "operationFailed", "operation": "database.call", "durationMs": 1, "code": "database.failed", "message": "private request contents" },
{ "kind": "operationFinished", "operation": "unregistered.query", "durationMs": 1 },
{ "kind": "degraded", "operation": "database.call", "code": "query failed with private data" },
{ "kind": "degraded", "operation": "database.call", "code": "limit", "count": 9007199254740992 }
],
"registeredFunctions": ["database.call"],
"plugin": "io.example.database",
"events": [
"plugins.operation.finished",
"plugins.operation.failed",
"plugins.operation.fallback_selected",
"plugins.operation.recovery_finished"
],
"levels": ["INFO", "ERROR", "WARN", "WARN"],
"channel": "plugins/io.example.database/host"
}
Loading