@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 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;
@@ -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.rename(temporary, filePath);
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;
@@ -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); a missing type is judged by the body. */
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;
@@ -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); a missing type is judged by the body. */
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
- if (!ports.renderPage)
273
- throw new ToolInputError({ message: "web_fetch_page: rendered pages are unavailable in this deployment." });
274
- const page = await ports.renderPage({ input: { ...input, url: url.href } }, { signal, timeoutMs });
275
- return projectRendered({ input, ports, url, page });
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.5.10",
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.4.0"
203
+ "@jini-ai/core": "^0.6.11"
204
204
  },
205
205
  "publishConfig": {
206
206
  "access": "public",