@prismer/runtime 1.9.1 → 1.9.6
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.
- package/CHANGELOG.md +178 -0
- package/README.md +363 -229
- package/dist/cli.cjs +10269 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.js +10257 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +10603 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1643 -0
- package/dist/index.d.ts +1496 -1916
- package/dist/index.js +9702 -9177
- package/dist/index.js.map +1 -1
- package/package.json +31 -33
- package/dist/adapter-registry-EMZFKFYK.mjs +0 -8
- package/dist/adapter-registry-EMZFKFYK.mjs.map +0 -1
- package/dist/artifacts-uploader-N2HNBSTW.mjs +0 -159
- package/dist/artifacts-uploader-N2HNBSTW.mjs.map +0 -1
- package/dist/auto-register-M4YSPJS6.mjs +0 -204
- package/dist/auto-register-M4YSPJS6.mjs.map +0 -1
- package/dist/bin/prismer.js +0 -14988
- package/dist/chunk-BJTO5JO5.mjs +0 -11
- package/dist/chunk-BJTO5JO5.mjs.map +0 -1
- package/dist/chunk-JIEDFDVI.mjs +0 -148
- package/dist/chunk-JIEDFDVI.mjs.map +0 -1
- package/dist/chunk-NDNX2G6O.mjs +0 -68
- package/dist/chunk-NDNX2G6O.mjs.map +0 -1
- package/dist/chunk-POWV475F.mjs +0 -45
- package/dist/chunk-POWV475F.mjs.map +0 -1
- package/dist/chunk-VTFKZAUY.mjs +0 -68
- package/dist/chunk-VTFKZAUY.mjs.map +0 -1
- package/dist/dispatch-mux-MW4HHICS.mjs +0 -8
- package/dist/dispatch-mux-MW4HHICS.mjs.map +0 -1
- package/dist/dispatch-rpc-J7H6KC2V.mjs +0 -78
- package/dist/dispatch-rpc-J7H6KC2V.mjs.map +0 -1
- package/dist/fs-rpc-OSHJZYK6.mjs +0 -58
- package/dist/fs-rpc-OSHJZYK6.mjs.map +0 -1
- package/dist/heartbeat-loop-H2LAI3V5.mjs +0 -95
- package/dist/heartbeat-loop-H2LAI3V5.mjs.map +0 -1
- package/dist/index.d.mts +0 -2063
- package/dist/index.mjs +0 -8807
- package/dist/index.mjs.map +0 -1
- package/dist/mode-b-DXJ7FJAL.mjs +0 -152
- package/dist/mode-b-DXJ7FJAL.mjs.map +0 -1
- package/dist/registry-ZYU2HDFL.mjs +0 -12
- package/dist/registry-ZYU2HDFL.mjs.map +0 -1
- /package/{dist → assets}/icon +0 -0
- /package/{dist → assets}/smallicon +0 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Changelog — @prismer/runtime
|
|
2
|
+
|
|
3
|
+
## Unreleased — Origin Adapter framework + memory-curation skill
|
|
4
|
+
|
|
5
|
+
Doc 26 §4 Phase 2 + doc 27 rev 5 §3. Daemon-side asset producers behind a
|
|
6
|
+
uniform OriginAdapter SPI; agent-driven memory curation guidance loaded into
|
|
7
|
+
the four host adapters.
|
|
8
|
+
|
|
9
|
+
### Added — **Origin Adapter framework** (`daemon/asset/origin/`)
|
|
10
|
+
- **SPI** (`spi.ts`): `OriginAdapter` interface with `kind: 'upload' | 'drop-folder' | 'agent-gen'`, `observe / identifySource / fetch` decomposition. Identification stays separate from byte fetch so the outbox can dedupe before paying read cost.
|
|
11
|
+
- **Outbox** (`outbox.ts`): SQLite-backed pending-upload queue mirroring `daemon/memory/outbox.ts` shape — Zod validation → dead-letter, `idempotencyKey = sha256(originKind|wid|sourceRef|observedAt)` UNIQUE, `claimNext()` transactional, `recordFailure(maxAttempts)` auto-spills to dead-letter. Restart-resume-friendly (rows persist across daemon process death).
|
|
12
|
+
- **Drop-folder adapter** (`drop-folder.ts`): polls `~/.prismer/workspaces/<wid>/drop/` (1s tick, recursive), enqueues observations, on success moves files to `uploaded/`, on persistent failure to `upload-failed/`. Mime detected by extension.
|
|
13
|
+
- **Agent-gen adapter** (`agent-gen.ts`): synchronous handler `handleAssetWrite(body, { cloud })` for the new `POST /local/asset/write` RPC. Daemon writes under owner identity; `sourceAgentImUserId` is a passenger metadata field (decoupled from auth — no dependency on Line B B1 POST permission alignment).
|
|
14
|
+
- **Web-upload adapter** (`web-upload.ts`): cloud-side marker class for SPI symmetry. Throws if invoked daemon-side; the actual upload is the cloud `POST /api/im/assets` handler.
|
|
15
|
+
- **Upload runner** (`upload-runner.ts`): generic worker draining the outbox, dispatching to adapters by `originKind`. Hooks `onUploadSuccess` / `onUploadFailure` on the adapter (drop-folder uses these for the uploaded/upload-failed file moves).
|
|
16
|
+
- **`POST /local/asset/write` route** (`local-server.ts`): wires `onAssetWrite` sink alongside `onDispatch` / `attachMemory`. Returns `{ assetId, contentHash, prismerUri: 'prismer://workspace/<wid>/asset/<contentHash>' }`. 400/502/501 error contract.
|
|
17
|
+
|
|
18
|
+
### Added — **memory-curation skill** (doc 27 rev 5 §3)
|
|
19
|
+
- **Codex adapter** (`adapters/codex/index.ts`): `buildCodexPrompt()` extracted as pure function; injects `MEMORY_CURATION_SKILL_TEXT` as system-prompt preamble (Codex has no native skill framework).
|
|
20
|
+
- **Embedded skill module** (`src/skills/memory-curation.ts`): regenerated from canonical `sdk/prismer-cloud/skill/memory-curation.md` via `scripts/regenerate-memory-curation-skill.cjs` (also handles the openclaw / Python / CC plugin copies).
|
|
21
|
+
|
|
22
|
+
### Tests
|
|
23
|
+
- 44 new vitest assertions across 8 new test files: `origin-spi`, `origin-outbox`, `origin-drop-folder`, `origin-agent-gen`, `local-server-asset-write`, `origin-web-upload`, `skill-memory-curation-sync`, `codex-adapter-skill-injection`.
|
|
24
|
+
- Acceptance criteria covered: doc 26 §5 L2-T1 (100-burst, dead-letter=0), L2-T2 (restart resume), L2-T3 (RPC sync return).
|
|
25
|
+
- Total runtime suite: 192 → 234 passing, 0 failures.
|
|
26
|
+
|
|
27
|
+
### Cookbook
|
|
28
|
+
- `docs/cookbook/m-asset-origin-drop-folder.md`
|
|
29
|
+
- `docs/cookbook/m-asset-origin-agent-gen.md`
|
|
30
|
+
|
|
31
|
+
### Phase staging — what this PR does NOT yet wire
|
|
32
|
+
- **Daemon `runner.ts` does not yet construct `OriginOutbox` / `DropFolderAdapter` / `UploadRunner`.** The SPI + handlers ship here; the runner-level wiring + a concrete `CloudUploadClient` (FormData wrapper around the existing `CloudClient`) land in a follow-up PR alongside the rest of doc 26 §4 Phase 2 production-enable work.
|
|
33
|
+
- `POST /local/asset/write` returns **501 `asset_write_unavailable`** in production until that PR wires the `onAssetWrite` sink on `LocalServer`.
|
|
34
|
+
- Acceptance criteria (L2-T1, L2-T2, L2-T3) are validated against `fakeCloud` in unit tests; an end-to-end cookbook run against the live `POST /api/im/assets` handler is part of the follow-up.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Unreleased — CLI hardening pass
|
|
39
|
+
|
|
40
|
+
User-reported "大量空洞" — full audit of the 16-verb CLI surface. No new daemon
|
|
41
|
+
behavior; existing commands now fail closed and emit consistent JSON when
|
|
42
|
+
`--json` is set.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **`--json` was dead code on every subcommand.** `applyCommonFlags` stripped
|
|
47
|
+
`--json` from argv before commander parsed per-command options, so
|
|
48
|
+
`if (opts.json) printJson(...)` branches in setup/status/workspace/sandbox/
|
|
49
|
+
asset/cookbook/chat never fired. UI mode was set, but the pretty branches
|
|
50
|
+
silently no-op'd in JSON mode and stdout came out empty (e.g.
|
|
51
|
+
`prismer status --json` exit 0 with no body). Now we keep `--json` in argv
|
|
52
|
+
and declare it on every subcommand that prints — empty-output paths are gone.
|
|
53
|
+
- **`prismer setup --cloud 0.0.0.0:3000` accepted bare host:port and exploded
|
|
54
|
+
with raw Zod JSON six stack frames later.** `normalizeCloudUrl()` now
|
|
55
|
+
auto-prefixes `http://` for bare host shapes and rejects everything else
|
|
56
|
+
with a single-line error. The same helper is wired into `pair`, `setup`,
|
|
57
|
+
and `config set cloud_api_base`.
|
|
58
|
+
- **`exitWithError` is JSON-aware.** When the UI is in JSON mode it emits
|
|
59
|
+
`{ok:false, error:{code, message}}` to stdout (with stable codes like
|
|
60
|
+
`invalid_cloud_url`, `config_missing`, `task_create_failed`) instead of
|
|
61
|
+
the pretty `Error: …` line. Every action handler now goes through a
|
|
62
|
+
`runAction(fn, { code })` wrapper that catches throws — including the
|
|
63
|
+
`loadConfig()` "Config not found" throw that previously bubbled to
|
|
64
|
+
commander's default handler with a `prismer:` prefix.
|
|
65
|
+
- **Daemon ignored application-level `AUTH_FAILED`.** Cloud sends an
|
|
66
|
+
application error frame (not WS close 4001) when the post-handshake API
|
|
67
|
+
key lookup fails; the runner only logged it and stayed alive forever with
|
|
68
|
+
a stale pid file. Now the runner detects `AUTH_FAILED` / `AUTH_REQUIRED`
|
|
69
|
+
/ `auth_invalid` payload codes, emits `auth-failed`, and `daemon run`'s
|
|
70
|
+
handler stops the runner, clears the pid file, and exits 2.
|
|
71
|
+
- **`memory stats` returned exit 0 when the gateway was unavailable** even
|
|
72
|
+
though `delete` and `sync` both exit 1 in the same condition. All five
|
|
73
|
+
memory subcommands now consistently set `process.exitCode = 1` when the
|
|
74
|
+
gateway is missing.
|
|
75
|
+
- **`task create` printed `(0): fetch failed`.** Network errors now render
|
|
76
|
+
as `(network error): …` so the `0` HTTP status doesn't leak.
|
|
77
|
+
- **`daemon restart` dropped `--port` / `--foreground` / `--no-local-server`
|
|
78
|
+
on the floor.** Restart now forwards the flags through to `start`.
|
|
79
|
+
- **`daemon logs --tail` read the entire log file into memory.** Now seeks
|
|
80
|
+
from the end in 64 KiB chunks until enough newlines are collected — works
|
|
81
|
+
on the multi-MB logs that long-lived hosts accumulate.
|
|
82
|
+
- **`--no-start` was a redundant declaration** of commander's automatic
|
|
83
|
+
`--no-X` negation; removed.
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
|
|
87
|
+
- **`prismer config (path|show|set)`** — inspect or edit
|
|
88
|
+
`~/.prismer/config.toml` without hand-editing TOML. `show` redacts the
|
|
89
|
+
api_key by default; `--reveal` prints clear text. `set cloud_api_base
|
|
90
|
+
<url>` runs through `normalizeCloudUrl`, `set api_key sk-…` validates
|
|
91
|
+
the key format.
|
|
92
|
+
- **`prismer setup --check`** — dry-run mode. Validates `--cloud`, prints
|
|
93
|
+
the would-be config target and api-key source, and returns without
|
|
94
|
+
touching disk. JSON-friendly: `{ok:true, check:true, mode, cloud, …}`.
|
|
95
|
+
- **Default `--cloud` URL is `https://prismer.cloud`** (was
|
|
96
|
+
`https://cloud.prismer.dev`). The default is now visible in
|
|
97
|
+
`setup --help` / `pair --help`.
|
|
98
|
+
- **Stable error codes** on every CLI exit path so JSON consumers can
|
|
99
|
+
branch on `error.code` rather than parsing `error.message`.
|
|
100
|
+
|
|
101
|
+
### Internal
|
|
102
|
+
|
|
103
|
+
- New `src/cli/util.ts` helpers: `DEFAULT_CLOUD_BASE_URL`,
|
|
104
|
+
`normalizeCloudUrl(input)`, `runAction(fn, { code, sanitize })`, and the
|
|
105
|
+
JSON-aware `exitWithError(message, { code, exitCode, details })`.
|
|
106
|
+
- `applyCommonFlags` now keeps `--json` in argv (only `--quiet`,
|
|
107
|
+
`--no-color`, `--color` are still stripped). Every subcommand that
|
|
108
|
+
prints JSON now declares `.option('--json', …)` so commander parses it
|
|
109
|
+
consistently; pure-text commands (`daemon stop`, `pair`, `banner`,
|
|
110
|
+
`adapter install / remove`) declare `--json` as a passthrough so
|
|
111
|
+
`prismer --json daemon stop` doesn't error.
|
|
112
|
+
|
|
113
|
+
## v1.9.3 — 2026-05-07 — Hosted agents + Hermes long-running + 16-verb CLI
|
|
114
|
+
|
|
115
|
+
First published cut of `@prismer/runtime`. The runtime is the TS-only daemon that hosts Prismer agents on a user's Mac (under `launchd`) or inside a sandbox pod (as PID 1). One binary, one WebSocket to cloud, four in-process adapters, local SQLite mirror.
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
|
|
119
|
+
- **CLI: 8 new verbs** — `setup`, `banner`, `chat`, `cookbook`, `asset`, `sandbox`, `memory`, `events` (+ `events:stats`). Plus `workspace` (already shipped). Total surface is now **16 verbs**.
|
|
120
|
+
- `setup` — three onboarding paths (direct API key, `--token <jwt>` mints a daemon-scoped key via `POST /api/keys`, `--pair` delegates to QR/local-only). `--force` archives `local.db` → `local.db.<iso>.bak` when key/cloud/daemon_id changed.
|
|
121
|
+
- `chat` — `me` / `direct` / `messages` / `group (create|send|messages|remove-member)`. Sanitizes `sk-prismer-*` from error messages.
|
|
122
|
+
- `cookbook run` — 54release MVP regression suites (`status` / `im` / `task` / `group` / `asset` / `sandbox`); `--strict` treats skips as failure.
|
|
123
|
+
- `asset` — `list` / `upload` (multipart) / `download` / `get` / `by-hash`.
|
|
124
|
+
- `sandbox` — full `/api/sandboxes` CRUD + `runCmd` + log streaming.
|
|
125
|
+
- `memory` — daemon Memory Gateway first; falls back to read-only view of `~/.prismer/local.db` cache tables when gateway is absent (returns `error.code='memory_gateway_unavailable'` with a fix hint).
|
|
126
|
+
- `events` / `events:stats` — read `~/.prismer/para/events.jsonl` with `--limit / --agent-id / --session-id / --family / --type` filters; stats aggregates `byFamily / byType / byAgentId / bySessionId`.
|
|
127
|
+
- **Hermes long-running adapter** — autostart from `~/.hermes/profiles/<name>/config.yaml`, `/v1/runs` SSE consumer, full `tool.started` / `tool.completed` / `reasoning.available` event handling, Kanban + Goal mirror writeback onto `IMTask.metadata.bridge.hermes`. New runtime dep: `yaml@^2.8.2`. Hermes runs in the user's own Python venv; the runtime stays TS-only.
|
|
128
|
+
- **Hosted-agent pipeline** — `agent.host.declare` payload now stamps `daemonId` onto `IMAgentCard.metadata` so the workspace runtime view groups daemon-declared agents under the right device (instead of `__unbound__`). New `POST /v1/agents/install` LocalServer endpoint + `POST /:id/installAgent` sandbox-controller proxy enable cloud→daemon agent install RPC.
|
|
129
|
+
- **Static binding** — `PRISMER_HOSTED_AGENT_FILE` / `PRISMER_HOSTED_AGENT_JSON` / `PRISMER_STATIC_BINDING_REQUIRED` env vars consumed by `Runner.installStaticHostedAgentFromEnv`. K8s deployments seed one `agents` + `agent_profiles` row before the first `host.declare`.
|
|
130
|
+
- **Daemon-local shell route** — `runtimeRoute='shell'` (or `metadata.execution.kind === 'shell'`) routes through `shell-executor.ts`. Output capping (default 256 KiB, hard max 5 MiB), timeout enforcement (default 60s, hard max 30 min), `allowedWorkspaces` allow-list, `bash|zsh|sh` selector, structured error codes (`shell_disabled`, `shell_workspace_not_allowed`, `shell_command_required`, `shell_cwd_missing`, `shell_spawn_failed`, `shell_timeout`, `task_cancelled`, `shell_exit_nonzero`).
|
|
131
|
+
- **Stuck-task reaper** (60s) + **per-`taskId` dedupe** in `Runner.runningTasks` — closes the failure mode where cloud's `redispatchPending` fires the same task five times during a long LLM call and an upstream gateway half-closes a connection. Reaper aborts past `max(timeoutMs, 5min)` and emits `task.dispatch.reply {ok:false, error.code:'daemon_task_timeout'}`.
|
|
132
|
+
- **30s heartbeat re-declare** — re-sends `agent.host.declare` so cloud's 90s `sweepTimedOut()` doesn't flip status back offline.
|
|
133
|
+
- **Bridge + observability writeback** — dispatch PATCH-merges `result.metadata.hermes` onto `IMTask.metadata.bridge.hermes` (with `lastSyncedAt`) and writes `metadata.observability` with `{identity, memory, goals, lastSyncedAt}`.
|
|
134
|
+
- **8 new tests** under `test/` (Vitest):
|
|
135
|
+
- `asset.test.ts` (240 LOC) — CLI asset CRUD + envelope normalization.
|
|
136
|
+
- `chat-cli.test.ts` (135 LOC) — chat envelope + `sk-prismer` redaction.
|
|
137
|
+
- `cookbook.test.ts` (148 LOC) — suite parsing, summary, strict-skip.
|
|
138
|
+
- `dispatch.test.ts` (265 LOC) — `composePrompt` / `appendGoalContext` / `appendMemoryContext` + full `handleDispatch` path with mocked resolver/cache/ws/service-pool, plus error paths.
|
|
139
|
+
- `hermes-validate.test.ts` (88 LOC) — `hermesAdapter.validate` accepts/rejects port, apiKey, env-var key regex, provider URL.
|
|
140
|
+
- `sandbox.test.ts` (189 LOC) — sandbox CRUD + `runCmd` + logs streaming.
|
|
141
|
+
- `shell-executor.test.ts` (66 LOC) — `resolveShellConfig` defaults + clamping, `isShellDispatch` routing, `executeShellDispatch` enabled/disabled/timeout.
|
|
142
|
+
- `workspace-cli.test.ts` (89 LOC) — `prismer workspace (list|create|get|runtime|files)` envelope unwrapping.
|
|
143
|
+
- **2 opt-in Playwright e2e specs** under `e2e-playwright/specs/`:
|
|
144
|
+
- `workspace-hermes-daemon-runtime.spec.ts` — `WAVE7_HERMES_E2E=1 HERMES_API_KEY=...`. Drives full `pair → register Hermes agent → AgentProfile → host.declare → POST /api/im/tasks → Hermes reply` chain via product APIs only.
|
|
145
|
+
- `workspace-hermes-ui-flow.spec.ts` — `WAVE7_HERMES_UI_E2E=1`. Workspace UI: New Agent dialog → Hermes long-running role → direct session → IM message → reply persisted. Requires a real local daemon on `127.0.0.1:3210`.
|
|
146
|
+
|
|
147
|
+
### Evolved
|
|
148
|
+
|
|
149
|
+
- **5 existing CLI verbs reworked**: `adapter` (richer install/uninstall + per-adapter register, new `doctor` + `hooks`), `agent` (host register/list/unregister + 6-check `doctor`), `daemon` (`--foreground` for LaunchAgent + log tail-N + follow), `status` (consolidated banner + composite `/healthz` + `/api/im/me` + `local.db` row counts), `task` (tolerant envelope parsing for both `{task:{...}}` and flat shapes; reads `output` from `task.output` or `task.result.output`).
|
|
150
|
+
- **`Runner` + `dispatch` + `LocalServer`** gain hosted-agent paths (~940 lines added across the three files).
|
|
151
|
+
|
|
152
|
+
### Breaking
|
|
153
|
+
|
|
154
|
+
- **`package.json` `module` + `exports.import` now point at `dist/index.js`** (was `dist/index.mjs`, which `tsup` never actually emitted — bug fix that may affect bundlers respecting the old field).
|
|
155
|
+
- **`yaml@^2.8.2` is a new runtime dependency.** Used by the Hermes adapter to read/write `~/.hermes/profiles/<name>/config.yaml`.
|
|
156
|
+
- **`public/install.sh` strict-pins `@prismer/runtime@1.9.3`** (no caret). Stale 1.8.x installs will not auto-upgrade via curl re-run; users must explicitly re-run the installer.
|
|
157
|
+
- **LaunchAgent plist `ProgramArguments` now include `--foreground`.** Existing 1.8.x installs that loaded the plist without it would respawn-loop unless the user re-runs the installer (which does `bootout` + `bootstrap`).
|
|
158
|
+
- **`agent.host.declare` payload now requires `daemonId`** to be honored by cloud — without it the agent renders under `__unbound__` in the workspace runtime view.
|
|
159
|
+
- **`runtimeRoute` extended** with `'shell'` as a valid value of `TaskDispatchRequestPayload.runtimeRoute`. Cloud-side dispatchers that previously hard-coded `'agent'|'sandbox'` need a passthrough for `'shell'`.
|
|
160
|
+
- **`adapter list` JSON output now includes `installed: { package_manager, package_name, binary, version, installed_at } | null`** field. Strict-shape consumers need an update.
|
|
161
|
+
- **New CLI verbs may shadow user aliases**: `setup`, `banner`, `chat`, `cookbook`, `asset`, `sandbox`, `memory`, `events`, `events:stats`, `workspace`. None overlap with the prior 7-verb shape, but wrapper scripts that did `prismer <unknown>` and got a friendly error will now get command-not-found.
|
|
162
|
+
- **claude-code adapter closes child stdin** (`stdio: ['ignore', 'pipe', 'pipe']`). Operators relying on stdin-mode interactive use of `claude` via the daemon (which was never supported) see no behavior change; this only removes the 3s "no stdin" warning that made daemon dispatches appear hung on `claude` ≥ 2.1.128.
|
|
163
|
+
|
|
164
|
+
### Fixed
|
|
165
|
+
|
|
166
|
+
- **LaunchAgent KeepAlive respawn loop on macOS** — `install.sh` now passes `--foreground` to `prismer daemon start` in the plist `ProgramArguments`. Without it, `daemon start` spawned a child and exited, `launchd` saw exit-zero, KeepAlive=true respawned immediately. With `--foreground` the binary blocks in the polling loop until `config.toml` appears.
|
|
167
|
+
|
|
168
|
+
### Internal
|
|
169
|
+
|
|
170
|
+
- `module` / `exports.import` field correctness (`dist/index.js`).
|
|
171
|
+
- `qrcode@^1.5.4` runtime dep added (used by the QR pair flow).
|
|
172
|
+
- `eslint-plugin-react-hooks` added to dev tooling.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## v1.8.x — Pre-publish scaffolding
|
|
177
|
+
|
|
178
|
+
The runtime tree was scaffolded across the `feat/refactoring` branch. There were no published cuts before v1.9.3; the package landed at v1.9.3 directly when its public API surface stabilized.
|