@jini-ai/platform 0.5.10 → 0.6.11
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 +25 -1
- package/dist/fs/atomic-write.d.ts +1 -1
- package/dist/fs/atomic-write.js +18 -1
- package/dist/web/web-page.d.ts +4 -1
- package/dist/web/web-page.js +23 -7
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @jini-ai/platform
|
|
2
2
|
|
|
3
|
+
## 0.6.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Tool inputs apply a sole near-miss and declared input aliases; plugin-load outcomes are reported; `web_fetch` reads SVG/JS as text; `content_read` suggests its resource first; the browser falls back to system Chrome.
|
|
8
|
+
- `./node` file-tree projects with revision undo and a React-free `./session`; `./html` gains a non-throwing region scan and pure region edits; async atomic writes in platform.
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
- @jini-ai/core@0.6.11
|
|
12
|
+
|
|
13
|
+
## 0.6.10
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- e16a907: Preserve page-reader emphasis, raw Markdown text and inline-code whitespace; cap HTML before synchronous conversion.
|
|
18
|
+
|
|
19
|
+
The library projection retains its definition-list, fenced-code language, q/kbd, explicit hard-break and compact GFM table serialization. Ordinary text remains unescaped.
|
|
20
|
+
|
|
21
|
+
- b63cb5f: Add an explicit child-environment omission helper that reports dropped key names without exposing values, including case-insensitive matching for Windows hosts.
|
|
22
|
+
- Updated dependencies [22f0619]
|
|
23
|
+
- Updated dependencies [82ed2d3]
|
|
24
|
+
- Updated dependencies [5a3de86]
|
|
25
|
+
- @jini-ai/core@0.6.10
|
|
26
|
+
|
|
3
27
|
## 0.5.10 — 2026-10-22
|
|
4
28
|
|
|
5
29
|
- 2026-10-22: Mail: sender-identity resolver, payload hash covers the full delivery, rate-limit fixes, console preview; coverage.
|
|
@@ -54,6 +78,7 @@
|
|
|
54
78
|
## Unreleased
|
|
55
79
|
|
|
56
80
|
- New `./object-store` (node): `createLocalFsObjectStore({ rootDir })` and `createLocalFsObjectStoreProvider()` — the default `local` provider for `@jini-ai/core/object-store`. Path formula `join(rootDir, key)`, byte-identical to cms `LocalFsBlobStore`; atomic `ifAbsent` via `wx`; keys escaping the root are refused.
|
|
81
|
+
|
|
57
82
|
### Compilation fixes
|
|
58
83
|
|
|
59
84
|
- Omit an absent guarded HTTP abort signal to satisfy the canonical request contract.
|
|
@@ -66,7 +91,6 @@
|
|
|
66
91
|
- Remove `./trash` and the trash root namespace; import `@jini-ai/cms/trash`.
|
|
67
92
|
- Move both capabilities' behavior tests with their source; platform retains only generic OS/transport capabilities.
|
|
68
93
|
|
|
69
|
-
|
|
70
94
|
### Shared platform primitives
|
|
71
95
|
|
|
72
96
|
- BREAKING: HTTP primitive types now come from `@jini-ai/core/primitives`; guarded HTTP exports
|
|
@@ -109,7 +109,7 @@ export declare function writeJsonFileAtomic(required: Omit<AtomicWriteRequired,
|
|
|
109
109
|
* Parent creation supports runtime-state and secret stores without another writer implementation. */
|
|
110
110
|
export declare function writeFileAtomicAsync(required: {
|
|
111
111
|
filePath: string;
|
|
112
|
-
content: string;
|
|
112
|
+
content: string | Uint8Array;
|
|
113
113
|
}, options?: AtomicWriteOptions & {
|
|
114
114
|
fs?: AtomicAsyncFilesystem;
|
|
115
115
|
createParent?: boolean;
|
package/dist/fs/atomic-write.js
CHANGED
|
@@ -232,7 +232,7 @@ export async function writeFileAtomicAsync(required, options = {}) {
|
|
|
232
232
|
closed = true;
|
|
233
233
|
if (options.refuseSymlink)
|
|
234
234
|
await ignoreMissingAsync(async () => assertNotSymlink(await filesystem.lstat(filePath), options));
|
|
235
|
-
await filesystem
|
|
235
|
+
await renameWithRetry({ filesystem, from: temporary, to: filePath });
|
|
236
236
|
renamed = true;
|
|
237
237
|
await syncDirectoryBestEffortAsync({ directory: path.dirname(filePath), open: (path, flags) => filesystem.open(path, flags) }, { platform });
|
|
238
238
|
}
|
|
@@ -244,6 +244,23 @@ export async function writeFileAtomicAsync(required, options = {}) {
|
|
|
244
244
|
throw error;
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
|
+
/** Editors, antivirus and dev-server watchers can hold Windows destinations briefly.
|
|
248
|
+
* Retry only transient rename refusals; retain the same temporary file and fail after 5 tries.
|
|
249
|
+
* @complexity O(1) time/space, at most 150ms of backoff plus five filesystem calls.
|
|
250
|
+
*/
|
|
251
|
+
async function renameWithRetry({ filesystem, from, to }) {
|
|
252
|
+
for (let attempt = 0;; attempt += 1) {
|
|
253
|
+
try {
|
|
254
|
+
await filesystem.rename(from, to);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
if (attempt >= 4 || !hasErrorCode(error, ['EPERM', 'EBUSY']))
|
|
259
|
+
throw error;
|
|
260
|
+
await new Promise(resolve => setTimeout(resolve, 10 * 2 ** attempt));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
247
264
|
/** Pretty JSON can retain a state-store's trailing newline without changing its serialization. */
|
|
248
265
|
export async function writeJsonFileAtomicAsync(required, options = {}) {
|
|
249
266
|
const { data, ...writer } = required;
|
package/dist/web/web-page.d.ts
CHANGED
|
@@ -77,6 +77,8 @@ export interface WebFetchResult {
|
|
|
77
77
|
meta: Record<string, string>;
|
|
78
78
|
fetchedAt: string;
|
|
79
79
|
rendered?: true;
|
|
80
|
+
/** Explains a browser-unavailable fallback; the returned content has not executed JavaScript. */
|
|
81
|
+
note?: string;
|
|
80
82
|
data?: RenderedWebPage["data"];
|
|
81
83
|
dataUnmatched?: RenderedWebPage["dataUnmatched"];
|
|
82
84
|
dataTruncated?: boolean;
|
|
@@ -129,7 +131,8 @@ export interface WebFetchPorts {
|
|
|
129
131
|
* @complexity O(url length + filter characters), with at most 20 filters of 500 characters each.
|
|
130
132
|
*/
|
|
131
133
|
export declare function readWebFetchInput(input: Record<string, unknown>, _optional?: Record<string, never>): WebFetchInput;
|
|
132
|
-
/** text/*, HTML/XHTML, XML (incl. RSS/Atom/sitemaps), JSON (incl. +json);
|
|
134
|
+
/** text/*, JavaScript, SVG, HTML/XHTML, XML (incl. RSS/Atom/sitemaps), JSON (incl. +json);
|
|
135
|
+
* a missing type is judged by the body. */
|
|
133
136
|
export declare function isAllowedContentType({ mediaType }: {
|
|
134
137
|
mediaType: string;
|
|
135
138
|
}, _optional?: Record<string, never>): boolean;
|
package/dist/web/web-page.js
CHANGED
|
@@ -108,11 +108,12 @@ function header(headers, name) {
|
|
|
108
108
|
return value;
|
|
109
109
|
return "";
|
|
110
110
|
}
|
|
111
|
-
/** text/*, HTML/XHTML, XML (incl. RSS/Atom/sitemaps), JSON (incl. +json);
|
|
111
|
+
/** text/*, JavaScript, SVG, HTML/XHTML, XML (incl. RSS/Atom/sitemaps), JSON (incl. +json);
|
|
112
|
+
* a missing type is judged by the body. */
|
|
112
113
|
export function isAllowedContentType({ mediaType }, _optional = {}) {
|
|
113
114
|
if (mediaType === "" || mediaType.startsWith("text/"))
|
|
114
115
|
return true;
|
|
115
|
-
return /^application\/(?:xhtml\+xml|xml|json|[\w.+-]+\+(?:xml|json))$/.test(mediaType);
|
|
116
|
+
return /^(?:image\/svg\+xml|application\/(?:javascript|ecmascript|xhtml\+xml|xml|json|[\w.+-]+\+(?:xml|json)))$/.test(mediaType);
|
|
116
117
|
}
|
|
117
118
|
function charsetOf(contentType, body, isHtml) {
|
|
118
119
|
const fromHeader = /charset\s*=\s*"?([\w.:-]+)/i.exec(contentType)?.[1];
|
|
@@ -178,7 +179,7 @@ function render(format, body, isHtml, pageUrl, ports) {
|
|
|
178
179
|
/** Renderer error codes whose meaning the model can act on; any other code falls through to transport mapping. */
|
|
179
180
|
const RENDER_ERROR_MESSAGES = {
|
|
180
181
|
WEB_RENDER_BUSY: "web_fetch_page: render capacity is busy; retry shortly.",
|
|
181
|
-
WEB_RENDER_UNAVAILABLE: "web_fetch_page: this deployment cannot render pages: its headless browser (Playwright Chromium) is not installed or failed to start.",
|
|
182
|
+
WEB_RENDER_UNAVAILABLE: "web_fetch_page: this deployment cannot render pages: its headless browser (Playwright Chromium or system Chrome) is not installed or failed to start.",
|
|
182
183
|
WEB_RENDER_REFUSED: "web_fetch_page: the rendered page target is not a public internet address (including after redirects). Localhost, private-network and cloud-metadata addresses are refused.",
|
|
183
184
|
};
|
|
184
185
|
function errorCode({ error }, _optional = {}) {
|
|
@@ -268,11 +269,26 @@ async function enforceRateLimit({ ports, url, rateLimitScope, observe }) {
|
|
|
268
269
|
throw new ToolInputError({ message: `web_fetch_page: too many fetches from ${host} in the last minute. Retry in ${verdict.retryAfterSeconds} seconds.` });
|
|
269
270
|
}
|
|
270
271
|
}
|
|
272
|
+
/** Only an unavailable browser permits a guarded raw fetch; screenshots still require a browser.
|
|
273
|
+
* Reuses the call's deadline, rate-limit check and caller-safe error mapping.
|
|
274
|
+
* @complexity O(response bytes), with the same byte and redirect bounds as the ordinary fetch. */
|
|
271
275
|
async function fetchRendered({ input, ports, url }, { signal, timeoutMs }) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
+
try {
|
|
277
|
+
if (!ports.renderPage)
|
|
278
|
+
throw Object.assign(new Error("Browser unavailable"), { code: "WEB_RENDER_UNAVAILABLE" });
|
|
279
|
+
const page = await ports.renderPage({ input: { ...input, url: url.href } }, { signal, timeoutMs });
|
|
280
|
+
return projectRendered({ input, ports, url, page });
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
// Refusals, capacity, navigation and cancellation must never trigger a different network path.
|
|
284
|
+
if (signal.aborted || input.screenshot || errorCode({ error }) !== "WEB_RENDER_UNAVAILABLE")
|
|
285
|
+
throw error;
|
|
286
|
+
const response = await send(ports, url, signal, timeoutMs);
|
|
287
|
+
return {
|
|
288
|
+
...project({ input, ports, url, response }),
|
|
289
|
+
note: "Browser unavailable; returned a guarded non-rendered fetch. JavaScript was not executed.",
|
|
290
|
+
};
|
|
291
|
+
}
|
|
276
292
|
}
|
|
277
293
|
function project({ input, ports, url, response }) {
|
|
278
294
|
const contentType = header(response.headers, "content-type");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/platform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.11",
|
|
4
4
|
"description": "Generic OS/platform primitives: process exec and lifecycle, filesystem containment, HTTP readiness polling, toolchain discovery, blob storage, AWS signing, and an SSRF-safe asset cache.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
"!dist/**/*.map"
|
|
201
201
|
],
|
|
202
202
|
"dependencies": {
|
|
203
|
-
"@jini-ai/core": "^0.
|
|
203
|
+
"@jini-ai/core": "^0.6.11"
|
|
204
204
|
},
|
|
205
205
|
"publishConfig": {
|
|
206
206
|
"access": "public",
|