dsh-one-click-polish 1.2.0 → 1.2.2
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 +57 -11
- package/client.js +83 -5
- package/index.js +64 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
# 一键润色 / 扩写 / 自动驾驶
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/dsh-one-click-polish)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
3
6
|
给 DeepSeek Harness(dsh)桌面端的输入框加一组动作:**润色**、**扩写**、**自动驾驶**。所有模型调用都用**你当前会话正在用的那个模型**,结果直接写回输入框。
|
|
4
7
|
|
|
5
8
|
- 润色:按三档深度重写草稿
|
|
6
9
|
- 扩写:把一句话扩写成能直接开工的需求说明
|
|
7
10
|
- 自动驾驶:让 AI 读对话、写下一步提示词、发给助手,一轮接一轮推进到你设定的目标
|
|
8
11
|
|
|
12
|
+
一句安装:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
dsh-one-click-polish
|
|
16
|
+
```
|
|
17
|
+
|
|
9
18
|
装好之后,输入框左下角多一排按钮:
|
|
10
19
|
|
|
11
20
|
```
|
|
@@ -26,15 +35,40 @@
|
|
|
26
35
|
|
|
27
36
|
## 安装
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
两种来源,功能完全一样:
|
|
39
|
+
|
|
40
|
+
| 来源 | 填什么 | 更新按钮 |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| **npm** | `dsh-one-click-polish` | ✅ 有「可更新」 |
|
|
43
|
+
| GitHub | `https://github.com/HTRP1486/dsh-one-click-polish` | ❌ 只能看提交 |
|
|
44
|
+
|
|
45
|
+
### 方式一:npm(推荐)
|
|
30
46
|
|
|
31
|
-
打开 dsh → 设置 → 插件 →
|
|
47
|
+
打开 dsh → 设置 → 插件 → 添加插件,输入框里就填**包名**:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
dsh-one-click-polish
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
不用加 `npm:` 前缀,也不用加版本号(默认装 `latest`)。
|
|
54
|
+
|
|
55
|
+
命令行等价写法:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
dsh plugin --profile <你的 profile 名> install dsh-one-click-polish
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**为什么推荐这个**:npm 上有版本号,dsh 能比对,所以插件列表里会出现「可更新」和「卸载」两个按钮。缺点是更新要走一遍发布流程。
|
|
62
|
+
|
|
63
|
+
### 方式二:GitHub
|
|
64
|
+
|
|
65
|
+
输入框里填仓库地址:
|
|
32
66
|
|
|
33
67
|
```
|
|
34
68
|
https://github.com/HTRP1486/dsh-one-click-polish
|
|
35
69
|
```
|
|
36
70
|
|
|
37
|
-
|
|
71
|
+
等价写法都行:
|
|
38
72
|
|
|
39
73
|
```
|
|
40
74
|
https://github.com/HTRP1486/dsh-one-click-polish
|
|
@@ -44,23 +78,35 @@ github:HTRP1486/dsh-one-click-polish
|
|
|
44
78
|
|
|
45
79
|
**末尾不要加斜杠**。加了会报 `a URL must point at a git repository or a tarball` —— dsh 的判断规则是 `^https?://主机/用户/仓库(.git)?$`,最后一段不允许出现 `/`。
|
|
46
80
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
dsh plugin --profile <你的 profile 名> install github:HTRP1486/dsh-one-click-polish
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
不知道 profile 名的话,看你 `~/.dsh/profiles/` 下的目录名,常见的是 `desktop`。
|
|
81
|
+
**为什么用它**:跟最新提交走,不用等发版。但没有「可更新」按钮——想更新就是重装一次(`remove` 后 `install`)。
|
|
54
82
|
|
|
55
83
|
### 其他方式
|
|
56
84
|
|
|
57
85
|
| 方式 | 做法 |
|
|
58
86
|
| --- | --- |
|
|
59
|
-
| 本地目录 | target 填仓库的**绝对路径**,如 `D:\Documents\dsh-one-click-polish
|
|
87
|
+
| 本地目录 | target 填仓库的**绝对路径**,如 `D:\Documents\dsh-one-click-polish`。改代码即时生效,适合开发 |
|
|
60
88
|
| Release 包 | 从 [Releases](https://github.com/HTRP1486/dsh-one-click-polish/releases) 下 `.tgz`,target 填该文件的绝对路径 |
|
|
61
89
|
|
|
90
|
+
### 三种方式怎么选
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
只想用 → npm
|
|
94
|
+
要用最新代码 → GitHub
|
|
95
|
+
自己在改它 → 本地目录(link)
|
|
96
|
+
```
|
|
97
|
+
|
|
62
98
|
装完**重启 dsh**。判断是否成功:输入框左下角出现 `润色 | 深度 | 扩写 | 自动`,设置里多出「润色 / 扩写」一页。
|
|
63
99
|
|
|
100
|
+
### 版本号
|
|
101
|
+
|
|
102
|
+
三个地方保持同步,每次发布一起改:
|
|
103
|
+
|
|
104
|
+
| 位置 | 当前 |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `package.json` 的 `version` | `1.2.0` |
|
|
107
|
+
| npm 的 `latest` 标签 | `1.2.0` |
|
|
108
|
+
| GitHub 的 tag / Release | `v1.2.0` |
|
|
109
|
+
|
|
64
110
|
### 装不上时
|
|
65
111
|
|
|
66
112
|
| 报错 | 原因 |
|
package/client.js
CHANGED
|
@@ -41,6 +41,7 @@ window.__ModuleLoader__.load({
|
|
|
41
41
|
const PROGRESS = ENDPOINT.replace(/\/polish$/, '/progress')
|
|
42
42
|
const PILOT = ENDPOINT.replace(/\/polish$/, '/autopilot')
|
|
43
43
|
const RUNS = ENDPOINT.replace(/\/polish$/, '/runs')
|
|
44
|
+
const VERSION = ENDPOINT.replace(/\/polish$/, '/version')
|
|
44
45
|
|
|
45
46
|
/** The in-flight request, so the panel can cancel it. */
|
|
46
47
|
let activeAbort = null
|
|
@@ -253,8 +254,8 @@ window.__ModuleLoader__.load({
|
|
|
253
254
|
'auto.warn.body': '启动后 AI 会自动往这个会话里发送消息:读对话 → 写下一步提示词 → 发给助手,一轮接一轮。它会消耗模型额度,也可能做出你没想到的改动。随时可以中断或结束循环。',
|
|
254
255
|
'auto.warn.cancel': '再想想',
|
|
255
256
|
'auto.warn.confirm': '确认启动',
|
|
256
|
-
'auto.interrupt': '跳过这一轮',
|
|
257
|
-
'auto.interrupt.hint': '取消助手正在做的这一轮,循环继续,它会重新判断下一步。',
|
|
257
|
+
'auto.interrupt': '跳过这一轮',
|
|
258
|
+
'auto.interrupt.hint': '取消助手正在做的这一轮,循环继续,它会重新判断下一步。',
|
|
258
259
|
'auto.stop.hint': '整个自动驾驶停下。之后可以从这里继续。',
|
|
259
260
|
'auto.stop': '停止自动',
|
|
260
261
|
'auto.running': '运行中',
|
|
@@ -281,6 +282,13 @@ window.__ModuleLoader__.load({
|
|
|
281
282
|
'settings.idleWait': '等待助手完成上一轮的上限',
|
|
282
283
|
'settings.idleWait.hint': '自动驾驶要发下一条时,如果助手还在处理,它会等。超过这个上限就先停下,点「继续」可以接着来。长会话需要更长的时间。',
|
|
283
284
|
'settings.idleWait.unit': '分钟',
|
|
285
|
+
'version.current': '当前版本',
|
|
286
|
+
'version.latest': '已是最新',
|
|
287
|
+
'version.newest': 'npm 上有',
|
|
288
|
+
'version.checking': '检查更新中…',
|
|
289
|
+
'version.unknown': '查不到最新版本',
|
|
290
|
+
'version.how': '怎么更新',
|
|
291
|
+
'version.recheck': '重新检查',
|
|
284
292
|
'settings.explore': '检索预算',
|
|
285
293
|
'settings.explore.hint': '标准/深度润色时会先查资料。这是查资料总共能用多久、最多查几轮。查完后再改写,两者互不占用。',
|
|
286
294
|
'settings.explore.unit': '秒',
|
|
@@ -412,8 +420,8 @@ window.__ModuleLoader__.load({
|
|
|
412
420
|
'auto.warn.body': 'The AI will send messages into this conversation by itself: read the history, write the next prompt, send it, round after round. It spends model quota and may change things you did not expect. You can interrupt or end the loop at any time.',
|
|
413
421
|
'auto.warn.cancel': 'Not now',
|
|
414
422
|
'auto.warn.confirm': 'Start',
|
|
415
|
-
'auto.interrupt': 'Skip this round',
|
|
416
|
-
'auto.interrupt.hint': 'Cancel the turn the assistant is running. The loop stays alive and re-plans the next step.',
|
|
423
|
+
'auto.interrupt': 'Skip this round',
|
|
424
|
+
'auto.interrupt.hint': 'Cancel the turn the assistant is running. The loop stays alive and re-plans the next step.',
|
|
417
425
|
'auto.stop.hint': 'End the autopilot. You can continue from here afterwards.',
|
|
418
426
|
'auto.stop': 'Stop autopilot',
|
|
419
427
|
'auto.running': 'Running',
|
|
@@ -440,6 +448,13 @@ window.__ModuleLoader__.load({
|
|
|
440
448
|
'settings.idleWait': 'How long to wait for the assistant',
|
|
441
449
|
'settings.idleWait.hint': 'When the loop is ready to send but the assistant is still working, it waits. Past this limit it parks, and Continue picks it up again. Long conversations need more time.',
|
|
442
450
|
'settings.idleWait.unit': 'minutes',
|
|
451
|
+
'version.current': 'Version',
|
|
452
|
+
'version.latest': 'Up to date',
|
|
453
|
+
'version.newest': 'npm has',
|
|
454
|
+
'version.checking': 'Checking for updates…',
|
|
455
|
+
'version.unknown': 'Cannot reach npm',
|
|
456
|
+
'version.how': 'How to update',
|
|
457
|
+
'version.recheck': 'Check again',
|
|
443
458
|
'settings.explore': 'Lookup budget',
|
|
444
459
|
'settings.explore.hint': 'A standard or deep polish looks things up first. This is how long that may take in total, and how many rounds it may spend. The rewrite runs separately with its own budget.',
|
|
445
460
|
'settings.explore.unit': 's',
|
|
@@ -564,6 +579,21 @@ window.__ModuleLoader__.load({
|
|
|
564
579
|
.dsh-op-head{margin-bottom:18px}
|
|
565
580
|
.dsh-op-h2{margin:0 0 4px;font-size:17px;font-weight:600;line-height:26px;letter-spacing:-.01em}
|
|
566
581
|
.dsh-op-sub{margin:0;color:var(--dsw-alias-label-secondary);font-size:12.5px;line-height:20px}
|
|
582
|
+
/* Version line: this plugin's own update affordance. dsh's plugin page only
|
|
583
|
+
computes installTarget for bundles in its official catalogue, so a third-party
|
|
584
|
+
plugin has no update button there. */
|
|
585
|
+
.dsh-op-version{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:9px;
|
|
586
|
+
font-size:11.5px;line-height:17px;color:var(--dsw-alias-label-secondary)}
|
|
587
|
+
.dsh-op-versionNow{font-variant-numeric:tabular-nums;
|
|
588
|
+
font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;
|
|
589
|
+
background:var(--dsw-alias-bg-base);border-radius:999px;padding:1px 8px;
|
|
590
|
+
color:var(--dsw-alias-label-primary)}
|
|
591
|
+
.dsh-op-versionNew{display:flex;flex-wrap:wrap;align-items:center;gap:8px;
|
|
592
|
+
font-size:11.5px;color:var(--dsw-alias-state-warn-primary)}
|
|
593
|
+
.dsh-op-versionBad{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));opacity:.8}
|
|
594
|
+
/* The busy-wait line: a live counter, so it is monospaced and does not reflow as
|
|
595
|
+
the seconds tick, and it holds a fixed height so the panel does not jump. */
|
|
596
|
+
.dsh-op-waiting{font-variant-numeric:tabular-nums;min-height:17px}
|
|
567
597
|
.dsh-op-card{display:flex;flex-direction:column;gap:2px;padding:2px 16px;
|
|
568
598
|
background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);
|
|
569
599
|
border-radius:14px}
|
|
@@ -2266,6 +2296,8 @@ window.__ModuleLoader__.load({
|
|
|
2266
2296
|
const [form, setForm] = useState(null) // what the user is editing
|
|
2267
2297
|
const [efforts, setEfforts] = useState({ state: 'idle', list: [], defaultEffort: null })
|
|
2268
2298
|
const [saveState, setSaveState] = useState({ state: 'idle', message: '' })
|
|
2299
|
+
// null while the first lookup is in flight; afterwards { current, latest, error }
|
|
2300
|
+
const [version, setVersion] = useState(null)
|
|
2269
2301
|
const [runs, setRuns] = useState([])
|
|
2270
2302
|
const [runsPick, setRunsPick] = useState(null)
|
|
2271
2303
|
const [runEntries, setRunEntries] = useState([])
|
|
@@ -2317,6 +2349,24 @@ window.__ModuleLoader__.load({
|
|
|
2317
2349
|
|
|
2318
2350
|
useEffect(() => { load() }, [load])
|
|
2319
2351
|
|
|
2352
|
+
/** Read this plugin's version against npm. Cached on the Host for 10 minutes. */
|
|
2353
|
+
const checkVersion = useCallback(async (force) => {
|
|
2354
|
+
try {
|
|
2355
|
+
const response = await fetch(force === true ? `${VERSION}?refresh=1` : VERSION)
|
|
2356
|
+
const payload = await response.json()
|
|
2357
|
+
if (payload?.ok !== true) throw new Error(payload?.error ?? 'HTTP error')
|
|
2358
|
+
setVersion({
|
|
2359
|
+
current: typeof payload.current === 'string' ? payload.current : '',
|
|
2360
|
+
latest: typeof payload.latest === 'string' ? payload.latest : null,
|
|
2361
|
+
error: typeof payload.error === 'string' ? payload.error : null,
|
|
2362
|
+
})
|
|
2363
|
+
} catch (failure) {
|
|
2364
|
+
setVersion({ current: '', latest: null, error: String(failure?.message ?? failure) })
|
|
2365
|
+
}
|
|
2366
|
+
}, [])
|
|
2367
|
+
|
|
2368
|
+
useEffect(() => { void checkVersion(false) }, [checkVersion])
|
|
2369
|
+
|
|
2320
2370
|
/** Sessions that have an archived run, newest first. */
|
|
2321
2371
|
const loadRuns = useCallback(async () => {
|
|
2322
2372
|
try {
|
|
@@ -2462,7 +2512,35 @@ window.__ModuleLoader__.load({
|
|
|
2462
2512
|
const pageBody = h('div', { className: 'dsh-op-page' },
|
|
2463
2513
|
h('div', { className: 'dsh-op-head' },
|
|
2464
2514
|
h('h2', { className: 'dsh-op-h2' }, t('settings.title')),
|
|
2465
|
-
h('p', { className: 'dsh-op-sub' }, t('settings.sub'))
|
|
2515
|
+
h('p', { className: 'dsh-op-sub' }, t('settings.sub')),
|
|
2516
|
+
// dsh's own plugin page can only offer 更新 for bundles in its official
|
|
2517
|
+
// catalogue, so a third-party plugin gets no update affordance there.
|
|
2518
|
+
// The version line puts that question here instead.
|
|
2519
|
+
h('div', { className: 'dsh-op-version' },
|
|
2520
|
+
h('span', { className: 'dsh-op-versionNow' },
|
|
2521
|
+
`${t('version.current')} ${version.current === '' ? '?' : version.current}`),
|
|
2522
|
+
version === null
|
|
2523
|
+
? h('span', { className: 'dsh-op-hint dsh-op-hint--inline' }, t('version.checking'))
|
|
2524
|
+
: version.error !== null && version.error !== ''
|
|
2525
|
+
? h('span', { className: 'dsh-op-versionBad' },
|
|
2526
|
+
`${t('version.unknown')} · ${version.error}`)
|
|
2527
|
+
: version.latest === null
|
|
2528
|
+
? h('span', { className: 'dsh-op-hint dsh-op-hint--inline' }, t('version.unknown'))
|
|
2529
|
+
: version.latest !== version.current
|
|
2530
|
+
? h('div', { className: 'dsh-op-versionNew' },
|
|
2531
|
+
h('span', null, `${t('version.newest')} ${version.latest}`),
|
|
2532
|
+
h('a', {
|
|
2533
|
+
className: 'dsh-op-link',
|
|
2534
|
+
href: 'https://www.npmjs.com/package/dsh-one-click-polish',
|
|
2535
|
+
target: '_blank',
|
|
2536
|
+
rel: 'noreferrer',
|
|
2537
|
+
}, t('version.how')),
|
|
2538
|
+
h('button', {
|
|
2539
|
+
type: 'button',
|
|
2540
|
+
className: 'dsh-op-link',
|
|
2541
|
+
onClick: () => { void checkVersion(true) },
|
|
2542
|
+
}, t('version.recheck')))
|
|
2543
|
+
: h('span', { className: 'dsh-op-hint dsh-op-hint--inline' }, t('version.latest')))),
|
|
2466
2544
|
h('div', { className: 'dsh-op-card' },
|
|
2467
2545
|
h('div', { className: 'dsh-op-cardTitle' }, t('settings.group.model')),
|
|
2468
2546
|
h('div', { className: 'dsh-op-field' },
|
package/index.js
CHANGED
|
@@ -23,7 +23,8 @@ export const name = 'one-click-polish'
|
|
|
23
23
|
export const inject = ['llm', 'agents', 'webServer']
|
|
24
24
|
|
|
25
25
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
26
|
-
import { join } from 'node:path'
|
|
26
|
+
import { dirname, join } from 'node:path'
|
|
27
|
+
import { fileURLToPath } from 'node:url'
|
|
27
28
|
|
|
28
29
|
const PREFIX = '/plugins/dsh-one-click-polish'
|
|
29
30
|
const DEFAULT_MAX_INPUT_CHARS = 12000
|
|
@@ -466,6 +467,61 @@ async function produceRewrite(ctx, settings, mode, strength, research, input, ag
|
|
|
466
467
|
return text
|
|
467
468
|
}
|
|
468
469
|
|
|
470
|
+
/**
|
|
471
|
+
* This plugin's own version, and the newest one on npm.
|
|
472
|
+
*
|
|
473
|
+
* dsh's plugin page only offers an update button for bundles in its official
|
|
474
|
+
* catalogue, because `installTarget` is computed from that catalogue. A third-party
|
|
475
|
+
* plugin therefore has no update affordance there at all, so the question "is there
|
|
476
|
+
* a newer version?" is answered here instead, on the plugin's own settings page.
|
|
477
|
+
*/
|
|
478
|
+
const VERSION_TTL_MS = 10 * 60 * 1000
|
|
479
|
+
let versionCache = { at: 0, latest: null, error: null }
|
|
480
|
+
|
|
481
|
+
async function versionInfo() {
|
|
482
|
+
let current = ''
|
|
483
|
+
try {
|
|
484
|
+
// Resolve from this module rather than the process cwd: the Host may run with
|
|
485
|
+
// any working directory, and this file sits next to its own manifest.
|
|
486
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
487
|
+
const manifest = JSON.parse(readFileSync(join(here, 'package.json'), 'utf8'))
|
|
488
|
+
current = typeof manifest.version === 'string' ? manifest.version : ''
|
|
489
|
+
} catch { /* a missing manifest just means no version to report */ }
|
|
490
|
+
|
|
491
|
+
if (versionCache.latest !== null && Date.now() - versionCache.at < VERSION_TTL_MS) {
|
|
492
|
+
return { current, latest: versionCache.latest, error: null, checked: versionCache.at }
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// The registry is the only source of truth for "what is published"; a failed
|
|
496
|
+
// lookup must not look like "you are up to date".
|
|
497
|
+
let latest = null
|
|
498
|
+
let error = null
|
|
499
|
+
try {
|
|
500
|
+
const controller = new AbortController()
|
|
501
|
+
const timer = setTimeout(() => controller.abort(), 8000)
|
|
502
|
+
const response = await fetch('https://registry.npmjs.org/dsh-one-click-polish/latest', {
|
|
503
|
+
signal: controller.signal,
|
|
504
|
+
headers: { accept: 'application/json' },
|
|
505
|
+
})
|
|
506
|
+
clearTimeout(timer)
|
|
507
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
508
|
+
const payload = await response.json()
|
|
509
|
+
latest = typeof payload?.version === 'string' ? payload.version : null
|
|
510
|
+
} catch (failure) {
|
|
511
|
+
error = textOf(failure)
|
|
512
|
+
}
|
|
513
|
+
versionCache = { at: Date.now(), latest, error }
|
|
514
|
+
return { current, latest, error, checked: Date.now() }
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async function versionRoute(req, res) {
|
|
518
|
+
// `refresh=1` drops the TTL cache: the panel offers a recheck, and a button that
|
|
519
|
+
// silently answers from cache would look broken.
|
|
520
|
+
const url = new URL(req.url ?? '/', 'http://localhost')
|
|
521
|
+
if (url.searchParams.get('refresh') === '1') versionCache.at = 0
|
|
522
|
+
sendJson(res, 200, { ok: true, ...(await versionInfo()) })
|
|
523
|
+
}
|
|
524
|
+
|
|
469
525
|
async function statusRoute(ctx, req, res) {
|
|
470
526
|
const url = new URL(req.url ?? '/', 'http://localhost')
|
|
471
527
|
const sessionId = url.searchParams.get('sessionId') ?? undefined
|
|
@@ -2466,6 +2522,13 @@ export function apply(ctx, config) {
|
|
|
2466
2522
|
})
|
|
2467
2523
|
|
|
2468
2524
|
|
|
2525
|
+
ctx.effect(() => server.register({
|
|
2526
|
+
kind: 'exact',
|
|
2527
|
+
path: `${PREFIX}/version`,
|
|
2528
|
+
handler: (req, res) => versionRoute(req, res)
|
|
2529
|
+
.catch(error => sendJson(res, 500, { ok: false, error: textOf(error) })),
|
|
2530
|
+
}), 'one-click-polish: version route')
|
|
2531
|
+
|
|
2469
2532
|
ctx.effect(() => server.register({
|
|
2470
2533
|
kind: 'exact',
|
|
2471
2534
|
path: `${PREFIX}/status`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-one-click-polish",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "One-click polish, expand and autopilot for the DeepSeek Harness composer: rewrite the draft with the model the session is switched to, expand a one-line wish into a task brief, or let an AI drive a goal round after round.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|