@plurnk/plurnk-execs 1.3.12 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.env.defaults +4 -0
  2. package/README.md +78 -36
  3. package/SPEC.md +386 -260
  4. package/dist/BaseExecutor.d.ts.map +1 -1
  5. package/dist/BaseExecutor.js +12 -13
  6. package/dist/BaseExecutor.js.map +1 -1
  7. package/dist/ErrorDetail.d.ts +8 -0
  8. package/dist/ErrorDetail.d.ts.map +1 -0
  9. package/dist/ErrorDetail.js +25 -0
  10. package/dist/ErrorDetail.js.map +1 -0
  11. package/dist/Notice.d.ts +3 -0
  12. package/dist/Notice.d.ts.map +1 -0
  13. package/dist/Notice.js +2 -0
  14. package/dist/Notice.js.map +1 -0
  15. package/dist/RuntimeTag.d.ts +5 -0
  16. package/dist/RuntimeTag.d.ts.map +1 -0
  17. package/dist/RuntimeTag.js +28 -0
  18. package/dist/RuntimeTag.js.map +1 -0
  19. package/dist/SubprocessExecutor.d.ts +1 -1
  20. package/dist/SubprocessExecutor.d.ts.map +1 -1
  21. package/dist/SubprocessExecutor.js +67 -20
  22. package/dist/SubprocessExecutor.js.map +1 -1
  23. package/dist/advertise.d.ts +4 -0
  24. package/dist/advertise.d.ts.map +1 -1
  25. package/dist/advertise.js +4 -2
  26. package/dist/advertise.js.map +1 -1
  27. package/dist/discover.d.ts.map +1 -1
  28. package/dist/discover.js +52 -84
  29. package/dist/discover.js.map +1 -1
  30. package/dist/index.d.ts +8 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +11 -6
  33. package/dist/index.js.map +1 -1
  34. package/dist/policy.d.ts +1 -0
  35. package/dist/policy.d.ts.map +1 -1
  36. package/dist/policy.js +21 -20
  37. package/dist/policy.js.map +1 -1
  38. package/dist/runtime.js +1 -1
  39. package/dist/runtime.js.map +1 -1
  40. package/dist/tokenizeArgv.d.ts +5 -0
  41. package/dist/tokenizeArgv.d.ts.map +1 -0
  42. package/dist/tokenizeArgv.js +69 -0
  43. package/dist/tokenizeArgv.js.map +1 -0
  44. package/dist/types.d.ts +6 -4
  45. package/dist/types.d.ts.map +1 -1
  46. package/package.json +11 -15
  47. package/dist/TelemetryEvent.d.ts +0 -18
  48. package/dist/TelemetryEvent.d.ts.map +0 -1
  49. package/dist/TelemetryEvent.js +0 -2
  50. package/dist/TelemetryEvent.js.map +0 -1
package/.env.defaults CHANGED
@@ -10,3 +10,7 @@
10
10
  # Absent ⇒ the runtime stays enabled. e.g.:
11
11
  # PLURNK_EXECS_PYTHON=0
12
12
  # PLURNK_EXECS_BC=0
13
+
14
+ # Maximum source characters retained from third-party diagnostics in a failed
15
+ # operation's model-facing Problem detail. Empty/unset leaves them unbounded.
16
+ PLURNK_EXECS_ERROR_DETAIL_LIMIT=512
package/README.md CHANGED
@@ -1,17 +1,32 @@
1
1
  # plurnk-execs
2
2
 
