dsh-openwrite 0.2.21 → 0.2.22
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/cordis.patch.yml +0 -9
- package/docs/INSTALL.md +1 -1
- package/docs/RELEASE_ACCEPTANCE.md +10 -0
- package/package.json +1 -1
- package/packages/openwrite-bridge/lib/managed-runtime.d.ts +17 -0
- package/packages/openwrite-bridge/lib/managed-runtime.js +82 -10
- package/packages/studio-panel/lib/client.js +481 -481
- package/release/source.json +3 -3
- package/vendor/dsh-dog/UPSTREAM.md +2 -1
- package/vendor/dsh-dog/lib/client.js +10 -0
- package/vendor/dsh-dog/lib/client.js.map +1 -1
- package/vendor/dsh-dog/lib/index.d.ts +10 -0
- package/vendor/dsh-dog/lib/index.js +104 -2
package/cordis.patch.yml
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
# 0.2's carrier-neutral Connection does not require webServer itself, but its
|
|
2
|
-
# custom RPC channel registrar reads that service from its declaring context.
|
|
3
|
-
# OpenWrite targets Web/Desktop, where the carrier is present; declare it here
|
|
4
|
-
# so authenticated DoG RPC channels can register without changing transport.
|
|
5
|
-
- id: connection
|
|
6
|
-
inject:
|
|
7
|
-
- webRuntime
|
|
8
|
-
- webServer
|
|
9
|
-
|
|
10
1
|
- insert:
|
|
11
2
|
- id: openwrite-bundle
|
|
12
3
|
name: dsh-openwrite
|
package/docs/INSTALL.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
若留空作品目录并点击“选择目录并进入”时出现 `directoryPicker.pick needs the native capability; the composed picker serves "browse"`,这是桌面宿主组合的目录选择能力与其接口要求不一致。OpenWrite 0.2.21 在宿主接口失败后调用桌面公开的 `__DSH_DESKTOP_PICK_DIRECTORY__`,覆盖侧栏进入、作品绑定、新建与恢复目录选择。取消目录对话框不会创建作品或会话;手填绝对路径仍可直接进入。
|
|
8
8
|
|
|
9
|
-
没有桌面选择通道的宿主会保留原始错误。此时可在同一个输入框填写作品的完整绝对路径,例如 `A:\AI
|
|
9
|
+
没有桌面选择通道的宿主会保留原始错误。此时可在同一个输入框填写作品的完整绝对路径,例如 `A:\AI\我的小说`,点击“进入作品”。升级插件后需要关闭并重新打开 DSH Desktop,使客户端重新加载。
|
|
10
10
|
|
|
11
11
|
## 标准安装
|
|
12
12
|
|
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
新建作品的验收请求最多等待 60 秒(原为 15 秒),保留成功耗时和失败诊断;不重试写请求,超时仍失败。此调整容纳慢速运行器,不代表初始化耗时问题已被修复。
|
|
8
8
|
|
|
9
|
+
## 0.2.21
|
|
10
|
+
|
|
11
|
+
[GitHub Release](https://github.com/LiPu-jpg/Openwrite/releases/tag/v0.2.21) 与 npm `dsh-openwrite@0.2.21` 已发布,npm `latest` 已更新到 0.2.21。使用[主分支完整 CI](https://github.com/LiPu-jpg/Openwrite/actions/runs/38444848871) 验收的原始产物,来源提交 `4e5f88aac8b14c2402c509b2282810cc85840129`,npm 包 SHA-256 为 `b53e3638308185b5890207bc01806641a966620915b99102f5cb35c462b1daee`。8 份安装报告与 3 份宿主客户端报告全部通过;GitHub 四份附件逐一与 CI 原文件比对一致,官方 npm registry 无认证下载包的字节与 SHA-256 也一致。
|
|
12
|
+
|
|
13
|
+
修复 [#79](https://github.com/LiPu-jpg/Openwrite/issues/79),合并来源 [PR #80](https://github.com/LiPu-jpg/Openwrite/pull/80)。Windows Desktop 宿主目录接口失败后,插件使用桌面公开的系统目录选择通道;侧栏进入和工作台内所有目录选择共用入口。宿主取消不触发第二个对话框,桌面取消不创建作品/会话,手填路径跳过选择器,没有桌面能力时保留原始错误。Core 保持 5.8.10,wheel 与 requirements.lock 未变。
|
|
14
|
+
|
|
15
|
+
`npm run check:plugin` 全部通过(298 项组件测试)。新增回归直接对正式 0.2.20 客户端复现同一错误,修复后的客户端通过;从本版主分支 CI 包提取客户端执行相同回归也通过。正式发布构建在打包前执行该回归。回归覆盖宿主优先、两种取消、首次进入、共享入口、缺失/无效桌面能力、桌面对话框失败和手填路径。
|
|
16
|
+
|
|
17
|
+
本版原生 Windows 安装与浏览器检查运行在 Web profile;Windows Electron 系统文件夹对话框的实机复测未计为通过。目录通道的名称与返回契约已对照桌面上游源码,安装指南补充排查及手填路径方式。升级后需关闭并重新打开 DSH Desktop,重新加载客户端。真实模型调用为 0。
|
|
18
|
+
|
|
9
19
|
## 0.2.20
|
|
10
20
|
|
|
11
21
|
[GitHub Release](https://github.com/LiPu-jpg/Openwrite/releases/tag/v0.2.20) 与 npm `dsh-openwrite@0.2.20` 已发布,npm `latest` 已更新到 0.2.20。使用[主分支完整 CI](https://github.com/LiPu-jpg/Openwrite/actions/runs/38296670387) 验收的同一份原始产物,来源提交 `4d54f3c619f853a4243cd60e67ef6c8ba8d336af`,npm 包 SHA-256 为 `486c20fbd36909e32cc6cac1fd1e734872abec53da749c0860f8e5e1d4cf6cda`。六组原生平台/Node、源码安装与 alpha 兼容共 8 份运行报告、三代宿主客户端 3 份报告全部通过;GitHub 四份附件逐一与 CI 原文件比对一致,从官方 npm registry 无认证重新下载后,包字节及 SHA-256 也一致。
|
package/package.json
CHANGED
|
@@ -104,6 +104,23 @@ export declare class ManagedRuntime {
|
|
|
104
104
|
private queueRestart;
|
|
105
105
|
private recover;
|
|
106
106
|
private download;
|
|
107
|
+
/**
|
|
108
|
+
* Restricted networks throttle single connections to GitHub releases to a
|
|
109
|
+
* few dozen KB/s, so a fixed total timeout can never finish a 23 MB runtime
|
|
110
|
+
* archive (OpenWrite issue #82). Try the origin first, then optional mirror
|
|
111
|
+
* prefixes; every candidate is verified against the manifest checksum, so a
|
|
112
|
+
* mirror cannot substitute content. OPENWRITE_DOWNLOAD_MIRRORS overrides the
|
|
113
|
+
* prefix list (empty disables mirrors); the slow-start window uses
|
|
114
|
+
* OPENWRITE_DOWNLOAD_SLOW_MS / OPENWRITE_DOWNLOAD_SLOW_BYTES.
|
|
115
|
+
*/
|
|
116
|
+
private downloadInto;
|
|
117
|
+
/**
|
|
118
|
+
* One download attempt against a single source. A source that stays below
|
|
119
|
+
* the byte threshold through the grace window is abandoned for the next
|
|
120
|
+
* candidate, and a transfer that stops progressing is cut by an idle
|
|
121
|
+
* timeout — there is deliberately no total-duration cap.
|
|
122
|
+
*/
|
|
123
|
+
private downloadFrom;
|
|
107
124
|
private extract;
|
|
108
125
|
private command;
|
|
109
126
|
private lock;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
|
-
import { chmod, mkdir, open, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { chmod, mkdir, open, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
@@ -12,6 +12,8 @@ export const DEFAULT_RESTART_LIMIT = 5;
|
|
|
12
12
|
export const DEFAULT_BACKOFF_MS = [1_000, 2_000, 4_000, 8_000, 16_000];
|
|
13
13
|
/** Budget for the per-request liveness probe of a previously-ready backend. */
|
|
14
14
|
const HEALTH_PROBE_TIMEOUT_MS = 1_500;
|
|
15
|
+
/** A download with no progress for this long is cut and retried on the next source. */
|
|
16
|
+
const DOWNLOAD_IDLE_TIMEOUT_MS = 30_000;
|
|
15
17
|
/** The backend stderr log on disk keeps only the newest chunk. */
|
|
16
18
|
const STDERR_LOG_LIMIT = 64 * 1024;
|
|
17
19
|
/**
|
|
@@ -63,6 +65,13 @@ const ALLOWED_ENV = new Set([
|
|
|
63
65
|
'LANG', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES', 'LANGUAGE', 'TZ',
|
|
64
66
|
'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY', 'ALL_PROXY', 'http_proxy', 'https_proxy', 'no_proxy', 'all_proxy',
|
|
65
67
|
'SSL_CERT_FILE', 'SSL_CERT_DIR', 'REQUESTS_CA_BUNDLE', 'CURL_CA_BUNDLE',
|
|
68
|
+
// Package-index overrides let authors on restricted networks point the
|
|
69
|
+
// installer at a mirror (OpenWrite issue #82). uv reads UV_* variables only;
|
|
70
|
+
// PIP_* covers pip itself. UV_NO_CONFIG stays set so stray config files
|
|
71
|
+
// cannot change the locked dependency set — environment variables still
|
|
72
|
+
// apply, which is exactly the supported escape hatch.
|
|
73
|
+
'UV_DEFAULT_INDEX', 'UV_INDEX_URL', 'UV_INDEX', 'UV_HTTP_TIMEOUT',
|
|
74
|
+
'PIP_INDEX_URL', 'PIP_EXTRA_INDEX_URL', 'PIP_TRUSTED_HOST', 'PIP_DEFAULT_TIMEOUT',
|
|
66
75
|
]);
|
|
67
76
|
export async function sha256(file) {
|
|
68
77
|
const hash = createHash('sha256');
|
|
@@ -364,19 +373,86 @@ export class ManagedRuntime {
|
|
|
364
373
|
const destination = join(cache, item.sha256);
|
|
365
374
|
if (await sha256(destination).catch(() => '') === item.sha256)
|
|
366
375
|
return destination;
|
|
376
|
+
// A host killed mid-download leaves .partial fragments that are never
|
|
377
|
+
// resumed (observed in OpenWrite issue #82); clear them before starting.
|
|
378
|
+
for (const entry of await readdir(cache))
|
|
379
|
+
if (entry.endsWith('.partial'))
|
|
380
|
+
await rm(join(cache, entry), { force: true });
|
|
367
381
|
const partial = destination + '.' + randomUUID() + '.partial';
|
|
368
382
|
this.update('downloading', 'Downloading and verifying the writing environment');
|
|
369
383
|
try {
|
|
370
|
-
|
|
384
|
+
await this.downloadInto(item, partial, signal);
|
|
385
|
+
await rename(partial, destination);
|
|
386
|
+
return destination;
|
|
387
|
+
}
|
|
388
|
+
finally {
|
|
389
|
+
await rm(partial, { force: true });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Restricted networks throttle single connections to GitHub releases to a
|
|
394
|
+
* few dozen KB/s, so a fixed total timeout can never finish a 23 MB runtime
|
|
395
|
+
* archive (OpenWrite issue #82). Try the origin first, then optional mirror
|
|
396
|
+
* prefixes; every candidate is verified against the manifest checksum, so a
|
|
397
|
+
* mirror cannot substitute content. OPENWRITE_DOWNLOAD_MIRRORS overrides the
|
|
398
|
+
* prefix list (empty disables mirrors); the slow-start window uses
|
|
399
|
+
* OPENWRITE_DOWNLOAD_SLOW_MS / OPENWRITE_DOWNLOAD_SLOW_BYTES.
|
|
400
|
+
*/
|
|
401
|
+
async downloadInto(item, partial, signal) {
|
|
402
|
+
const grace = Number(process.env['OPENWRITE_DOWNLOAD_SLOW_MS'] ?? 12_000);
|
|
403
|
+
const threshold = Number(process.env['OPENWRITE_DOWNLOAD_SLOW_BYTES'] ?? 524_288);
|
|
404
|
+
const prefixes = (process.env['OPENWRITE_DOWNLOAD_MIRRORS'] ?? 'https://gh-proxy.com/')
|
|
405
|
+
.split(',').map(prefix => prefix.trim()).filter(Boolean);
|
|
406
|
+
const candidates = [item.url, ...prefixes.map(prefix => prefix + item.url)];
|
|
407
|
+
const failures = [];
|
|
408
|
+
for (const candidate of candidates) {
|
|
409
|
+
signal.throwIfAborted();
|
|
410
|
+
try {
|
|
411
|
+
await this.downloadFrom(candidate, partial, signal, grace, threshold);
|
|
412
|
+
if (await sha256(partial) !== item.sha256)
|
|
413
|
+
throw new Error('checksum mismatch');
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
signal.throwIfAborted();
|
|
418
|
+
let host = candidate;
|
|
419
|
+
try {
|
|
420
|
+
host = new URL(candidate).host;
|
|
421
|
+
}
|
|
422
|
+
catch { /* keep the raw candidate */ }
|
|
423
|
+
failures.push(`${host}: ${error instanceof Error ? error.message : String(error)}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
throw new Error(`Download failed (${failures.join('; ')}); retry`);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* One download attempt against a single source. A source that stays below
|
|
430
|
+
* the byte threshold through the grace window is abandoned for the next
|
|
431
|
+
* candidate, and a transfer that stops progressing is cut by an idle
|
|
432
|
+
* timeout — there is deliberately no total-duration cap.
|
|
433
|
+
*/
|
|
434
|
+
async downloadFrom(url, partial, signal, grace, threshold) {
|
|
435
|
+
const controller = new AbortController();
|
|
436
|
+
const started = Date.now();
|
|
437
|
+
let received = 0;
|
|
438
|
+
let lastChunkAt = started;
|
|
439
|
+
const watchdog = setInterval(() => {
|
|
440
|
+
if (Date.now() - lastChunkAt > DOWNLOAD_IDLE_TIMEOUT_MS)
|
|
441
|
+
controller.abort(new Error(`stalled (no data for ${DOWNLOAD_IDLE_TIMEOUT_MS}ms)`));
|
|
442
|
+
else if (received < threshold && Date.now() - started > grace)
|
|
443
|
+
controller.abort(new Error(`too slow (${received} of ${threshold} bytes within ${grace}ms)`));
|
|
444
|
+
}, 250);
|
|
445
|
+
try {
|
|
446
|
+
const response = await fetch(url, { signal: AbortSignal.any([signal, controller.signal]) });
|
|
371
447
|
if (!response.ok || !response.body)
|
|
372
|
-
throw new Error(`
|
|
448
|
+
throw new Error(`HTTP ${response.status}`);
|
|
373
449
|
const size = Number(response.headers.get('content-length')) || undefined;
|
|
374
|
-
const file = await open(partial, '
|
|
375
|
-
let received = 0;
|
|
450
|
+
const file = await open(partial, 'w', 0o600);
|
|
376
451
|
try {
|
|
377
452
|
for await (const chunk of response.body) {
|
|
378
453
|
signal.throwIfAborted();
|
|
379
454
|
received += chunk.length;
|
|
455
|
+
lastChunkAt = Date.now();
|
|
380
456
|
if (received > 1024 ** 3)
|
|
381
457
|
throw new Error('Download exceeds the allowed size');
|
|
382
458
|
await file.write(chunk);
|
|
@@ -386,13 +462,9 @@ export class ManagedRuntime {
|
|
|
386
462
|
finally {
|
|
387
463
|
await file.close();
|
|
388
464
|
}
|
|
389
|
-
if (await sha256(partial) !== item.sha256)
|
|
390
|
-
throw new Error('Download checksum failed; retry');
|
|
391
|
-
await rename(partial, destination);
|
|
392
|
-
return destination;
|
|
393
465
|
}
|
|
394
466
|
finally {
|
|
395
|
-
|
|
467
|
+
clearInterval(watchdog);
|
|
396
468
|
}
|
|
397
469
|
}
|
|
398
470
|
async extract(item, signal) {
|