@wenathlan/saddle 1.8.6 → 1.8.8
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/README.md +115 -104
- package/api/service.js +1 -1
- package/core/errors.js +20 -0
- package/docs/actionsincident.md +4 -0
- package/docs/enginearchitecture.md +5 -4
- package/docs/libraryapi.md +3 -3
- package/docs/release.md +5 -5
- package/docs/releaseassets.md +1 -1
- package/docs/reorganization-1.8.8.md +31 -0
- package/docs/securityaudit-1.8.7.md +21 -0
- package/extension/manifest.json +1 -1
- package/index.js +2 -7
- package/library/public.js +1 -1
- package/mcp/server.js +1 -1
- package/package.json +3 -7
- package/runtime/retry.js +27 -0
- package/scrape/crawl.js +104 -0
- package/crawl/crawler.js +0 -29
- package/crawl/frontier.js +0 -34
- package/crawl/normalize.js +0 -14
- package/crawl/persistent.js +0 -13
- package/errors/taxonomy.js +0 -18
- package/readme.txt +0 -163
- package/retry/circuit.js +0 -15
- package/retry/policy.js +0 -12
- package/scrape/package-lock.json +0 -9397
- package/scrape/package.json +0 -1420
package/README.md
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
# Saddle
|
|
2
|
-
|
|
3
1
|
<p align="center">
|
|
4
2
|
<img src="docs/assets/saddlemark.svg" alt="Saddle" width="720" />
|
|
5
3
|
</p>
|
|
6
4
|
|
|
7
5
|
<p align="center">
|
|
8
6
|
<strong>Storage-backed jobs, scraping contracts and portable runners for Node.js.</strong><br/>
|
|
9
|
-
<strong>Binary computing
|
|
7
|
+
<strong>Binary computing engine, agent browser, scraper and packager.</strong><br/>
|
|
10
8
|
<a href="https://github.com/wenathlan/saddle/actions/workflows/ci.yml"><img src="https://github.com/wenathlan/saddle/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
|
|
11
|
-
<a href="https://github.com/wenathlan/saddle/releases/tag/v1.8.
|
|
9
|
+
<a href="https://github.com/wenathlan/saddle/releases/tag/v1.8.8"><img src="https://img.shields.io/badge/release-v1.8.8-d35d3d" alt="Release 1.8.8" /></a>
|
|
12
10
|
<a href="https://github.com/wenathlan/saddle/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-GPL--3.0-202a2f" alt="GPL 3.0 license" /></a>
|
|
13
11
|
</p>
|
|
14
12
|
|
|
15
|
-
> **Core idea:** storage is the durable side of the working set; the runner is replaceable; the artifact is the boundary. **Storage == Compute**
|
|
13
|
+
> **Core idea:** storage is the durable side of the working set; the runner is replaceable; the artifact is the boundary. **Storage == Compute** means that the same bytes can be retained or processed according to an explicit usage flag.
|
|
16
14
|
|
|
17
|
-
Saddle is a **JavaScript ESM engine** for jobs that move data between storage, a working set,
|
|
15
|
+
Saddle is a **JavaScript ESM engine** for jobs that move data between storage, a bounded working set, a caller-injected runner and durable artifacts. It is also a virtual machine published as a package: the caller can run it on GitHub Actions, Forgejo, Gitea, GitLab, Codeberg, Docker or another third-party compute surface. The engine does not require the operator's local machine, does not embed credentials and does not choose a mandatory cloud provider.
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
The canonical JavaScript package is `@wenathlan/saddle`. GitHub Packages npm, Maven and GHCR use the `wenathlan` owner namespace; NuGet and RubyGems retain their ecosystem package names. Older `@devthink`, `@iakadion` and `io.devthink` references in archived documents are historical records, not current package identities.
|
|
20
18
|
|
|
21
19
|
## Start here
|
|
22
20
|
|
|
@@ -35,160 +33,173 @@ const context = formatforagent(result, { maxchunksize: 2000, keypoints: 4 });
|
|
|
35
33
|
console.log(context.summary);
|
|
36
34
|
```
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
The deterministic examples and tests do not require network access or real credentials:
|
|
39
37
|
|
|
40
38
|
```bash
|
|
41
39
|
node examples/publicapi.js
|
|
40
|
+
npm test
|
|
42
41
|
```
|
|
43
42
|
|
|
44
|
-
##
|
|
43
|
+
## Progressive architecture
|
|
44
|
+
|
|
45
|
+
The project documentation follows a progressive arc. The foundation describes the storage and runner model; the engine describes the contracts that make the model executable; productization describes the package, extension, workflow and web surfaces.
|
|
46
|
+
|
|
47
|
+
### Foundation: storage, runners and working sets
|
|
48
|
+
|
|
49
|
+
Saddle treats a repository, bucket or object store as durable state and a third-party runner as a replaceable processor. GitHub Actions is one adapter, not the core. Forgejo, Gitea, GitLab, Codeberg, Docker and caller-owned runners can implement the same runner contracts.
|
|
50
|
+
|
|
51
|
+
The physical limit remains explicit: remote storage is not VRAM. A storage-to-RAM bridge can stage a bounded working set through a local filesystem, tmpfs, mmap, cache or caller-owned storage adapter, but it cannot remove network latency or create the bandwidth of a GPU bus. The engine exposes that distinction instead of hiding it behind marketing language.
|
|
52
|
+
|
|
53
|
+
The execution model is:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
repository or bucket -> runner working set -> process -> durable artifact
|
|
57
|
+
persistent state virtual processor published boundary
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The repository may act as a disk, a CI workflow may act as a function call, Pages may act as a static bus and a release artifact may act as the durable boundary. `workflow_dispatch`, `repository_dispatch` and HTTP adapters remain caller-configured interfaces.
|
|
61
|
+
|
|
62
|
+
### Engine: contracts instead of vendor lock-in
|
|
45
63
|
|
|
46
|
-
| Area |
|
|
64
|
+
| Area | Contracts shipped | Result |
|
|
47
65
|
| --- | --- | --- |
|
|
48
|
-
| Jobs | `engine`, `scheduler`, `inprocess` | `prepare
|
|
49
|
-
| Storage | local, chunked, content-addressed, S3-compatible, GitHub Contents
|
|
50
|
-
| Working set | memory bridge, modes, objects
|
|
51
|
-
| Scraping | robots, cache, extraction, semantic facts, schema
|
|
52
|
-
| Crawl | normalization, priority frontier, BFS crawler
|
|
53
|
-
| Browser | snapshots, tabs, frames, actions, fingerprint, session
|
|
66
|
+
| Jobs | `engine`, `scheduler`, `inprocess` | `prepare -> process -> sync -> cleanup` |
|
|
67
|
+
| Storage | local, chunked, content-addressed, S3-compatible, GitHub Contents and file-hosting adapters | durable objects, ranges, dedupe and sync |
|
|
68
|
+
| Working set | memory bridge, modes, objects and transforms | storage-to-compute and compute-to-storage flows |
|
|
69
|
+
| Scraping | robots, cache, extraction, semantic facts, schema and normalization | bounded text, metadata, links, controls and structured output |
|
|
70
|
+
| Crawl | normalization, priority frontier, BFS crawler and persistent frontier contracts | domain-aware bounded crawling |
|
|
71
|
+
| Browser | snapshots, tabs, frames, actions, fingerprint, session and replay contracts | caller-owned browser automation without a mandatory provider |
|
|
54
72
|
| Operations | queues, idempotency, saga, retry, circuit breaker, health and heartbeat | controlled execution and recovery |
|
|
55
73
|
| Protocols | JSON, NDJSON, SSE, blocks, API envelopes and MCP | transport-neutral messages |
|
|
56
|
-
| Delivery | manifests, workflow registry,
|
|
74
|
+
| Delivery | manifests, workflow registry, extension packaging and release assets | repeatable package and runner surfaces |
|
|
57
75
|
| Integrations | GitHub, GitLab, Forgejo, app lifecycle, command scopes and delivery adapters | caller-owned provider connectivity |
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
76
|
+
|
|
77
|
+
The root entry point is transport-neutral. Node filesystem, HTTP server, persistent sessions and Playwright are explicit subpaths or optional adapters. The library accepts caller-provided fetchers, browser transports, storage adapters, persistence, proxy pools, captcha evidence handlers, webhook secrets and remote credentials.
|
|
78
|
+
|
|
79
|
+
### Productization: one engine, many shells
|
|
80
|
+
|
|
81
|
+
The same contracts can be surfaced as an npm library, CLI, binary, Manifest V3 browser extension, webhook server, MCP transport, workflow action, container image, Maven package, NuGet package or RubyGem. These surfaces are adapters around the engine; they are not separate sources of truth.
|
|
62
82
|
|
|
63
83
|
## Public API
|
|
64
84
|
|
|
65
85
|
| Export | Purpose |
|
|
66
86
|
| --- | --- |
|
|
67
|
-
| `saddleurl` | choose fetch or injected browser path |
|
|
68
|
-
| `scrapeurl` | fetch one URL and extract |
|
|
69
|
-
| `scrapehtml` | extract from HTML without network |
|
|
87
|
+
| `saddleurl` | choose a fetch or caller-injected browser path |
|
|
88
|
+
| `scrapeurl` | fetch one URL and extract bounded content |
|
|
89
|
+
| `scrapehtml` | extract from HTML without network access |
|
|
70
90
|
| `extractcontent` | structured extraction |
|
|
71
|
-
| `serializeresult` | serialize
|
|
72
|
-
| `formatforagent` | summary, chunks
|
|
91
|
+
| `serializeresult` | serialize JSON, Markdown or XML results |
|
|
92
|
+
| `formatforagent` | summary, chunks and token count |
|
|
73
93
|
| `batchscrape` | bounded URL groups |
|
|
74
|
-
| `crawlurl` | crawl contract |
|
|
75
|
-
| `browseragent` | navigation, click, type
|
|
76
|
-
| `mcpserver` / `mcptransport` | MCP tools over JSONL
|
|
94
|
+
| `crawlurl` | crawl contract with domain and budget controls |
|
|
95
|
+
| `browseragent` | caller-owned navigation, click, type and screenshot actions |
|
|
96
|
+
| `mcpserver` / `mcptransport` | MCP tools over JSONL or HTTP |
|
|
77
97
|
| `nodeserver` | Web Request/Response handler |
|
|
98
|
+
| `engine` / `scheduler` | job lifecycle and runner dispatch |
|
|
99
|
+
| `release-assets` | SHA256SUMS, SBOM and provenance metadata for caller-selected artifacts |
|
|
78
100
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
## The execution model
|
|
82
|
-
|
|
83
|
-
Saddle coordinates contracts instead of hiding providers. A repo + CI runner is a virtual processor:
|
|
84
|
-
|
|
85
|
-
- Repo = Disk (persistent state)
|
|
86
|
-
- CI = CPU (workflow_dispatch = function call)
|
|
87
|
-
- Pages = Bus + CDN
|
|
88
|
-
- Static site = BIOS
|
|
89
|
-
- repository_dispatch = IPC
|
|
90
|
-
|
|
91
|
-
```js
|
|
92
|
-
import { engine, eventbus, inprocess, scheduler } from "@wenathlan/saddle";
|
|
93
|
-
import { localmemory } from "@wenathlan/saddle/memory-node";
|
|
94
|
-
import { localstorage } from "@wenathlan/saddle/storage-node";
|
|
95
|
-
const events = eventbus();
|
|
96
|
-
const run = engine({
|
|
97
|
-
storage: localstorage("./.saddle-data"),
|
|
98
|
-
memory: localmemory(),
|
|
99
|
-
scheduler: scheduler([inprocess()]),
|
|
100
|
-
events
|
|
101
|
-
});
|
|
102
|
-
const result = await run.run(
|
|
103
|
-
{ name: "example", input: { value: 42 } },
|
|
104
|
-
({ job }) => ({ jobid: job.id, ok: true })
|
|
105
|
-
);
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
The caller still chooses how to provide `fetcher`, browser transport, persistence, proxy pool, captcha solver, webhook secret and remote credentials. The root entry is transport-neutral; Node filesystem and HTTP adapters are explicit subpaths such as `@wenathlan/saddle/storage-node`, `@wenathlan/saddle/memory-node`, `@wenathlan/saddle/server-node`, `@wenathlan/saddle/sessions-file` and `@wenathlan/saddle/queue-persistent`. Saddle does not embed secrets, fixed hosts or a mandatory cloud vendor.
|
|
101
|
+
The complete export map is documented in [`docs/libraryapi.md`](docs/libraryapi.md). The product index is in [`docs/productindex.md`](docs/productindex.md), and runnable examples are in [`docs/usage.md`](docs/usage.md).
|
|
109
102
|
|
|
110
103
|
## Browser extension
|
|
111
104
|
|
|
112
|
-
|
|
105
|
+
The extension is a pure JavaScript Manifest V3 reference surface in [`extension/`](extension/). It contains a popup, service worker, isolated content bridge, read-only page-world `pagefacts` boundary, snapshot diffs and persisted window/tab/frame context for explicit resume.
|
|
113
106
|
|
|
114
107
|
```bash
|
|
115
108
|
# load the unpacked extension from chrome://extensions
|
|
116
109
|
ls extension/manifest.json extension/worker.js extension/content.js extension/popup.html
|
|
117
110
|
|
|
118
|
-
# build an isolated
|
|
111
|
+
# build an isolated artifact using the version supplied by the caller or release tag
|
|
119
112
|
npm run extension:build -- --output build/extension
|
|
120
113
|
```
|
|
121
114
|
|
|
122
|
-
The
|
|
115
|
+
The base permission set is `activeTab`, `scripting` and `storage`. It does not request broad host permissions, cookies, `webRequest`, debugger access or arbitrary page code execution. Optional host escalation remains caller-owned. Releases attach `saddle-extension-<version>.zip`; cross-browser profiles remain adapter work.
|
|
123
116
|
|
|
124
|
-
##
|
|
117
|
+
## Security boundaries
|
|
118
|
+
|
|
119
|
+
| Boundary | Policy |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| Credentials | injected by the caller or repository secret; never committed or printed |
|
|
122
|
+
| Network | HTTP/HTTPS targets are validated; private-target access remains caller policy |
|
|
123
|
+
| Crawling | robots rules, crawl delay, limits and budgets are explicit |
|
|
124
|
+
| Storage | adapters are replaceable; the core does not own a provider account |
|
|
125
|
+
| Runtime | Node-only filesystem, HTTP, Playwright and release metadata stay outside the transport-neutral root |
|
|
126
|
+
| Extension | page-world reads are bounded, token-correlated and read-only |
|
|
127
|
+
| Failure | retry, circuit breaker, idempotency and resume are configurable |
|
|
128
|
+
| Releases | version comes from the `vX.Y.Z` tag and must match `package.json` |
|
|
129
|
+
|
|
130
|
+
Version 1.8.8 preserves the dependency-free JavaScript scrape contracts and groups the related crawl context in `scrape/crawl.js`. The retry and circuit context now lives in `runtime/retry.js`, while scraper-specific error classification is part of `core/errors.js`. The root lockfile and security gates remain authoritative; see [`docs/reorganization-1.8.8.md`](docs/reorganization-1.8.8.md) for the ownership decisions and [`docs/securityaudit-1.8.7.md`](docs/securityaudit-1.8.7.md) for the preceding security baseline.
|
|
131
|
+
|
|
132
|
+
## Package surfaces and release automation
|
|
133
|
+
|
|
134
|
+
Workflows use the release tag and the local `releaseversion` action. They do not contain a manually edited version number. The action fetches the tag, checks out its commit and rejects a release when the tag version does not match the root `package.json`.
|
|
135
|
+
|
|
136
|
+
| Registry | Artifact | Workflow |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| GitHub Packages npm | `@wenathlan/saddle@<version>` | `publishgithubnpm.yml` |
|
|
139
|
+
| Public npmjs | `@wenathlan/saddle@<version>` | `publishnpmjs.yml` |
|
|
140
|
+
| GHCR | `ghcr.io/wenathlan/saddle:<version>` | `publishghcr.yml` |
|
|
141
|
+
| Maven | `io.wenathlan:saddle:<version>` | `publishmaven.yml` |
|
|
142
|
+
| NuGet | `Saddle.<version>.nupkg` | `publishnuget.yml` |
|
|
143
|
+
| RubyGems | `saddle <version>` | `publishrubygems.yml` |
|
|
144
|
+
|
|
145
|
+
Release assets are caller-selected and deterministic: `SHA256SUMS`, `sbom.cdx.json` in CycloneDX 1.5 shape and `provenance.intoto.jsonl` in an in-toto statement shape. The adapter does not publish, authenticate or choose a registry. The npm token previously sent in chat is compromised and must never be used; public npmjs publication uses only the owner-managed `NPM_TOKEN` repository secret.
|
|
146
|
+
|
|
147
|
+
## GitHub Pages web surface
|
|
148
|
+
|
|
149
|
+
The marketing site lives under [`web/`](web/) with a root-based TypeScript/React layout. It has no `client/` or `src/` subdirectory. Vite normalizes the base path and all visual assets resolve through a shared helper, so the same build works at `/` and `/saddle/`.
|
|
125
150
|
|
|
126
151
|
```bash
|
|
127
|
-
|
|
128
|
-
saddle
|
|
129
|
-
saddle runexample
|
|
130
|
-
saddle mcp
|
|
152
|
+
npm run web:check
|
|
153
|
+
VITE_BASE_PATH=/saddle npm run web:build:pages
|
|
131
154
|
```
|
|
132
155
|
|
|
133
|
-
|
|
156
|
+
Small public configuration and visual assets live under `web/public/`. The development collector is `web/public/debugcollector.js` and uses `/debuglogs`; it is not part of the production build. The obsolete `web/public/__manus__` directory is intentionally absent.
|
|
134
157
|
|
|
135
|
-
|
|
136
|
-
| --- | --- |
|
|
137
|
-
| Credentials | injected at runtime; never committed |
|
|
138
|
-
| Network | http/https validated; private targets blocked |
|
|
139
|
-
| Crawling | robots rules and crawl delay explicit |
|
|
140
|
-
| Storage | adapters replaceable |
|
|
141
|
-
| Runtime | Node HTTP isolated |
|
|
142
|
-
| Failure | retry, circuit breaker, idempotency configurable |
|
|
143
|
-
|
|
144
|
-
## Package surfaces
|
|
145
|
-
|
|
146
|
-
| Registry | Artifact | Workflow | Status |
|
|
147
|
-
| --- | --- | --- | --- |
|
|
148
|
-
| GitHub npm | `@wenathlan/saddle@1.8.6` | publishgithubnpm.yml | pending release |
|
|
149
|
-
| GHCR | `ghcr.io/wenathlan/saddle:1.8.6` and `latest` | publishghcr.yml | pending release |
|
|
150
|
-
| Maven | `io.wenathlan:saddle:1.8.6` | publishmaven.yml | pending release |
|
|
151
|
-
| NuGet | `Saddle.1.8.6.nupkg` | publishnuget.yml | pending release |
|
|
152
|
-
| RubyGems | `saddle 1.8.6` | publishrubygems.yml | pending release |
|
|
153
|
-
| npmjs | `@wenathlan/saddle@1.8.6` | publishnpmjs.yml | pending release |
|
|
154
|
-
|
|
155
|
-
## Development
|
|
158
|
+
## Development and release gates
|
|
156
159
|
|
|
157
160
|
```bash
|
|
158
161
|
npm ci
|
|
159
|
-
npm test
|
|
160
162
|
npm run check
|
|
161
163
|
npm run formatcheck
|
|
164
|
+
npm test
|
|
162
165
|
npm run pack:check
|
|
166
|
+
npm audit --audit-level=high
|
|
167
|
+
npm run web:check
|
|
168
|
+
VITE_BASE_PATH=/saddle npm run web:build:pages
|
|
163
169
|
```
|
|
164
170
|
|
|
165
|
-
|
|
171
|
+
The engine test suite is deterministic and does not require real credentials or network access. The release path is: update `package.json` and the manifest files, update `changelog.md`, run all gates, create `v<package-version>`, push the tag and create the GitHub release. Registry workflows then derive the same version from that release tag.
|
|
166
172
|
|
|
167
173
|
## Repository map
|
|
168
174
|
|
|
169
|
-
```
|
|
170
|
-
core/ errors, events and
|
|
175
|
+
```text
|
|
176
|
+
core/ engine errors, scrape error taxonomy, events, identifiers and hashing
|
|
171
177
|
domain/ jobs, artifacts, sessions and providers
|
|
172
178
|
memory/ working-set bridge, modes, objects and transforms
|
|
173
179
|
storage/ local, chunked, remote and file-hosting adapters
|
|
174
|
-
scrape/ robots, cache, extraction, schema and
|
|
175
|
-
crawl/ URL normalization, crawler and persistent frontier
|
|
180
|
+
scrape/ robots, cache, extraction, schema, normalization and grouped crawl contracts
|
|
176
181
|
queue/ queue, idempotency, saga and recovery
|
|
177
|
-
browser/ fingerprint, session and
|
|
178
|
-
|
|
179
|
-
mcp/ optional server and JSONL/HTTP transport
|
|
182
|
+
browser/ fingerprint, session, agent and Playwright adapter contracts
|
|
183
|
+
extension/ Manifest V3 reference surface and packager
|
|
180
184
|
protocol/ JSON, NDJSON, SSE and block serializers
|
|
181
|
-
workflow/ manifests, templates and registry
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
workflow/ manifests, templates and registry contracts
|
|
186
|
+
packager/ package and publication plans
|
|
187
|
+
release/ checksums, SBOM and provenance metadata
|
|
188
|
+
runtime/ engine orchestration, capability detection, worker and grouped retry context
|
|
189
|
+
web/ root-based static marketing site
|
|
190
|
+
tests/ deterministic engine and extension coverage
|
|
191
|
+
docs/ architecture, API, security, release and registry notes
|
|
185
192
|
```
|
|
186
193
|
|
|
187
|
-
|
|
194
|
+
The engine remains pure JavaScript ESM with JSDoc comments in English. The web surface is TypeScript/React, while the published library has no TypeScript build requirement and no hardcoded host, port or credential.
|
|
195
|
+
|
|
196
|
+
## Historical documentation
|
|
197
|
+
|
|
198
|
+
Earlier README snapshots remain in `docs/plans/README.md`, `docs/talks9/README.md` and `docs/talks9/README (2).md` as archival evidence. Their useful architecture ideas were consolidated here, while stale `@devthink`, `@iakadion`, `io.devthink`, Node 20/22, `client/src` and speculative provider quotas were not copied into the canonical contract.
|
|
188
199
|
|
|
189
200
|
## Current scope
|
|
190
201
|
|
|
191
|
-
Version 1.8.
|
|
202
|
+
Version 1.8.8 extends the 1.8.7 engine with conservative context regrouping. Browser binaries, provider credentials, n8n host registration, persistent databases, captcha solvers and production deployment remain caller-selected adapters. Future work should extend contracts without coupling the core to one forge, registry, browser or storage vendor.
|
|
192
203
|
|
|
193
204
|
## License
|
|
194
205
|
|
package/api/service.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* saddle service exposes universal routes without choosing hono fastify express or another server.
|
|
3
3
|
*/
|
|
4
|
-
import { crawl } from "../crawl
|
|
4
|
+
import { crawl } from "../scrape/crawl.js";
|
|
5
5
|
import { ratelimiter } from "./rate.js";
|
|
6
6
|
import { errorresponse, jsonresponse, sseresponse } from "./http.js";
|
|
7
7
|
import { assertpublicurl } from "./security.js";
|
package/core/errors.js
CHANGED
|
@@ -35,3 +35,23 @@ export function aserror(error, jobid) {
|
|
|
35
35
|
if (error?.name === "saddleerror") return error;
|
|
36
36
|
return saddleerror(`job ${jobid} failed`, { code: errorcodes.jobfailed, cause: error, details: { jobid } });
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
/** Scrape error presets keep status, recovery and retry decisions explicit. */
|
|
40
|
+
export const errorcatalog = Object.freeze({
|
|
41
|
+
timeout: { code: "E1001", statuscode: 504, retryable: true, recovery: "WAIT_AND_RETRY" },
|
|
42
|
+
connectionrefused: { code: "E1002", statuscode: 503, retryable: true, recovery: "WAIT_AND_RETRY" },
|
|
43
|
+
dns: { code: "E1003", statuscode: 503, retryable: true, recovery: "ROTATE_PROXY" },
|
|
44
|
+
ratelimited: { code: "E2001", statuscode: 429, retryable: true, recovery: "WAIT_AND_RETRY" },
|
|
45
|
+
forbidden: { code: "E2002", statuscode: 403, retryable: false, recovery: "REVIEW_ROBOTS_TXT" },
|
|
46
|
+
notfound: { code: "E2003", statuscode: 404, retryable: false, recovery: "STOP_CRAWLING" },
|
|
47
|
+
parse: { code: "E4002", statuscode: 422, retryable: false, recovery: "STOP_CRAWLING" },
|
|
48
|
+
captcha: { code: "E4003", statuscode: 403, retryable: false, recovery: "REVIEW_ROBOTS_TXT" },
|
|
49
|
+
session: { code: "E5001", statuscode: 401, retryable: true, recovery: "ROTATE_USER_AGENT" },
|
|
50
|
+
config: { code: "E6001", statuscode: 400, retryable: false, recovery: "STOP_CRAWLING" }
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/** Creates a stable scrape error with recovery metadata. */
|
|
54
|
+
export function webscrapeerror(kind, message, options = {}) { const preset = errorcatalog[kind] ?? errorcatalog.config; const error = new Error(message, { cause: options.cause }); error.name = "webscrapeerror"; error.code = options.code ?? preset.code; error.statuscode = options.statuscode ?? preset.statuscode; error.retryable = options.retryable ?? preset.retryable; error.recovery = options.recovery ?? preset.recovery; error.severity = options.severity ?? (error.statuscode >= 500 ? "high" : "medium"); error.details = options.details ?? {}; return error; }
|
|
55
|
+
|
|
56
|
+
/** Converts unknown failures into the stable scrape taxonomy. */
|
|
57
|
+
export function classifyerror(error) { if (error?.name === "webscrapeerror") return error; const message = String(error?.message ?? error); if (/timeout|aborted/i.test(message)) return webscrapeerror("timeout", message, { cause: error }); if (/dns|enotfound/i.test(message)) return webscrapeerror("dns", message, { cause: error }); return webscrapeerror("config", message, { cause: error }); }
|
package/docs/actionsincident.md
CHANGED
|
@@ -27,3 +27,7 @@ The repository metadata audit found `main` as the default branch and one open De
|
|
|
27
27
|
The repository homepage About editor was opened in the authenticated browser and saved with the canonical Saddle description. The repository API should now report that description instead of the placeholder `saddle`.
|
|
28
28
|
|
|
29
29
|
The six non-main Dependabot tips were preserved as `archive-dependabot-*` tags, their PRs were closed, and their branch refs were removed. The cleanup leaves `main` as the only active branch without deleting the archived commit objects.
|
|
30
|
+
|
|
31
|
+
The first `v1.8.6` release fan-out passed release validation, GitHub Packages npm, public npmjs, Maven, NuGet, RubyGems and extension packaging. GHCR alone failed because the Docker image ran `npm ci --omit=dev` against the root manifest, whose dev-only Vite peer graph is rejected by npm's strict peer resolver. The corrective Dockerfile now sets `NPM_CONFIG_LEGACY_PEER_DEPS=true` and passes `--legacy-peer-deps`; the GHCR workflow also checks out the release tag for both release and manual dispatch paths.
|
|
32
|
+
|
|
33
|
+
The corrected manual GHCR run [31706464064](https://github.com/wenathlan/saddle/actions/runs/31706464064) completed successfully, including the Docker build and push. The six registry outcomes for `v1.8.6` are therefore green: GitHub Packages npm, public npmjs, Maven, NuGet, RubyGems and GHCR. The Pages deployment remains green in [run 31705301175](https://github.com/wenathlan/saddle/actions/runs/31705301175).
|
|
@@ -12,17 +12,18 @@ The project has no `src` folder. The root is the map of the engine.
|
|
|
12
12
|
|
|
13
13
|
| folder | responsibility |
|
|
14
14
|
|---|---|
|
|
15
|
-
| `core` | errors,
|
|
15
|
+
| `core` | engine errors, scrape error taxonomy, ids, events, and hashing primitives |
|
|
16
16
|
| `domain` | jobs, sessions, artifacts, providers, and runtime records |
|
|
17
17
|
| `storage` | storage adapter contract, local backend, and checksums |
|
|
18
18
|
| `memory` | working set preparation, sync, and cleanup |
|
|
19
19
|
| `runners` | provider factories and deterministic scheduling |
|
|
20
|
-
| `runtime` | engine orchestration and
|
|
20
|
+
| `runtime` | engine orchestration, capability detection, worker boundary, retry and circuit context |
|
|
21
|
+
| `scrape` | single page extraction, response normalization, robots policy and grouped crawl context |
|
|
21
22
|
| `cli` | explicit command surface with local error handling |
|
|
22
23
|
| `tests` | local deterministic tests without credentials |
|
|
23
24
|
| `examples` | small runnable integration examples |
|
|
24
25
|
|
|
25
|
-
All internal file names are lowercase and contain no underscore or hyphen. Related logic stays grouped and each module remains small enough to reason about in isolation.
|
|
26
|
+
All internal file names are lowercase and contain no underscore or hyphen. Related logic stays grouped and each module remains small enough to reason about in isolation. Version 1.8.8 groups crawl URL normalization, traversal, frontier budgets and persistence in `scrape/crawl.js`; it does not merge distinct storage cache, job queue or browser session contracts merely because they use similar words.
|
|
26
27
|
|
|
27
28
|
## public contracts
|
|
28
29
|
|
|
@@ -64,7 +65,7 @@ The same contracts support paired modes. A mode can exist without its pair, and
|
|
|
64
65
|
| browser | headless job definition | capture and replay adapter |
|
|
65
66
|
| network | local deterministic job | remote provider and storage adapter |
|
|
66
67
|
|
|
67
|
-
The first cut implements library, cli, binary entry point, internal memory, and internal file. The other modes are extension points, not hardcoded promises.
|
|
68
|
+
The first cut implements library, cli, binary entry point, internal memory, and internal file. The other modes are extension points, not hardcoded promises. Version 1.8.8 preserves this boundary while reducing duplicated active context files.
|
|
68
69
|
|
|
69
70
|
## infrastructure rules
|
|
70
71
|
|
package/docs/libraryapi.md
CHANGED
|
@@ -28,7 +28,7 @@ The public API is designed around injected transports. Consumers can use the sam
|
|
|
28
28
|
| `workflowtriggers` / `triggermatch` | normalize and match manual, event, schedule and retry starts |
|
|
29
29
|
| `resumablerun` / `transitionrun` | recover remote run state through legal transitions |
|
|
30
30
|
| `extractsemantic` | expose bounded headings, landmarks, controls and links |
|
|
31
|
-
| `crawlfrontier` |
|
|
31
|
+
| `crawlfrontier` / `normalizeurl` / `persistentqueue` | grouped crawl context for URL normalization, traversal budgets and durable frontier state |
|
|
32
32
|
| `provenance` / `mergeprovenance` | link context chunks to source and retrieval evidence |
|
|
33
33
|
| `metricstore` | collect bounded counters and duration summaries |
|
|
34
34
|
| `authorize` | verify caller credentials through an injected verifier |
|
|
@@ -58,8 +58,8 @@ The `fetcher`, browser adapter, persistence adapter, proxy pool, captcha solver,
|
|
|
58
58
|
|
|
59
59
|
The package exposes explicit subpaths for `./browser`, `./bot`, `./captcha`, `./memory-engine`, and `./deploy`. Desktop, mobile, and n8n contracts are exported from the root entry; the root entry remains the complete JavaScript API for consumers that prefer one import.
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
The `./extension` subpath exposes browser-neutral message, snapshot and service-worker routing contracts. The concrete Manifest V3 files live in `extension/`; they are not imported by the core at runtime and do not require Chrome when the library is used as a Node package. The browser surface also exposes snapshots, tab/frame context, action results, bounded action batches and recording through `./browser`.
|
|
62
62
|
|
|
63
|
-
The current
|
|
63
|
+
The current release exposes `desktopmanifest`, `mobilemanifest`, `desktopadapter`, `mobileadapter`, `n8nnode`, `n8nmatch`, `n8nexecute`, `controlsurface`, `controlservice`, and `workerbridge`. These factories describe surface boundaries and invoke caller-owned handlers; they do not install a native toolkit, start an n8n server, create a dashboard, or store credentials.
|
|
64
64
|
|
|
65
65
|
The root entry is cross-runtime safe for the tested core contract. Filesystem, Node HTTP, persistent queue, file sessions, local memory and captcha evidence adapters remain available through explicit Node-only files or subpaths. `runtimecontract` reports the capabilities of the current global scope, while `memorystorage` provides a process-local backend for browser workers, Deno, Bun and deterministic tests.
|
package/docs/release.md
CHANGED
|
@@ -6,10 +6,10 @@ The release path is intentionally split into source validation, package validati
|
|
|
6
6
|
|
|
7
7
|
| step | owner | condition |
|
|
8
8
|
|---|---|---|
|
|
9
|
-
| package version | repository | `package.json`
|
|
9
|
+
| package version | repository | `package.json` matches the release tag, for example `1.8.8` |
|
|
10
10
|
| quality gate | GitHub Actions | `npm run pack:check` passes |
|
|
11
|
-
| tag | repository owner | tag `v1.8.
|
|
12
|
-
| GitHub release | repository owner | release `v1.8.
|
|
11
|
+
| tag | repository owner | tag `v1.8.8` points to the validated release commit |
|
|
12
|
+
| GitHub release | repository owner | release `v1.8.8` is created from the validated tag |
|
|
13
13
|
| GitHub Packages | GitHub Actions | `publishgithubnpm.yml`, `publishghcr.yml`, `publishmaven.yml`, `publishnuget.yml`, and `publishrubygems.yml` use `GITHUB_TOKEN` |
|
|
14
14
|
| public npmjs | owner-managed GitHub Actions secret | `publishnpmjs.yml` uses `NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}` and derives the version from the release tag or latest release in manual maintenance runs |
|
|
15
15
|
| extension zip | GitHub Actions | `buildextension.yml` derives the version from the release tag, validates the unpacked artifact and attaches `saddle-extension-<version>.zip` |
|
|
@@ -22,8 +22,8 @@ The npm token previously sent in chat is compromised and must not be used. GitHu
|
|
|
22
22
|
|
|
23
23
|
```text
|
|
24
24
|
npm run pack:check
|
|
25
|
-
git tag v1.8.
|
|
26
|
-
git push origin v1.8.
|
|
25
|
+
git tag v1.8.8
|
|
26
|
+
git push origin v1.8.8
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
The release-created event is the publication and extension-asset trigger for the release workflows. A dry-run verifies package shape and local tests, but cannot verify registry ownership, Trusted Publisher configuration, package scope authorization, package visibility or browser-store submission; those remain settings controlled by the owner.
|
package/docs/releaseassets.md
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Reorganization 1.8.8
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Version 1.8.8 applies the architecture skill's grouping rule to active JavaScript contracts. The objective is not to erase historical features or flatten unrelated domains. The objective is to give each correlated responsibility one canonical owner, reduce redundant folder boundaries, and preserve public names and deterministic behavior.
|
|
6
|
+
|
|
7
|
+
## Audit result
|
|
8
|
+
|
|
9
|
+
The active package contains a JavaScript runtime and a legacy TypeScript scrape tree. The TypeScript tree is not imported by the active root entry or package export map. It remains in place because it documents a broader historical surface and contains features that would require a separate compatibility decision before conversion to the current JavaScript library boundary.
|
|
10
|
+
|
|
11
|
+
| Context | Previous ownership | Version 1.8.8 owner | Decision |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| Crawl acquisition | `crawl/normalize.js`, `crawl/frontier.js`, `crawl/crawler.js`, `crawl/persistent.js` | `scrape/crawl.js` | Consolidated. URL normalization, traversal, frontier budgets and durable crawl state share one context and preserve all public function names. |
|
|
14
|
+
| Retry protection | `retry/policy.js`, `retry/circuit.js` | `runtime/retry.js` | Consolidated. Both modules protect caller-owned runners, storage and network handlers from transient failure storms. |
|
|
15
|
+
| Scrape error classification | `errors/taxonomy.js` | `core/errors.js` | Consolidated. Generic engine errors and scrape recovery metadata share one stable error boundary. |
|
|
16
|
+
| Page cache | `scrape/cache.js` | `scrape/cache.js` | Kept separate. It caches single-page scrape results and robots policy. |
|
|
17
|
+
| Storage cache | `storage/cache.js` | `storage/cache.js` | Kept separate. It manages hot and cold byte-backed tiers, encoding, eviction and revalidation. |
|
|
18
|
+
| Job persistence | `queue/persistent.js` | `queue/persistent.js` | Kept separate. It persists generic job lifecycle records rather than crawl URLs. |
|
|
19
|
+
| Browser sessions | `browser/session.js`, `domain/sessions.js`, `sessions/*` | existing owners | Kept separate. These contracts represent browser identity, domain validation and file or replay persistence. |
|
|
20
|
+
|
|
21
|
+
## Compatibility rule
|
|
22
|
+
|
|
23
|
+
The root export barrel continues to expose `normalizeurl`, `sameorigin`, `crawlfrontier`, `persistentqueue`, `crawl`, `retrypolicy`, `circuitbreaker`, `webscrapeerror` and `classifyerror`. Internal imports, tests and package payload declarations now point to the canonical owners. No public export was renamed for the regrouping.
|
|
24
|
+
|
|
25
|
+
## Verification rule
|
|
26
|
+
|
|
27
|
+
The reorganization is accepted only when syntax checks, deterministic tests, format checks, package dry-run, dependency audit, web typecheck and Pages build pass. A deleted file is considered redundant only after its imports, tests, package payload and documented public names have been checked.
|
|
28
|
+
|
|
29
|
+
## Deferred historical surface
|
|
30
|
+
|
|
31
|
+
The legacy TypeScript modules under `scrape/` remain a separate historical surface. They include richer browser, renderer, cache, middleware, server, session, pool, sitemap and format contracts. They are not silently merged into the active JavaScript modules because doing so would either discard features or introduce an undeclared TypeScript and external dependency build into the published core.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Security and web audit for 1.8.7
|
|
2
|
+
|
|
3
|
+
This audit records the baseline collected before remediation. The repository owner can cross-check the live GitHub view at [Saddle Security](https://github.com/wenathlan/saddle/security) and the [Dependabot alerts API](https://docs.github.com/en/rest/dependabot/alerts).
|
|
4
|
+
|
|
5
|
+
## Baseline
|
|
6
|
+
|
|
7
|
+
GitHub reported **42 open Dependabot alerts**: 2 critical, 14 high, 23 medium and 3 low. Five alerts were associated with the root `package-lock.json`; the remaining alerts were associated with the tracked `scrape/package-lock.json`. The largest groups were `undici`, `hono`, `vite`, `shell-quote`, `brace-expansion`, `postcss`, `ip-address` and `sharp` in the nested scrape manifest.
|
|
8
|
+
|
|
9
|
+
The local root `npm audit` reported 4 vulnerabilities in the current root installation: 2 moderate, 1 high and 1 critical. The root audit is a separate view from GitHub's repository-wide alert count and does not include the stale nested scrape dependency graph unless that directory is audited independently.
|
|
10
|
+
|
|
11
|
+
Code scanning returned no analysis found, and secret scanning returned that the feature is disabled. These are coverage gaps, not evidence that the repository has no code or secret findings. The remediation therefore includes enabling or documenting the appropriate GitHub security controls without fabricating a clean result.
|
|
12
|
+
|
|
13
|
+
## Web inventory
|
|
14
|
+
|
|
15
|
+
The requested directory `web/public/manos` does not exist. The actual platform directory is `web/public/__manus__`, containing `debug-collector.js`; it is a small runtime support directory rather than a product asset directory. The public visual assets are tracked under `web/public/assets/` as four WebP files.
|
|
16
|
+
|
|
17
|
+
The first path audit found root-absolute application entry and route paths, while the asset files themselves are in the correct public directory. The Pages site is served below `/saddle/`, so every asset and internal route must be resolved through the Vite base path rather than assuming `/`.
|
|
18
|
+
|
|
19
|
+
## Remediation policy
|
|
20
|
+
|
|
21
|
+
The 1.8.7 work will prioritize Node.js built-ins for new logic, update direct and transitive dependencies through the root lockfile, isolate or remove the obsolete nested scrape dependency graph when it is not part of the published engine, preserve the small `__manus__` support directory unless its script is proven unnecessary, and make web assets base-aware. A final audit will distinguish resolved advisories from external or unfixable advisories rather than hiding them.
|
package/extension/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Saddle browser bridge",
|
|
4
|
-
"version": "1.8.
|
|
4
|
+
"version": "1.8.8",
|
|
5
5
|
"description": "User initiated page snapshots through the Saddle browser contract.",
|
|
6
6
|
"minimum_chrome_version": "110",
|
|
7
7
|
"permissions": ["activeTab", "scripting", "storage"],
|
package/index.js
CHANGED
|
@@ -28,7 +28,7 @@ export * from "./runners/heartbeat.js";
|
|
|
28
28
|
export * from "./workflow/triggers.js";
|
|
29
29
|
export * from "./dispatch/resumable.js";
|
|
30
30
|
export * from "./scrape/semantic.js";
|
|
31
|
-
export * from "./crawl
|
|
31
|
+
export * from "./scrape/crawl.js";
|
|
32
32
|
export * from "./ai/provenance.js";
|
|
33
33
|
export * from "./observability/metrics.js";
|
|
34
34
|
export * from "./api/auth.js";
|
|
@@ -73,9 +73,6 @@ export * from "./protocol/blocks.js";
|
|
|
73
73
|
export * from "./workflow/manifest.js";
|
|
74
74
|
export * from "./workflow/templates.js";
|
|
75
75
|
export * from "./workflow/registry.js";
|
|
76
|
-
export * from "./crawl/normalize.js";
|
|
77
|
-
export * from "./crawl/crawler.js";
|
|
78
|
-
export * from "./crawl/persistent.js";
|
|
79
76
|
export * from "./scrape/schema.js";
|
|
80
77
|
export * from "./scrape/normalize.js";
|
|
81
78
|
export * from "./mcp/server.js";
|
|
@@ -102,9 +99,7 @@ export * from "./surfaces/adapters.js";
|
|
|
102
99
|
export * from "./surfaces/controls.js";
|
|
103
100
|
export * from "./surfaces/operations.js";
|
|
104
101
|
export * from "./library/public.js";
|
|
105
|
-
export * from "./
|
|
106
|
-
export * from "./retry/policy.js";
|
|
107
|
-
export * from "./retry/circuit.js";
|
|
102
|
+
export * from "./runtime/retry.js";
|
|
108
103
|
export * from "./storage/githubcontents.js";
|
|
109
104
|
export * from "./storage/filehosting.js";
|
|
110
105
|
export * from "./persistence/migrations.js";
|
package/library/public.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* public library helpers compose fetch extraction serialization chunking and crawl contracts.
|
|
3
3
|
*/
|
|
4
|
-
import { crawl } from "../crawl
|
|
4
|
+
import { crawl } from "../scrape/crawl.js";
|
|
5
5
|
import { chunkmarkdown } from "../ai/chunk.js";
|
|
6
6
|
import { estimatetokens } from "../ai/tokens.js";
|
|
7
7
|
import { extracthtml } from "../scrape/extract.js";
|
package/mcp/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* mcp server implements a small JSON RPC tool surface and remains transport agnostic.
|
|
3
3
|
*/
|
|
4
|
-
import { crawl } from "../crawl
|
|
4
|
+
import { crawl } from "../scrape/crawl.js";
|
|
5
5
|
import { extractwithschema } from "../scrape/schema.js";
|
|
6
6
|
import { jsonencode } from "../protocol/json.js";
|
|
7
7
|
import { browsertools } from "./browser.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenathlan/saddle",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.8",
|
|
4
4
|
"description": "binary computing engine that turns distributed storage into a publishable working set",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -84,7 +84,6 @@
|
|
|
84
84
|
"queue",
|
|
85
85
|
"dispatch",
|
|
86
86
|
"scrape",
|
|
87
|
-
"crawl",
|
|
88
87
|
"api",
|
|
89
88
|
"mcp",
|
|
90
89
|
"browser",
|
|
@@ -94,8 +93,6 @@
|
|
|
94
93
|
"webhook",
|
|
95
94
|
"surfaces",
|
|
96
95
|
"library",
|
|
97
|
-
"errors",
|
|
98
|
-
"retry",
|
|
99
96
|
"server",
|
|
100
97
|
"binary",
|
|
101
98
|
"format",
|
|
@@ -113,7 +110,7 @@
|
|
|
113
110
|
"LICENSE"
|
|
114
111
|
],
|
|
115
112
|
"scripts": {
|
|
116
|
-
"check": "node --check index.js && node --check runtime/engine.js && node --check cli/main.js && node --check extension/protocol.js && node --check extension/serviceworker.js && node --check extension/worker.js && node --check extension/content.js && node --check extension/pagebridge.js && node --check extension/popup.js && node --check extension/permissions.js && node --check extension/build.js && node --check release/assets.js && node --check sessions/replay.js && node --check browser/recorder.js && node --check scrape/normalize.js",
|
|
113
|
+
"check": "node --check index.js && node --check runtime/engine.js && node --check runtime/retry.js && node --check cli/main.js && node --check extension/protocol.js && node --check extension/serviceworker.js && node --check extension/worker.js && node --check extension/content.js && node --check extension/pagebridge.js && node --check extension/popup.js && node --check extension/permissions.js && node --check extension/build.js && node --check release/assets.js && node --check sessions/replay.js && node --check browser/recorder.js && node --check scrape/normalize.js && node --check scrape/crawl.js",
|
|
117
114
|
"formatcheck": "node format/check.js",
|
|
118
115
|
"test": "node --test tests/*.test.js",
|
|
119
116
|
"run": "node cli/main.js",
|
|
@@ -178,7 +175,6 @@
|
|
|
178
175
|
"vaul": "^1.1.2",
|
|
179
176
|
"wouter": "^3.3.5",
|
|
180
177
|
"zod": "^4.1.12",
|
|
181
|
-
"@builder.io/vite-plugin-jsx-loc": "^0.1.1",
|
|
182
178
|
"@tailwindcss/typography": "^0.5.15",
|
|
183
179
|
"@tailwindcss/vite": "^4.1.3",
|
|
184
180
|
"@types/express": "4.17.21",
|
|
@@ -198,6 +194,6 @@
|
|
|
198
194
|
"typescript": "5.6.3",
|
|
199
195
|
"vite": "^7.1.7",
|
|
200
196
|
"vite-plugin-manus-runtime": "^0.0.58",
|
|
201
|
-
"vitest": "^
|
|
197
|
+
"vitest": "^4.1.10"
|
|
202
198
|
}
|
|
203
199
|
}
|