3
- Framework + contract for `@plurnk/plurnk-execs-*` runtime executor packages. Consumed by [plurnk-service](https://github.com/plurnk/plurnk-service)'s `exec` scheme for EXEC op dispatch.
3
+ Lean framework and contract for `@plurnk/plurnk-execs-*` runtime packages. Core
4
+ uses it to discover EXEC tags, admit calls, and stream each runtime's output
5
+ under its own tag-addressed scheme. The default service manifest, not this
6
+ framework, owns the standard installed leaf set ({§bundled-set}).
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ npm install @plurnk/plurnk-execs
12
+ ```
13
+
14
+ Install executor leaves beside the framework to assemble a standalone host.
15
+ Any compatible third-party leaf remains discoverable without changing the
16
+ framework or service.
4
17
 
5
18
  ## Documentation
6
19
 
7
- - [`SPEC.md`](./SPEC.md) — the authoritative author-facing contract. This README is the orientation and the how-to.
8
- - Constellation: [plurnk-grammar](https://github.com/plurnk/plurnk-grammar) (EXEC AST), [plurnk-providers](https://github.com/plurnk/plurnk-providers), [plurnk-schemes](https://github.com/plurnk/plurnk-schemes), [plurnk-mimetypes](https://github.com/plurnk/plurnk-mimetypes) (the reference family this one mirrors).
20
+ - [`SPEC.md`](./SPEC.md) — authoritative executor author and consumer contract.
21
+ - [plurnk-contracts](https://github.com/plurnk/plurnk-service/tree/main/plurnk-contracts) — EXEC AST and shared runtime-neutral contracts.
22
+ - [plurnk-schemes](https://github.com/plurnk/plurnk-service/tree/main/plurnk-schemes) — universal operation results and derived output-scheme contract.
9
23
 
10
24
  ## Write an executor
11
25
 
12
- Ship an executor by publishing a package — **under any scope** (`@acme/whatever`; discovery keys on `plurnk.kind`, not the `@plurnk` scope) — that declares its runtime tags and default-exports a `BaseExecutor` subclass.
26
+ Publish a package under any scope with `plurnk.kind === "exec"`, one or more
27
+ runtime declarations, and a default-exported `BaseExecutor` subclass.
13
28
 
14
- ### 1. Declare tags in `package.json`
29
+ ### Declare runtime tags
15
30
 
16
31
  ```json
17
32
  {
@@ -21,58 +36,85 @@ Ship an executor by publishing a package — **under any scope** (`@acme/whateve
21
36
  {
22
37
  "name": "cobol",
23
38
  "glyph": "🗄",
24
- "example": "<<EXEC[cobol]:DISPLAY 'HI'.:EXEC",
25
- "documentation": "# cobol\n\nGnuCOBOL `cobc -xj`. Fixed-format source; the body is one program…"
39
+ "example": "<<EXEC[cobol]:DISPLAY 'HI'.:EXEC"
26
40
  }
27
41
  ]
28
42
  }
29
43
  }
30
44
  ```
31
45
 
32
- One package may claim many tags (the search sibling claims `search`/`news`/`images`/…); each `runtimes[]` entry registers independently. `glyph` is display; `example` and `documentation` are how the tag tells the model what it is — see [How the model sees your tag](#how-the-model-sees-your-tag).
46
+ One package may claim several tags; the consumer instantiates and probes each
47
+ tag independently. A name uses lowercase URI-scheme syntax
48
+ `[a-z][a-z0-9+.-]*`; `only` is reserved by runtime policy. `example` is a
49
+ compact verbatim `plurnk` snippet, and every line in it must be a complete
50
+ `<<`-delimited operation. A `docs/<tag>.md` file supplies the full reference
51
+ material. See {§executor-runtime-declaration}.
52
+
53
+ ### Implement the executor
54
+
55
+ | Runtime shape | Base class | Author-owned hooks |
56
+ | --------------------- | -------------------- | --------------------------------------------------------------------------------------- |
57
+ | Subprocess | `SubprocessExecutor` | `spawnArgs()` and normally `binary`; stdout/stderr, abort, env, and exit are inherited. |
58
+ | Logical or in-process | `BaseExecutor` | `channels`, `run()`, and optional `probe()` / `effect()`. |
33
59
 
34
- ### 2. Default-export a `BaseExecutor` subclass
60
+ `run()` receives only the inputs and consumer-owned sinks in
61
+ {§executor-sinks}. Return a universal result; expected failures carry RFC 9457
62
+ Problem Details and leave affected channels `errored`. Honor `signal`, write
63
+ only declared channels, and retain no state between runs.
35
64
 
36
- The framework instantiates **one executor per tag**, injecting `{ runtime, glyph }` (`ExecutorMetadata`) — branch on `this.runtime` when one class backs several tags. Two ways in:
65
+ `effect(target)` declares one admission fact for the consumer-canonical logical
66
+ target. `host` is proposal-gated; `read` and `pure` are automatically accepted.
67
+ The consumer preserves the fact rather than asking again after target
68
+ materialization. All three then use the same background stream path—automatic
69
+ acceptance is not a same-turn result. See {§executor-effect}.
37
70
 
38
- - **Subprocess runtimes** (the common case): subclass **`SubprocessExecutor`** and override one hook — `spawnArgs(runtime, command, target) → { cmd, args, useShell, stdin? }`. With no `target`, `command` is the inline program; with a `target`, run it as the program and hand `command` to `stdin` (plurnk-execs#15). You inherit stdout/stderr streaming, process-group abort, **env scoping**, and exit-code reporting. Override the `binary` getter so `probe()` checks it's on PATH (`null` = always available).
39
- - **Logical / in-process runtimes** (sqlite, search, wasm, jq): subclass **`BaseExecutor`** directly and implement:
40
- - `get channels()` — the output channels you write, each `{ mimetype, defaultState? }`.
41
- - `run(args) → ExecResult` — do the work, resolve `{ status }`. Never throw for an expected failure — `emit` telemetry and set the channel to `errored` instead.
42
- - `probe(signal?)` *(optional)* — `{ available, detail? }`; defaults to available. Override when you depend on an external binary or config (the `detail` is model-facing on a 501). If you spawn a child (or open a connection) to check, hand it `signal` so a resolved/timed-out probe reaps it — no stray write EPIPEs after teardown (plurnk-execs#16).
43
- - `effect(target)` *(optional)* — `"pure" | "read" | "host"`; the consumer maps it to its proposal policy (host → propose, read/pure → auto-run inline). Classify the **target only**, never the command. Defaults to `host` (the safe end).
71
+ ### Understand the target
44
72
 
45
- ### 3. What `worker` receives (`ExecArgs`) — sinks, never the substrate
73
+ The EXEC `(target)` slot is runtime-specific:
46
74
 
47
- `{ runtime, command, cwd, target, env, signal, write(channel, chunk, mimetype?), setState(channel, state), emit(event) }`. The executor gets sinks and honors `signal` — never the db, subscriptions, or wake machinery (those stay in the consumer). **`cwd`** is the workspace workspace (the process working directory); **`target`** is the parsed EXEC `(target)` slot — a referenced resource you interpret **per your tool's CLI**: a *data* runtime reads it as input with `command` as the program (jq `(file):filter`, sqlite `(db):SQL`); an *executable* runtime runs it as the program with `command` as its **stdin** (sh `(cmdline):stdin`, python `(script):stdin`). Resolved relative to `cwd`, `null` when the op names none (plurnk-execs#15). `write`'s optional `mimetype` stamps the channel with the real per-call output type. **`env`**, when the consumer scopes it, is exactly the environment a spawned child should see (the host's own secrets already dropped — never inherit `process.env` for model-run children yourself). Stay stateless across runs beyond your construction metadata.
75
+ | Runtime family | Typical mapping |
76
+ | -------------- | ----------------------------------------------------------------- |
77
+ | Data | Target is input; body is the program (`jq`, SQLite, WebAssembly). |
78
+ | Executable | Target is the program; body is its stdin (shell, Python). |
48
79
 
49
- ### How the model sees your tag
80
+ The consumer supplies both `cwd` and a resolved `target`; the leaf maps them
81
+ to its tool rather than reconstructing filesystem or scheme policy.
50
82
 
51
- Your tag documents itself to the model at two altitudes — you provide the content, the consumer decides how it reaches the model:
83
+ ### Address output
52
84
 
53
- - **`example`** — one line, the always-on form. The model uses it to reach for a simple tag without reading anything more. Store it as the **complete `<<`-delimited op** (`<<EXEC[tag]:…:EXEC`) — the consumer renders it verbatim, so an example missing the `<<` opener teaches the model a malformed op. Keep it to a single line — it's surfaced per available tag, so it's token-sensitive.
54
- - **`documentation`** — full markdown, depth on demand. The flags, modes, and gotchas the one-liner can't carry (sqlite's `:memory:`-vs-file, jq's `$ENV`, git's tokenized argv). Optional — provide it for non-trivial tags; the consumer can still derive a baseline from the rest of your registry entry (example, channels, effect, availability) when you don't.
85
+ The runtime tag is also the output scheme. A subprocess result is therefore
86
+ read at an address such as `sh:///1/2/3#stdout`, while a structured result may
87
+ be `sqlite:///1/2/3#results`. `exec://` is not an output address. Executors only
88
+ produce channels; the consumer owns storage and every later READ/FIND. See
89
+ {§executor-output-address}.
55
90
 
56
- Declare the two and a third-party tag gets the same self-documenting surface the first-party tags do. **How** the consumer renders the one-liner and serves the docs on demand is plurnk-service's concern, not this contract's — execs owns the two fields, the consumer owns the wiring.
91
+ ## Discovery and policy
57
92
 
58
- ## Discovery & trust
93
+ `discover(options?)` scans scoped and unscoped packages under the nearest
94
+ `node_modules`, applies trust before executable hooks, applies boot policy, and
95
+ returns `{ registry, packageAttributions, skipped, disabled }`. Tag collisions
96
+ fail hard. Static attribution is normalized once per represented package; an
97
+ executor may additionally decide per provider attempt whether its synchronous
98
+ `attributions(context)` hook returns no, one, or many opaque tags
99
+ ({§plugin-attribution}).
59
100
 
60
- `discover(options?)` scans **every installed package** under the nearest `node_modules` (walking up from `cwd` for workspace hoisting) — scope-agnostic — for `plurnk.kind === "exec"`, returning `{ registry, skipped, disabled }`.
101
+ | Policy | Result |
102
+ | ----------------------------------- | --------------------------------------------- |
103
+ | `PLURNK_EXECS_<TAG>=0` or `false` | Tag is not registered. |
104
+ | `PLURNK_EXECS_ONLY=<list>` | Only named tags remain; empty registers none. |
105
+ | `Policy.enabledAcross(tag, layers)` | Every layer must admit the tag. |
61
106
 
62
- - **Tag collisions are fail-hard.** Two packages claiming the same tag throws at discovery, naming both — deliberately stricter than plurnk-mimetypes' last-wins. A runtime tag is an executable dispatch key, so a third party silently shadowing `python` is exactly the failure we refuse to let ship quietly; the operator resolves it.
63
- - **Trust gate.** `discover()` honors **`PLURNK_PLUGINS_TRUSTED_ONLY`** (host posture, plurnk-service#229): unset/`""`/`0` → every package registers (default, no regression); any value → `@plurnk/*` always trusted plus a comma-separated allowlist (`1` = first-party only). An untrusted package is discovered but **not** registered and returned in `Discovery.skipped` for the consumer to note — never a crash.
64
- - **Runtime policy (enable/disable cascade).** `discover()` applies the operator kill-switch uniformly to every tag (SPEC §3.3): `PLURNK_EXECS_<TAG>=0` kills one tag, `PLURNK_EXECS_ONLY=a,b,c` is an allowlist (all else off). Disabled tags are **not** registered — returned in `Discovery.disabled`. It's purely subtractive, so it **cascades**: the consumer feeds a client-declared policy (same `PLURNK_EXECS_*` format) through `Policy.enabledAcross(tag, [serviceEnv, clientLayer])` and the client can only narrow — never re-enable what the service disabled. `disable all but search` → `PLURNK_EXECS_ONLY=search`.
107
+ Policy is subtractive; a downstream layer cannot restore a removed tag. See
108
+ {§executor-discovery}, {§executor-trust}, and {§executor-policy}.
65
109
 
66
110
  ## Exports
67
111
 
68
- - `BaseExecutor` — abstract base: `channels`, `run(args)`, optional `probe()` / `effect(target)`.
69
- - `SubprocessExecutor` — concrete base for subprocess runtimes; override `spawnArgs()` (and `binary`). Streaming + process-group abort + env scoping + exit code, inherited.
70
- - `discover(options?)` — the scope-agnostic registry scan (trust-gated + runtime-policy-gated, fail-hard on collision).
71
- - `Policy` — the runtime enable/disable resolver (SPEC §3.3): `Policy.isEnabled(tag, env?)`, `Policy.enabledAcross(tag, layers)`. Same parser the daemon and the consumer's per-workspace client layer share.
72
- - Contract types: `ExecArgs`, `ExecResult`, `ChannelDecl`, `ChannelState`, `ExecutorMetadata`, `RuntimeAvailability`, `Effect`, `ExecInfo`, `ExecRegistry`, `Discovery`, `DiscoverOptions`.
73
- - `TelemetryEvent`, `ContentOffset`, `LogCoordinate` — the `emit` sink's payload (mirror of grammar's telemetry envelope).
74
- - `SpawnArgs` — the `SubprocessExecutor.spawnArgs()` return shape for
75
- runtime-specific subclasses.
112
+ - `BaseExecutor`, `SubprocessExecutor`, and `SpawnArgs`.
113
+ - `discover`, `Policy`, `RuntimeTag`, and runtime discovery types.
114
+ - `Advertise`, a deprecated frozen 1.x compatibility helper with no current service path.
115
+ - `ErrorDetail` and `PLURNK_EXECS_ERROR_DETAIL_LIMIT`.
116
+ - Executor arguments, channel, availability, and effect types.
117
+ - `Results`, result types, and runtime-neutral Notice types.
76
118
 
77
119
  ## Tests
78
120