dsh-openwrite 0.2.17 → 0.2.19
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 +4 -0
- package/docs/RELEASE_ACCEPTANCE.md +18 -1
- package/package.json +1 -1
- package/packages/openwrite-bridge/lib/tools.js +133 -3
- package/packages/studio-panel/lib/client.js +483 -483
- package/release/{openwrite-5.8.8-py3-none-any.whl → openwrite-5.8.9-py3-none-any.whl} +0 -0
- package/release/runtime-manifest.json +8 -5
- package/release/source.json +3 -3
- package/vendor/dsh-dog/lib/core.js +1 -1
- package/vendor/dsh-dog/lib/index.d.ts +1 -1
- package/vendor/dsh-dog/lib/index.js +202 -4
- package/vendor/dsh-dog/lib/{core-DdfMcrB4.js → core-BAlgZpLq.js} +2 -2
package/README.md
CHANGED
|
@@ -66,6 +66,10 @@ dsh web
|
|
|
66
66
|
|
|
67
67
|
### dsh 0.2 桌面端升级
|
|
68
68
|
|
|
69
|
+
`0.2.19` 候选包修复两个加载/工具问题:部分安装下「OpenWrite 创作」预设从列表消失、DoG 工具不可用(dsh-dog 宿主包解析失败,[#74](https://github.com/LiPu-jpg/Openwrite/issues/74)),以及 `novel_*` 工具的对象/数组参数在模型输出 JSON 字符串时预览失败、`data` 字段丢失([#75](https://github.com/LiPu-jpg/Openwrite/issues/75))。正式可下载版本以 npm `latest` 和 GitHub Release 为准。升级后需停止并重新启动 dsh,再刷新浏览器页面。
|
|
70
|
+
|
|
71
|
+
`0.2.18` 候选包修正 `auto:cheapest`(Core 5.8.9):零价模型不再参与比价,自动落到付费低价模型上。正式可下载版本以 npm `latest` 和 GitHub Release 为准。升级后需停止并重新启动 dsh,再刷新浏览器页面。
|
|
72
|
+
|
|
69
73
|
`0.2.17` 候选包新增自动选模(Core 5.8.8):模型档案的模型名写成 `auto:cheapest` 会自动选用服务商价格目录中总价最低且满足上下文/输出要求的模型(目录缓存一小时,价格变动自动跟随),写成 `auto:popular`(OpenRouter)则由官方 `openrouter/auto` 按社区用量自动选模型。正式可下载版本以 npm `latest` 和 GitHub Release 为准。升级后需停止并重新启动 dsh,再刷新浏览器页面。
|
|
70
74
|
|
|
71
75
|
`0.2.16` 候选包补漏 [#64](https://github.com/LiPu-jpg/Openwrite/issues/64)/[#67](https://github.com/LiPu-jpg/Openwrite/issues/67):字数精简重试耗尽后保稿时,`length_out_of_range`/`length_warning` 标记与校验问题现在随草稿产物持久化(Core 5.8.7),导出/审阅侧可追溯。正式可下载版本以 npm `latest` 和 GitHub Release 为准。升级后需停止并重新启动 dsh,再刷新浏览器页面。
|
|
@@ -6,9 +6,26 @@
|
|
|
6
6
|
|
|
7
7
|
新建作品的验收请求最多等待 60 秒(原为 15 秒),保留成功耗时和失败诊断;不重试写请求,超时仍失败。此调整容纳慢速运行器,不代表初始化耗时问题已被修复。
|
|
8
8
|
|
|
9
|
+
## 0.2.19
|
|
10
|
+
|
|
11
|
+
<!-- TODO: 发布后回填 CI、Release、npm 链接与 SHA -->
|
|
12
|
+
|
|
13
|
+
修复 [#74](https://github.com/LiPu-jpg/Openwrite/issues/74) 与 [#75](https://github.com/LiPu-jpg/Openwrite/issues/75),均为插件侧改动,Core 保持 5.8.9(native-core `d0a75e30047c28c99af909e1b85b07190abb1fb7`),wheel 与 requirements.lock 未变。
|
|
14
|
+
|
|
15
|
+
- #74:vendor/dsh-dog 运行时不再裸导入 `@deepseek-ai/dsh-home-paths` 与 `@deepseek-ai/dsh-tools` 两个 peer 宿主包(pnpm 隔离布局下从 vendor 目录不可解析 → openwrite-dog 条目导入失败 → openwriteDog 未注册 → 预设从列表消失)。`dshHomePath` 与 `defineTool` 由新增 `src/host-shims.ts` 自包含实现,行为对齐宿主;构建产物 `lib/index.js` 只导入 node 内置模块与 dependencies 内的 `@deepseek-ai/schemastery`,独立 `import()` 成功。新增 host-shims 单元测试 10 例,dsh-dog 共 60/60 通过。
|
|
16
|
+
- #75:bridge 的 90 个 `novel_*` 工具统一经本地 `defineTool`(复用宿主 `parameterSchemaSpecToJsonSchema`/`valueSchemaSpecToJsonSchema`/`validateJsonSchemaValue`/`ToolArgsError`),在 schema 校验前对 `type: 'json'` 参数(含 array 的 json items)做字符串 `JSON.parse` 归一化;报错带参数名与实际类型。8 项归一化冒烟用例通过;四个宿主符号已核对在 0.1.2-rc.1 / 0.1.5-alpha.1 / 0.2.0-rc.2 三个受支持宿主中均导出且签名一致。
|
|
17
|
+
|
|
18
|
+
## 0.2.18
|
|
19
|
+
|
|
20
|
+
[GitHub Release](https://github.com/LiPu-jpg/Openwrite/releases/tag/v0.2.18) 已发布,直接使用[完整 CI](https://github.com/LiPu-jpg/Openwrite/actions/runs/38059156641)验收的同一份包。来源提交 `6e84c2a`,npm 包 SHA-256 为 `edd510c6d4de58bfa09eb30f8a3b74e5a0e6cf7cf2425bc6fe3681b732c1a196`。六组原生平台/Node、源码安装、alpha 兼容及三代宿主客户端共 12 份报告全部通过;Release 四份附件的校验值已与 CI `validated-release` 产物逐一核对。合并来源 [PR #73](https://github.com/LiPu-jpg/Openwrite/pull/73)。npm `dsh-openwrite@0.2.18` 已发布到官方 registry(registry.npmjs.org),`latest` 已更新到 0.2.18;从官方 registry 无认证重新下载后,SHA-256 与上述 CI 包一致。main CI 首次运行 source-install 因 pnpm `--find-links` 目录瞬时不存在失败,重跑同一提交通过,判定为运行器 flake。
|
|
21
|
+
|
|
22
|
+
修正 0.2.17 的 `auto:cheapest`:真实 Key 实测发现 OpenRouter 上部分免费模型没有 `:free` 后缀(`inclusionai/ling-3.1-flash` 定价 0/0),会被低价策略选中;免费模型可用性不稳定,Core 5.8.9(native-core `d0a75e30047c28c99af909e1b85b07190abb1fb7`)把零价模型一并排除,`auto:cheapest` 现在落在付费低价模型上。回归测试补充零价排除断言,9/9 通过。
|
|
23
|
+
|
|
24
|
+
Core 5.8.9 由 `native-core` 分支干净树 `pip wheel` 构建,manifest 三处已回填。
|
|
25
|
+
|
|
9
26
|
## 0.2.17
|
|
10
27
|
|
|
11
|
-
|
|
28
|
+
[GitHub Release](https://github.com/LiPu-jpg/Openwrite/releases/tag/v0.2.17) 已发布,直接使用[完整 CI](https://github.com/LiPu-jpg/Openwrite/actions/runs/38057362486)验收的同一份包。来源提交 `de2cacb`,npm 包 SHA-256 为 `da2a0b2ae264392f7d0f8c5d6c98de08cd0497f8a761bf0c96f9a43ce95771c5`。六组原生平台/Node、源码安装、alpha 兼容及三代宿主客户端共 12 份报告全部通过;Release 四份附件的校验值已与 CI `validated-release` 产物逐一核对。合并来源 [PR #72](https://github.com/LiPu-jpg/Openwrite/pull/72)。npm `dsh-openwrite@0.2.17` 已发布到官方 registry(registry.npmjs.org),`latest` 已更新到 0.2.17;从官方 registry 无认证重新下载后,SHA-256 与上述 CI 包一致。
|
|
12
29
|
|
|
13
30
|
新增自动选模(Core 5.8.8,native-core `a7f4d8769de7496cb18b9d6dc38872c60fa5517a`):档案模型名支持 `auto:cheapest`(实时拉取 `/models` 价格目录,过滤后取总价最低,缓存一小时、失败回退过期缓存)与 `auto:popular`(OpenRouter `openrouter/auto`)。解析在 `resolve`/`resolve_profile` 统一发生,结果写入 `auto_select` 字段;静态模型档案不受影响。新增 9 项回归测试全部通过(比价规则、缓存命中、过期缓存兜底、目录不可用报错、popular 非 OpenRouter 拒绝、未知策略拒绝、静态档案零网络);档案/连接测试/工作室相关面 227 项通过(2 项失败为 native-core 预存在的 `test_real_litellm_backend_accepts_openwrite_request_shape_without_network` 与 `test_studio_outline_task_uses_planning_snapshot_and_validated_artifact` 漂移,与本版无关)。真实 OpenRouter Key 实测:`auto:popular` 路由到 `qwen/qwen3.8-flash` 返回 200。
|
|
14
31
|
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@ import { createHash } from 'node:crypto';
|
|
|
8
8
|
import { realpathSync } from 'node:fs';
|
|
9
9
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
10
10
|
import { isAbsolute, join } from 'node:path';
|
|
11
|
-
import {
|
|
11
|
+
import { ToolArgsError, parameterSchemaSpecToJsonSchema, validateJsonSchemaValue, valueSchemaSpecToJsonSchema, } from '@deepseek-ai/dsh-tools';
|
|
12
12
|
import { StudioError } from './client.js';
|
|
13
13
|
import { materializeChapterDelivery } from './dog-delivery.js';
|
|
14
14
|
import { materializeCompletedDogTaskReview, materializeDogReview, reviewChapterId } from './dog-review.js';
|
|
@@ -23,6 +23,135 @@ const JSON_OUTPUT_SCHEMA = {
|
|
|
23
23
|
};
|
|
24
24
|
/** Manuscript chapter ids accepted by OpenWrite (`ch_<digits>`). */
|
|
25
25
|
const CHAPTER_ID_PATTERN = /^ch_\d+$/;
|
|
26
|
+
/** Short, model-facing description of a received argument value's actual shape. */
|
|
27
|
+
function describeValue(value) {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
return 'undefined';
|
|
30
|
+
if (value === null)
|
|
31
|
+
return 'null';
|
|
32
|
+
if (Array.isArray(value))
|
|
33
|
+
return `an array of ${value.length} item(s)`;
|
|
34
|
+
if (typeof value === 'string')
|
|
35
|
+
return 'a string';
|
|
36
|
+
return `a value of type ${typeof value}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Parse a JSON string argument once, naming the parameter and the received
|
|
40
|
+
* type when the text is not valid JSON.
|
|
41
|
+
*/
|
|
42
|
+
function parseJsonArgument(name, value) {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(value);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
48
|
+
throw new Error(`parameter "${name}" expects a JSON value; received a string that is not valid JSON (${detail})`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Normalize arguments for parameters declared `type: 'json'` (and
|
|
53
|
+
* `type: 'array'` with json items). DSH keeps `json` an annotation-only
|
|
54
|
+
* schema, so the value reaches the plugin in whatever shape the model
|
|
55
|
+
* emitted; models that serialize the object/array as a JSON *string* would
|
|
56
|
+
* otherwise fail the plugin-side object/array checks (GitHub issue #75).
|
|
57
|
+
*/
|
|
58
|
+
function normalizeJsonArguments(parameters, args) {
|
|
59
|
+
let normalized;
|
|
60
|
+
for (const [name, spec] of Object.entries(parameters)) {
|
|
61
|
+
const value = args[name];
|
|
62
|
+
if (value === undefined || spec === null || typeof spec !== 'object')
|
|
63
|
+
continue;
|
|
64
|
+
const type = spec.type;
|
|
65
|
+
if (type === 'json' && typeof value === 'string') {
|
|
66
|
+
normalized ??= { ...args };
|
|
67
|
+
normalized[name] = parseJsonArgument(name, value);
|
|
68
|
+
}
|
|
69
|
+
else if (type === 'array') {
|
|
70
|
+
const items = spec.items;
|
|
71
|
+
const itemType = items !== null && typeof items === 'object' ? items.type : undefined;
|
|
72
|
+
if (itemType !== 'json')
|
|
73
|
+
continue;
|
|
74
|
+
if (typeof value === 'string') {
|
|
75
|
+
normalized ??= { ...args };
|
|
76
|
+
normalized[name] = parseJsonArgument(name, value);
|
|
77
|
+
}
|
|
78
|
+
else if (Array.isArray(value) && value.some((item) => typeof item === 'string')) {
|
|
79
|
+
normalized ??= { ...args };
|
|
80
|
+
normalized[name] = value.map((item) => (typeof item === 'string' ? parseJsonArgument(`${name}[]`, item) : item));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return normalized ?? args;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Local `defineTool`: mirrors the host helper (`@deepseek-ai/dsh-tools`
|
|
88
|
+
* `defineTool`) using the host's own schema compiler and validator, except
|
|
89
|
+
* every argument flow (execute and the replay presenters) first normalizes
|
|
90
|
+
* string-typed JSON arguments — see {@link normalizeJsonArguments}.
|
|
91
|
+
* Normalization must run *before* validation, otherwise an `array` parameter
|
|
92
|
+
* receiving a JSON string would be rejected by the schema check before the
|
|
93
|
+
* plugin ever sees it (GitHub issue #75). Centralizing here keeps all
|
|
94
|
+
* `novel_*` call sites free of per-parameter parsing.
|
|
95
|
+
*/
|
|
96
|
+
function defineTool(options) {
|
|
97
|
+
const userExecute = options.execute;
|
|
98
|
+
const userFinalizeContent = options.finalizeContent;
|
|
99
|
+
const userRender = options.output.render;
|
|
100
|
+
const userPresentationMeta = options.output.presentationMeta;
|
|
101
|
+
const userPresentCall = options.presentCall;
|
|
102
|
+
const userPresentResult = options.presentResult;
|
|
103
|
+
const userIsConcurrencySafe = options.isConcurrencySafe;
|
|
104
|
+
if (options.timeoutMs !== undefined && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0))
|
|
105
|
+
throw new Error(`defineTool(${options.name}): timeoutMs must be a positive finite number`);
|
|
106
|
+
const parameters = parameterSchemaSpecToJsonSchema(options.parameters);
|
|
107
|
+
const outputSchema = valueSchemaSpecToJsonSchema(options.output.schema);
|
|
108
|
+
const normalize = (args) => normalizeJsonArguments(options.parameters, objectValue(args));
|
|
109
|
+
const validate = (args) => validateJsonSchemaValue(parameters, args, '');
|
|
110
|
+
const tool = {
|
|
111
|
+
name: options.name,
|
|
112
|
+
description: options.description,
|
|
113
|
+
parameters,
|
|
114
|
+
output: {
|
|
115
|
+
schema: outputSchema,
|
|
116
|
+
render: (args, value) => userRender(args, value),
|
|
117
|
+
...(userPresentationMeta !== undefined
|
|
118
|
+
? { presentationMeta: (args, value) => userPresentationMeta(args, value) }
|
|
119
|
+
: {}),
|
|
120
|
+
},
|
|
121
|
+
...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
|
|
122
|
+
async execute(args, exec) {
|
|
123
|
+
const normalized = normalize(args);
|
|
124
|
+
const violations = validate(normalized);
|
|
125
|
+
if (violations.length > 0)
|
|
126
|
+
throw new ToolArgsError(violations);
|
|
127
|
+
return userExecute(normalized, exec);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
if (userFinalizeContent)
|
|
131
|
+
tool.finalizeContent = (exec, result) => userFinalizeContent(exec, result);
|
|
132
|
+
if (userPresentCall)
|
|
133
|
+
tool.presentCall = (args) => {
|
|
134
|
+
const normalized = normalize(args);
|
|
135
|
+
if (validate(normalized).length > 0)
|
|
136
|
+
return undefined;
|
|
137
|
+
return userPresentCall(normalized);
|
|
138
|
+
};
|
|
139
|
+
if (userPresentResult)
|
|
140
|
+
tool.presentResult = (args, result) => {
|
|
141
|
+
const normalized = normalize(args);
|
|
142
|
+
if (validate(normalized).length > 0)
|
|
143
|
+
return undefined;
|
|
144
|
+
return userPresentResult(normalized, result);
|
|
145
|
+
};
|
|
146
|
+
if (userIsConcurrencySafe)
|
|
147
|
+
tool.isConcurrencySafe = (args) => {
|
|
148
|
+
const normalized = normalize(args);
|
|
149
|
+
if (validate(normalized).length > 0)
|
|
150
|
+
return false;
|
|
151
|
+
return userIsConcurrencySafe(normalized);
|
|
152
|
+
};
|
|
153
|
+
return tool;
|
|
154
|
+
}
|
|
26
155
|
/** Resolve a review target: an explicit manuscript path, or a chapter id mapped to `data/manuscript/<id>.md`. */
|
|
27
156
|
function resolveReviewPath(args) {
|
|
28
157
|
const path = args.path?.trim();
|
|
@@ -734,7 +863,8 @@ export function registerNovelTools(ctx, clientFactory, options) {
|
|
|
734
863
|
timeoutMs,
|
|
735
864
|
async execute(args, exec) {
|
|
736
865
|
if (args.action === 'preview' && (!Array.isArray(args.edits) || args.edits.length !== 1)) {
|
|
737
|
-
throw new Error('preview requires exactly one edit so the author can accept or reject each change independently'
|
|
866
|
+
throw new Error('preview requires exactly one edit so the author can accept or reject each change independently ' +
|
|
867
|
+
`(received ${describeValue(args.edits)})`);
|
|
738
868
|
}
|
|
739
869
|
if (args.action !== 'preview' && !args.preview_token?.trim()) {
|
|
740
870
|
throw new Error(`${args.action} requires preview_token`);
|
|
@@ -771,7 +901,7 @@ export function registerNovelTools(ctx, clientFactory, options) {
|
|
|
771
901
|
if (!args.change_kind)
|
|
772
902
|
throw new Error('preview requires change_kind');
|
|
773
903
|
if (args.change === null || typeof args.change !== 'object' || Array.isArray(args.change)) {
|
|
774
|
-
throw new Error(
|
|
904
|
+
throw new Error(`preview requires one change object (received ${describeValue(args.change)})`);
|
|
775
905
|
}
|
|
776
906
|
}
|
|
777
907
|
else if (!args.preview_token?.trim()) {
|