@godv61/dsh-task-engine 0.22.2 → 0.22.4
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/.p0-test.mjs +98 -5
- package/README.md +11 -3
- package/docs/manual.html +1 -1
- package/lib/client.d.ts +6 -0
- package/lib/client.js +142 -20
- package/lib/client.js.map +3 -3
- package/lib/controller.d.ts +14 -0
- package/lib/controller.js +41 -4
- package/lib/controller.js.map +1 -1
- package/lib/dev-task.js +66 -12
- package/lib/dev-task.js.map +1 -1
- package/lib/engine.d.ts +2 -0
- package/lib/engine.js +1 -0
- package/lib/engine.js.map +1 -1
- package/package.json +6 -2
package/.p0-test.mjs
CHANGED
|
@@ -33,6 +33,7 @@ async function assertThrows(fn, fragment, msg) {
|
|
|
33
33
|
|
|
34
34
|
function makeFs(initial = {}) {
|
|
35
35
|
const files = new Map(Object.entries(initial))
|
|
36
|
+
const versions = new Map()
|
|
36
37
|
return {
|
|
37
38
|
async resolve(relPath, opts) {
|
|
38
39
|
const base = opts && opts.cwd ? String(opts.cwd).replace(/\\/g, '/') : ''
|
|
@@ -42,8 +43,31 @@ function makeFs(initial = {}) {
|
|
|
42
43
|
async readText(target) {
|
|
43
44
|
return files.get(target.targetKey)
|
|
44
45
|
},
|
|
45
|
-
async
|
|
46
|
-
|
|
46
|
+
async lstat(path, opts) {
|
|
47
|
+
const base = opts && opts.cwd ? String(opts.cwd).replace(/\\/g, '/') : ''
|
|
48
|
+
const key = base ? `${base}/${path}` : path
|
|
49
|
+
if (!files.has(key)) return undefined
|
|
50
|
+
return { version: `v${versions.get(key) ?? 0}` }
|
|
51
|
+
},
|
|
52
|
+
async writeText(target, content, expected, signal, sandboxPolicy) {
|
|
53
|
+
const key = target.targetKey
|
|
54
|
+
if (expected !== undefined && expected !== null) {
|
|
55
|
+
if (expected.kind === 'createIfAbsent' && files.has(key)) {
|
|
56
|
+
const err = new Error('already exists')
|
|
57
|
+
err.code = 'FS_NOT_OBSERVED'
|
|
58
|
+
throw err
|
|
59
|
+
}
|
|
60
|
+
if (expected.kind === 'replaceIfVersion') {
|
|
61
|
+
const current = `v${versions.get(key) ?? 0}`
|
|
62
|
+
if (!files.has(key) || current !== expected.version) {
|
|
63
|
+
const err = new Error('stale version')
|
|
64
|
+
err.code = 'FS_STALE_VERSION'
|
|
65
|
+
throw err
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
files.set(key, content)
|
|
70
|
+
versions.set(key, (versions.get(key) ?? 0) + 1)
|
|
47
71
|
},
|
|
48
72
|
_files: files,
|
|
49
73
|
}
|
|
@@ -140,7 +164,8 @@ await assertThrows(
|
|
|
140
164
|
const prop = await exe({ operation: 'init', phase: 'propose', content: '# 项目\n示例' }, EXEC)
|
|
141
165
|
assert(prop.includes('proposed create'), 'propose previews without writing')
|
|
142
166
|
assert(fs._files.get('AGENTS.md') === undefined, 'propose writes nothing')
|
|
143
|
-
const
|
|
167
|
+
const hash9 = /content hash: ([0-9a-f]{64})/.exec(prop)[1]
|
|
168
|
+
const app = await exe({ operation: 'init', phase: 'apply', content: '# 项目\n示例', expected_hash: hash9 }, EXEC)
|
|
144
169
|
assert(app.includes('wrote ./AGENTS.md'), 'apply writes the file')
|
|
145
170
|
assert(fs._files.get('AGENTS.md') === '# 项目\n示例', 'apply persisted the draft')
|
|
146
171
|
}
|
|
@@ -149,16 +174,35 @@ await assertThrows(
|
|
|
149
174
|
{
|
|
150
175
|
const fs = makeFs({ 'AGENTS.md': '# 已有治理文件\n' })
|
|
151
176
|
const exe = await registered(fs)
|
|
177
|
+
const prop10 = await exe({ operation: 'init', phase: 'propose', content: '# new' }, EXEC)
|
|
178
|
+
const hash10 = /content hash: ([0-9a-f]{64})/.exec(prop10)[1]
|
|
179
|
+
const existingHash10 = /existing hash: ([0-9a-f]{64})/.exec(prop10)[1]
|
|
180
|
+
assert(existingHash10 !== undefined, 'propose returns the existing-file hash when the file exists')
|
|
152
181
|
await assertThrows(
|
|
153
|
-
() => exe({ operation: 'init', phase: 'apply', content: '# new' }, EXEC),
|
|
182
|
+
() => exe({ operation: 'init', phase: 'apply', content: '# new', expected_hash: hash10 }, EXEC),
|
|
183
|
+
'requires existing_hash',
|
|
184
|
+
'apply without existing_hash on an existing file is rejected',
|
|
185
|
+
)
|
|
186
|
+
await assertThrows(
|
|
187
|
+
() => exe({ operation: 'init', phase: 'apply', content: '# new', expected_hash: hash10, existing_hash: '0'.repeat(64) }, EXEC),
|
|
188
|
+
'existing_hash mismatch',
|
|
189
|
+
'apply with a stale existing_hash is rejected (TOCTOU)',
|
|
190
|
+
)
|
|
191
|
+
await assertThrows(
|
|
192
|
+
() => exe({ operation: 'init', phase: 'apply', content: '# new', expected_hash: hash10, existing_hash: existingHash10 }, EXEC),
|
|
154
193
|
'protected',
|
|
155
194
|
'apply without overwrite on an existing file is rejected',
|
|
156
195
|
)
|
|
157
196
|
await assertThrows(
|
|
158
|
-
() => exe({ operation: 'init', phase: 'apply', content: '# new', overwrite: true }, EXEC),
|
|
197
|
+
() => exe({ operation: 'init', phase: 'apply', content: '# new', expected_hash: hash10, existing_hash: existingHash10, overwrite: true }, EXEC),
|
|
159
198
|
'approval',
|
|
160
199
|
'overwrite requires an approval service',
|
|
161
200
|
)
|
|
201
|
+
await assertThrows(
|
|
202
|
+
() => exe({ operation: 'init', phase: 'apply', content: '# new' }, EXEC),
|
|
203
|
+
'requires expected_hash',
|
|
204
|
+
'apply without expected_hash is rejected',
|
|
205
|
+
)
|
|
162
206
|
}
|
|
163
207
|
|
|
164
208
|
// ── 11. a project cannot shadow a bundled core rule ────────────────────────
|
|
@@ -615,4 +659,53 @@ function memProbe(files) {
|
|
|
615
659
|
assert(controller.includes('not an allowed workspace'), 'remote rejects system-wide roots')
|
|
616
660
|
}
|
|
617
661
|
|
|
662
|
+
// ── 33. 0.23: verify runs in the task's recorded project root ──────────────
|
|
663
|
+
{
|
|
664
|
+
const fs = makeFs({ '.dsh/eng.json': JSON.stringify({ flow: 'standard' }), 'package.json': '{}' })
|
|
665
|
+
const seenWorkdirs = []
|
|
666
|
+
const shell = {
|
|
667
|
+
resolve(request) { return { command: request.command, workdir: request.workdir ?? '', timeoutMs: request.timeoutMs } },
|
|
668
|
+
async run(spec) { seenWorkdirs.push(spec.workdir); return { exitCode: 0, timedOut: false, aborted: false, stdout: { text: 'ok' }, stderr: { text: '' } } },
|
|
669
|
+
}
|
|
670
|
+
const ctx = makeCtx(fs, undefined)
|
|
671
|
+
ctx.get = (service) => (service === 'shell' ? shell : service === 'approval' ? undefined : undefined)
|
|
672
|
+
registerDevTask(ctx)
|
|
673
|
+
const exe = ctx._tools[0].execute
|
|
674
|
+
await exe({ operation: 'create', task_id: 'VR-1', title: 'x', branch: 'main' }, EXEC)
|
|
675
|
+
await exe({ operation: 'verify', task_id: 'VR-1' }, EXEC)
|
|
676
|
+
const record = JSON.parse(fs._files.get('.dsh/task-VR-1.json'))
|
|
677
|
+
assert(seenWorkdirs.length === 1, 'bare verify ran the default command')
|
|
678
|
+
assert(seenWorkdirs[0] === (record.root ?? ''), 'verify ran in the recorded project root, not the session cwd')
|
|
679
|
+
assert(record.verification.receipt.root === record.root, 'receipt binds the recorded project root')
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// ── 34. 0.23: revision increments on every write (CAS foundation) ───────────
|
|
683
|
+
{
|
|
684
|
+
const fs = makeFs({ '.dsh/eng.json': JSON.stringify({ flow: 'standard' }) })
|
|
685
|
+
const exe = await registered(fs)
|
|
686
|
+
await exe({ operation: 'create', task_id: 'CAS-1', title: 'x', branch: 'main' }, EXEC)
|
|
687
|
+
const r1 = JSON.parse(fs._files.get('.dsh/task-CAS-1.json')).revision
|
|
688
|
+
await exe({ operation: 'scope', task_id: 'CAS-1', files: ['a'] }, EXEC)
|
|
689
|
+
const r2 = JSON.parse(fs._files.get('.dsh/task-CAS-1.json')).revision
|
|
690
|
+
assert(r1 === 1 && r2 === 2, 'revision increments on every write')
|
|
691
|
+
const devtask = readFileSync('./lib/dev-task.js', 'utf8')
|
|
692
|
+
assert(devtask.includes('changed concurrently'), 'concurrent-write CAS guard is present')
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// ── 35. 0.23: Remote path guard + workspace registry ────────────────────────
|
|
696
|
+
{
|
|
697
|
+
const ctl = await import('./lib/controller.js')
|
|
698
|
+
assert(ctl.checkedPath('D:/proj/one') === 'D:/proj/one', 'absolute workspace passes')
|
|
699
|
+
assert(ctl.checkedPath('D:/proj/one/') === 'D:/proj/one', 'trailing slash is normalized')
|
|
700
|
+
await assertThrows(() => ctl.checkedPath('relative/path'), 'must be absolute', 'relative path is rejected')
|
|
701
|
+
await assertThrows(() => ctl.checkedPath('C:/Windows/system32'), 'not an allowed workspace', 'system root is rejected')
|
|
702
|
+
await assertThrows(() => ctl.checkedPath('c:/windows'), 'not an allowed workspace', 'case variant of a system root is rejected')
|
|
703
|
+
ctl.registerWorkspace('D:/proj/one')
|
|
704
|
+
assert(ctl.checkedPath('D:/PROJ/one') === 'D:/PROJ/one', 'registered workspace passes under any case')
|
|
705
|
+
await assertThrows(() => ctl.checkedPath('D:/other-project'), 'not registered', 'unregistered workspace is rejected once the registry is active')
|
|
706
|
+
ctl.enableStrictWorkspaces()
|
|
707
|
+
await assertThrows(() => ctl.checkedPath('D:/still-unregistered'), 'not registered', 'strict mode keeps rejecting unregistered workspaces')
|
|
708
|
+
assert(ctl.checkedPath('D:/proj/one') === 'D:/proj/one', 'strict mode still admits registered workspaces')
|
|
709
|
+
}
|
|
710
|
+
|
|
618
711
|
console.log(`\nP0 acceptance: ${passed} checks passed`)
|
package/README.md
CHANGED
|
@@ -137,13 +137,19 @@ dsh plugin --profile <name> add .
|
|
|
137
137
|
|
|
138
138
|
> 钩子读 `.dsh/eng.json` + `.dsh/task-*.json`,跨仓库零依赖、自包含。
|
|
139
139
|
|
|
140
|
+
### 门禁边界:hook / host / CI 各自负责什么(0.22.3 起明确)
|
|
141
|
+
|
|
142
|
+
- **git hook = 本地即时反馈**:拦常规 `git commit`(校验任务存在、阶段、范围、消息、快照 hash、敏感路径)。它挡不住 `git commit --no-verify`、`core.hooksPath` 替换或手改 task 记录——这是定位,不是漏洞。
|
|
143
|
+
- **host 工具流 = 正常路径的强约束**:`dev_task` 的阶段/范围/回执/审批门禁在工具调用时生效;Remote 路径校验 + workspace registry(host 集成 `registerWorkspace` 后生效)挡住越界访问;文件沙箱是最终文件边界。
|
|
144
|
+
- **CI = 最终可信门禁**:CI 里重新验证 task id、快照 hash、staged 范围、提交消息、真实验证退出码、高风险回执、hook 是否被绕过——这是唯一不依赖"模型配合"的可信验证点。
|
|
145
|
+
|
|
140
146
|
## 项目初始化(init)
|
|
141
147
|
|
|
142
148
|
二开 / 遗留项目常常没有文档,AI 接手前需要先「认识」这个项目。`dev_task`(operation=init)走 **inspect → propose → apply** 三阶段管理项目根的 `AGENTS.md`,绝不直接覆盖已有治理文件:
|
|
143
149
|
|
|
144
150
|
1. `inspect`——读取现有 `AGENTS.md`(若有)返回给模型;没有则提示扫描项目。
|
|
145
|
-
2. `propose
|
|
146
|
-
3. `apply
|
|
151
|
+
2. `propose`——模型扫描(目录结构、技术栈、构建/运行命令、约定、红线)后给出全文草稿,此步**只预览不落盘**,并返回草稿内容 hash(已有文件时另附现有文件 hash)。
|
|
152
|
+
3. `apply`——落盘。**必须传回 propose 返回的 `expected_hash`**(防预览与落盘之间内容被换);覆盖已有 `AGENTS.md` 必须 `overwrite: true` 且**经人工批准**,传回 `existing_hash` 可防审批期间文件被他人改动(TOCTOU),否则拒绝。
|
|
147
153
|
|
|
148
154
|
- **为什么是 `AGENTS.md`**:DSH 平台会把项目根的 `AGENTS.md` **自动注入到每个会话**——生成一次,之后每个任务开工 AI 都自带这份项目认知,引擎无需额外的注入逻辑。
|
|
149
155
|
- **行数上限**:`AGENTS.md` 每次都进上下文,200 行(约 4–6K token)是硬约束,逼着只写「项目是什么 → 怎么跑 → 结构 → 约定 → 坑」,而不是塞长篇文档。
|
|
@@ -179,4 +185,6 @@ dsh plugin --profile <name> add .
|
|
|
179
185
|
- **通用项目适配(0.21.0)**:① 新增项目适配层 `src/project.ts`——按特征文件识别 Node / Java / Python / Go / Rust 类型,从 workspace 向上发现项目根(`.git` / `.dsh` / 语言特征文件);② 各语言默认验证命令(`npm test` / `mvn -q test` / `python -m pytest` / `go test ./...` / `cargo test`),`verify` 不带 `command` 时按 `.dsh/eng.json` 的 `verify_command` → 语言默认链自动跑真实命令;③ 治理文件识别扩展至 `AGENTS.md`、`CLAUDE.md`、`.cursorrules`,`init inspect` 一并报告治理文件、项目根、语言栈与项目级 skill/rule 目录(约定 `.dsh/rules/*.md`、`.dsh/skills/<name>/SKILL.md`);④ 提交钩子加风险策略——触及敏感路径(`.git` / `.env` / credentials / secrets;`.dsh` 的任务记录与流程配置由快照 hash 与豁免保护)要求任务为 `high_risk` 且验证有真实命令回执,否则拒绝;⑤ 初始化写入越界保护——`init` / `create` 的写入目标必须落在项目根内;任务记录新增 `root` / `project_type` 字段。
|
|
180
186
|
- **审计和发布质量(0.22.0)**:① 流程快照 hash——任务创建时固化 `config` 的 SHA-256,工具与提交钩子读任务记录时校验,被手改的快照一律拒绝继续;② 提交钩子完整性检测——新增 `dev_task verify_hook`,比对 `.git/hooks/commit-msg` 与内置门禁的 hash,被替换/篡改立即报错;③ 风险降级审批——新增 `set_risk`,`high_risk → standard` 必须人工批准并落 `risk_downgrades` 审计记录;④ 验证回执绑定项目根——receipt 记录 `root`,与任务 workspace 绑定;⑤ 内置规则指纹锁定——创建时固化内置规则内容指纹,包升级后 `status` 报 `bindings_drift` 而非静默换规则;⑥ 多项目 / 多语言 / 多任务测试矩阵补强。
|
|
181
187
|
- **沙箱写入修复(0.22.1)**:`dev_task` 的文件写入此前没有携带按调用传递的沙箱策略(`writeText` 的 `sandboxPolicy` 参数),在 DSH 文件沙箱下会把 workspace 内的任务记录写入误判为越界而拒绝(`file access denied under workspace-write mode`,且会话策略变化无法影响它);现在每次写入显式携带 `{ mode, workspaceRoot: 会话目录 }`,并新增 `sandbox_permissions` 参数(`workspace-write` / `danger-full-access`)作为被拒后的一次性升级路径。
|
|
182
|
-
- **质量修补(0.22.2,采纳 codex 评审五项)**:① client typecheck 修复——`project.ts` 不再依赖 `node:path`,浏览器面可完整类型检查;② sandbox 升级补审批——`sandbox_permissions` 必须与 `justification` 成对出现,`danger-full-access` 需人工一次批准,无审批服务即拒绝;③ Remote 任意路径设防——工作台 Remote 拒绝非绝对路径与系统级根目录;④ `set_risk` 升到 `high_risk` 时受流程能力门约束(`minimal`/`agile` 拒绝,不再绕过 `create` 的检查);⑤ `loadTask` 补齐旧记录缺失字段(items / verification / review / commits),损坏记录 fail-closed 而非引擎裸崩。
|
|
188
|
+
- **质量修补(0.22.2,采纳 codex 评审五项)**:① client typecheck 修复——`project.ts` 不再依赖 `node:path`,浏览器面可完整类型检查;② sandbox 升级补审批——`sandbox_permissions` 必须与 `justification` 成对出现,`danger-full-access` 需人工一次批准,无审批服务即拒绝;③ Remote 任意路径设防——工作台 Remote 拒绝非绝对路径与系统级根目录;④ `set_risk` 升到 `high_risk` 时受流程能力门约束(`minimal`/`agile` 拒绝,不再绕过 `create` 的检查);⑤ `loadTask` 补齐旧记录缺失字段(items / verification / review / commits),损坏记录 fail-closed 而非引擎裸崩。
|
|
189
|
+
- **可信边界加固(0.22.3,采纳 GPT 评审)**:① 验证命令统一在任务记录的项目根运行(monorepo 子目录不再跑错目录),receipt.root 与任务根强校验;② 旧任务(无 frozen 快照)禁止升 `high_risk`——迁移或重建后才可;③ `init apply` 强制 `expected_hash`,新增 `existing_hash` 防审批期间文件被换(TOCTOU);④ 任务记录新增 `revision`,每次写入 compare-and-swap,并发覆盖直接报错(`changed concurrently`);⑤ 工作台 Remote 增加 host workspace registry(`registerWorkspace` 供 harness 集成,注册后未授权路径一律拒绝);⑥ sandbox 升级审批展示 workspace;⑦ 文档明确 hook(本地反馈)/ host(工具流约束)/ CI(最终可信门禁)三层职责边界。
|
|
190
|
+
- **并发与发布闭环(0.22.4,采纳 REVIEW-0.22.3)**:① `init apply` 在目标文件存在时**强制** `existing_hash`(不再可选),propose→apply 之间文件被篡改直接 fail-closed;② 任务写入升级为真正的原子 CAS——逻辑 `revision` 检查 + dsh-fs 的 `lstat` 版本号 `replaceIfVersion` write intent,并发写入报 `FS_STALE_VERSION` 而非静默覆盖;③ workspace registry 增加 `enableStrictWorkspaces()` 生产模式(空 registry 不再回退放行);④ `exports["./client"]` 补 `types` 条件(`lib/client.d.ts` 随构建生成);⑤ 新增 `npm run verify:package` 发布包黑盒验证(pack → 白名单一致性 → 真实安装 → 主入口 import → client 注册 → 包内 121 checks → CLI 语法)。REVIEW 指出的 Typert invocation context、host 可信状态签名、CI 最终门禁仍属 harness 层,集成点已在文档标注。
|
package/docs/manual.html
CHANGED
|
@@ -229,7 +229,7 @@ pnpm install && pnpm run build && pnpm dsh web</code></pre>
|
|
|
229
229
|
<ol>
|
|
230
230
|
<li><b>inspect</b>:对 AI 说「帮我初始化这个项目」;AI 读现有 <code>AGENTS.md</code>(若有)返回,没有则扫目录结构、技术栈、构建 / 运行命令、约定与红线。同时报告:项目根(自动从 workspace 向上发现)、语言栈(Node / Java / Python / Go / Rust)与默认验证命令、其它治理文件(<code>CLAUDE.md</code>、<code>.cursorrules</code>——init 只管理 AGENTS.md,不覆盖它们)、项目级 skill/rule 目录(<code>.dsh/rules/*.md</code>、<code>.dsh/skills/<name>/SKILL.md</code>)。</li>
|
|
231
231
|
<li><b>propose</b>:AI 写成 <code>AGENTS.md</code> 草稿,此步<b>只预览、不落盘</b>,同样硬校验<b>行数 ≤ 200 行</b>,并返回内容 hash。</li>
|
|
232
|
-
<li><b>apply</b
|
|
232
|
+
<li><b>apply</b>:落盘。<b>必须传回 propose 返回的 <code>expected_hash</code></b>(缺失或与内容不一致一律拒绝,防止预览与落盘之间内容被换);覆盖已有 <code>AGENTS.md</code> 时<b>还必须传回 <code>existing_hash</code></b>(证明审批期间现有文件未被他人改动)并带 <code>overwrite</code> 且<b>经人工批准</b>。写入目标必须在项目根内。</li>
|
|
233
233
|
</ol>
|
|
234
234
|
<pre><code>项目根/
|
|
235
235
|
├─ AGENTS.md ← init 生成,DSH 每会话自动注入
|
package/lib/client.d.ts
ADDED
package/lib/client.js
CHANGED
|
@@ -4469,6 +4469,7 @@ var surface = "var(--dsw-alias-bg-layer-2)";
|
|
|
4469
4469
|
var danger = "var(--dsw-alias-state-error-primary)";
|
|
4470
4470
|
var success = "var(--dsw-alias-state-success-primary)";
|
|
4471
4471
|
var warn = "var(--dsw-alias-state-warn-primary)";
|
|
4472
|
+
var brand = "var(--dsw-alias-state-business-primary)";
|
|
4472
4473
|
var styles = {
|
|
4473
4474
|
wrap: { display: "flex", flexDirection: "column", gap: 16, padding: 20, maxWidth: 900 },
|
|
4474
4475
|
head: { display: "flex", flexDirection: "column", gap: 4 },
|
|
@@ -4533,7 +4534,54 @@ var styles = {
|
|
|
4533
4534
|
noticeTitle: { color: warn, fontSize: 13, fontWeight: 600, display: "flex", alignItems: "center", gap: 8 },
|
|
4534
4535
|
okText: { color: success, fontSize: 13, display: "flex", alignItems: "center", gap: 6 },
|
|
4535
4536
|
status: { color: textTertiary, fontSize: 13, display: "flex", alignItems: "center", gap: 6 },
|
|
4536
|
-
actions: {
|
|
4537
|
+
actions: {
|
|
4538
|
+
display: "flex",
|
|
4539
|
+
alignItems: "center",
|
|
4540
|
+
gap: 12,
|
|
4541
|
+
marginTop: 4,
|
|
4542
|
+
paddingTop: 14,
|
|
4543
|
+
borderTop: `1px solid ${border}`
|
|
4544
|
+
},
|
|
4545
|
+
// ── flow preset picker ────────────────────────────────────────────────────
|
|
4546
|
+
flowGrid: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(230px, 1fr))", gap: 10 },
|
|
4547
|
+
flowOption: {
|
|
4548
|
+
display: "flex",
|
|
4549
|
+
flexDirection: "column",
|
|
4550
|
+
gap: 6,
|
|
4551
|
+
alignItems: "stretch",
|
|
4552
|
+
border: `1px solid ${border}`,
|
|
4553
|
+
borderRadius: 10,
|
|
4554
|
+
padding: "10px 12px",
|
|
4555
|
+
background: surface,
|
|
4556
|
+
cursor: "pointer",
|
|
4557
|
+
color: text,
|
|
4558
|
+
fontSize: 13,
|
|
4559
|
+
textAlign: "left",
|
|
4560
|
+
fontFamily: "inherit",
|
|
4561
|
+
transition: "border-color 120ms ease, box-shadow 120ms ease"
|
|
4562
|
+
},
|
|
4563
|
+
flowOptionActive: {
|
|
4564
|
+
borderColor: brand,
|
|
4565
|
+
boxShadow: `0 0 0 1px ${brand}`
|
|
4566
|
+
},
|
|
4567
|
+
flowOptionTitle: {
|
|
4568
|
+
display: "flex",
|
|
4569
|
+
alignItems: "center",
|
|
4570
|
+
gap: 7,
|
|
4571
|
+
fontSize: 13,
|
|
4572
|
+
fontWeight: 600,
|
|
4573
|
+
color: text
|
|
4574
|
+
},
|
|
4575
|
+
flowOptionDesc: { color: textTertiary, fontSize: 12, lineHeight: 1.45, margin: 0 },
|
|
4576
|
+
// ── init preview ──────────────────────────────────────────────────────────
|
|
4577
|
+
mdPreview: {
|
|
4578
|
+
border: `1px solid ${border}`,
|
|
4579
|
+
borderRadius: 8,
|
|
4580
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
4581
|
+
padding: "12px 14px",
|
|
4582
|
+
maxHeight: 420,
|
|
4583
|
+
overflowY: "auto"
|
|
4584
|
+
}
|
|
4537
4585
|
};
|
|
4538
4586
|
|
|
4539
4587
|
// src/client/TaskEngineSection.tsx
|
|
@@ -4722,8 +4770,8 @@ function deriveCapabilities(config) {
|
|
|
4722
4770
|
if (config.commit.file_scope) caps.push("file_scope");
|
|
4723
4771
|
return caps;
|
|
4724
4772
|
}
|
|
4725
|
-
function preset(id, version,
|
|
4726
|
-
return { id, version, label, description, capabilities: deriveCapabilities(config), config };
|
|
4773
|
+
function preset(id, version, label2, description, config) {
|
|
4774
|
+
return { id, version, label: label2, description, capabilities: deriveCapabilities(config), config };
|
|
4727
4775
|
}
|
|
4728
4776
|
var FLOW_PRESETS = {
|
|
4729
4777
|
standard: preset("standard", 1, "\u6807\u51C6\u7814\u53D1", "\u9700\u6C42\u8BC4\u5BA1 \u2192 \u8BBE\u8BA1 \u2192 \u5F00\u53D1 \u2192 \u4EA4\u4ED8 \u2192 \u4EE3\u7801\u5BA1\u6838\uFF0C\u542B\u4EA7\u7269\u95E8 + \u786E\u8BA4\u95E8", STANDARD),
|
|
@@ -4873,9 +4921,28 @@ function TaskEngineSection(props) {
|
|
|
4873
4921
|
] }),
|
|
4874
4922
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.field, children: [
|
|
4875
4923
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.fieldLabel, children: "\u6D41\u7A0B\u9884\u8BBE" }),
|
|
4876
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.
|
|
4877
|
-
|
|
4878
|
-
|
|
4924
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.flowGrid, children: FLOW_OPTIONS.map((option) => {
|
|
4925
|
+
const active = flow === option.id;
|
|
4926
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
4927
|
+
"button",
|
|
4928
|
+
{
|
|
4929
|
+
type: "button",
|
|
4930
|
+
"aria-pressed": active,
|
|
4931
|
+
onClick: () => {
|
|
4932
|
+
selectFlow(option.id);
|
|
4933
|
+
},
|
|
4934
|
+
style: { ...styles.flowOption, ...active ? styles.flowOptionActive : {} },
|
|
4935
|
+
children: [
|
|
4936
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles.flowOptionTitle, children: [
|
|
4937
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state: active ? "done" : "ongoing", size: 8 }),
|
|
4938
|
+
option.label
|
|
4939
|
+
] }),
|
|
4940
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.flowOptionDesc, children: option.description })
|
|
4941
|
+
]
|
|
4942
|
+
},
|
|
4943
|
+
option.id
|
|
4944
|
+
);
|
|
4945
|
+
}) }),
|
|
4879
4946
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.hint, children: FLOW_OPTIONS.find((option) => option.id === flow)?.description })
|
|
4880
4947
|
] }),
|
|
4881
4948
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionCard, { icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconBranchOutline16, { size: 16 }), title: "\u6D41\u7A0B\u8282\u70B9", hint: "\u8BE5\u6D41\u7A0B\u9884\u8BBE\u56FA\u5B9A\u7684\u9636\u6BB5\u987A\u5E8F\uFF1B\u6539\u53D8\u6D41\u7A0B\u8BF7\u5207\u6362\u4E0A\u9762\u7684\u9884\u8BBE\u3002", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.chips, children: stages.map((stage) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Pill, { active: stage === (FLOW_PRESETS[flow]?.config.start_stage ?? ""), title: stage === FLOW_PRESETS[flow]?.config.start_stage ? "\u8D77\u59CB\u9636\u6BB5" : void 0, children: stage }, stage)) }) }),
|
|
@@ -5113,12 +5180,25 @@ function InitPanel({ workspace, remote }) {
|
|
|
5113
5180
|
{ style: styles.muted },
|
|
5114
5181
|
"\u9879\u76EE\u6839 AGENTS.md \u4F1A\u88AB DSH \u81EA\u52A8\u6CE8\u5165\u5230\u6BCF\u4E2A\u4F1A\u8BDD\u3002\u521D\u59CB\u5316\u4E00\u6B21\uFF0C\u4E4B\u540E\u6BCF\u6B21\u4EFB\u52A1\u5F00\u5DE5 AI \u90FD\u81EA\u5E26\u8FD9\u4EFD\u9879\u76EE\u8BA4\u77E5\uFF08\u9879\u76EE\u662F\u4EC0\u4E48 \u2192 \u600E\u4E48\u8DD1 \u2192 \u7ED3\u6784 \u2192 \u7EA6\u5B9A \u2192 \u5751\uFF09\u3002"
|
|
5115
5182
|
),
|
|
5116
|
-
exists && !editing && draft === null ? (0, import_react2.createElement)(
|
|
5183
|
+
exists && !editing && draft === null ? (0, import_react2.createElement)(
|
|
5184
|
+
"div",
|
|
5185
|
+
{ style: styles.mdPreview },
|
|
5186
|
+
(0, import_react2.createElement)(import_dsh_client_ui_primitives2.MarkdownText, { text: view.content, labels: MD_LABELS })
|
|
5187
|
+
) : null,
|
|
5117
5188
|
draft !== null ? (0, import_react2.createElement)(
|
|
5118
5189
|
"div",
|
|
5119
5190
|
{ style: styles.section },
|
|
5120
|
-
(0, import_react2.createElement)(
|
|
5121
|
-
|
|
5191
|
+
(0, import_react2.createElement)(
|
|
5192
|
+
"div",
|
|
5193
|
+
{ style: styles.sourceRow },
|
|
5194
|
+
(0, import_react2.createElement)(import_dsh_client_ui_primitives2.StateDot, { state: "ongoing", size: 8 }),
|
|
5195
|
+
(0, import_react2.createElement)("span", { style: styles.sourceBadge }, `AI \u8349\u7A3F \xB7 ${draft.lines} \u884C \xB7 \u5C1A\u672A\u5199\u5165`)
|
|
5196
|
+
),
|
|
5197
|
+
(0, import_react2.createElement)(
|
|
5198
|
+
"div",
|
|
5199
|
+
{ style: styles.mdPreview },
|
|
5200
|
+
(0, import_react2.createElement)(import_dsh_client_ui_primitives2.MarkdownText, { text: draft.content, labels: MD_LABELS })
|
|
5201
|
+
)
|
|
5122
5202
|
) : null,
|
|
5123
5203
|
editing ? (0, import_react2.createElement)("textarea", {
|
|
5124
5204
|
style: { ...styles.textarea, minHeight: 300 },
|
|
@@ -5946,6 +6026,10 @@ var header = {
|
|
|
5946
6026
|
padding: "12px 20px",
|
|
5947
6027
|
borderBottom: "1px solid var(--dsw-alias-border-l2)"
|
|
5948
6028
|
};
|
|
6029
|
+
var headerBrand = { display: "flex", alignItems: "center", gap: 10, minWidth: 0 };
|
|
6030
|
+
var headerText = { display: "flex", flexDirection: "column", gap: 1, minWidth: 0 };
|
|
6031
|
+
var headerTitle = { fontSize: 15, fontWeight: 600, color: "var(--dsw-alias-label-primary)" };
|
|
6032
|
+
var headerSub = { fontSize: 12, color: "var(--dsw-alias-label-tertiary)", margin: 0 };
|
|
5949
6033
|
var tabbar = {
|
|
5950
6034
|
display: "flex",
|
|
5951
6035
|
alignItems: "center",
|
|
@@ -5999,12 +6083,33 @@ function Workbench({ useStore, actions, useWorkspaces, remote }) {
|
|
|
5999
6083
|
(0, import_react7.createElement)(
|
|
6000
6084
|
"div",
|
|
6001
6085
|
{ style: header },
|
|
6002
|
-
(0, import_react7.createElement)(
|
|
6086
|
+
(0, import_react7.createElement)(
|
|
6087
|
+
"div",
|
|
6088
|
+
{ style: headerBrand },
|
|
6089
|
+
(0, import_react7.createElement)(import_dsh_client_ui_primitives7.IconSettingsOutline16, { size: 18 }),
|
|
6090
|
+
(0, import_react7.createElement)(
|
|
6091
|
+
"div",
|
|
6092
|
+
{ style: headerText },
|
|
6093
|
+
(0, import_react7.createElement)("span", { style: headerTitle }, "\u5DE5\u7A0B\u6D41\u7A0B"),
|
|
6094
|
+
(0, import_react7.createElement)("p", { style: headerSub }, "\u5DE5\u7A0B\u5316\u4EA4\u4ED8\u5DE5\u4F5C\u53F0 \xB7 \u9700\u6C42\u8BC4\u5BA1 \u2192 \u8BBE\u8BA1 \u2192 \u5F00\u53D1 \u2192 \u4EA4\u4ED8 \u2192 \u4EE3\u7801\u5BA1\u6838")
|
|
6095
|
+
)
|
|
6096
|
+
),
|
|
6003
6097
|
(0, import_react7.createElement)(
|
|
6004
6098
|
"button",
|
|
6005
|
-
{
|
|
6006
|
-
|
|
6007
|
-
|
|
6099
|
+
{
|
|
6100
|
+
type: "button",
|
|
6101
|
+
title: "\u5173\u95ED",
|
|
6102
|
+
onClick: () => {
|
|
6103
|
+
actions.close();
|
|
6104
|
+
},
|
|
6105
|
+
style: closeStyle,
|
|
6106
|
+
onMouseEnter: (event) => {
|
|
6107
|
+
event.currentTarget.style.background = "var(--dsw-alias-bg-layer-2)";
|
|
6108
|
+
},
|
|
6109
|
+
onMouseLeave: (event) => {
|
|
6110
|
+
event.currentTarget.style.background = "transparent";
|
|
6111
|
+
}
|
|
6112
|
+
},
|
|
6008
6113
|
(0, import_react7.createElement)(import_dsh_client_ui_primitives7.IconCloseOutline16, { size: 16 })
|
|
6009
6114
|
)
|
|
6010
6115
|
),
|
|
@@ -6033,6 +6138,8 @@ function Workbench({ useStore, actions, useWorkspaces, remote }) {
|
|
|
6033
6138
|
// src/client/TriggerButton.tsx
|
|
6034
6139
|
var import_react8 = require("react");
|
|
6035
6140
|
var import_dsh_client_ui_primitives8 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
6141
|
+
var hover = "var(--dsw-alias-bg-layer-2)";
|
|
6142
|
+
var label = "var(--dsw-alias-label-primary)";
|
|
6036
6143
|
function TriggerButton({ wide, actions }) {
|
|
6037
6144
|
return (0, import_react8.createElement)(
|
|
6038
6145
|
"button",
|
|
@@ -6043,24 +6150,39 @@ function TriggerButton({ wide, actions }) {
|
|
|
6043
6150
|
onClick: () => {
|
|
6044
6151
|
actions.open();
|
|
6045
6152
|
},
|
|
6153
|
+
onMouseEnter: (event) => {
|
|
6154
|
+
event.currentTarget.style.background = hover;
|
|
6155
|
+
},
|
|
6156
|
+
onMouseLeave: (event) => {
|
|
6157
|
+
event.currentTarget.style.background = "transparent";
|
|
6158
|
+
},
|
|
6046
6159
|
style: {
|
|
6047
6160
|
display: "flex",
|
|
6048
6161
|
alignItems: "center",
|
|
6049
6162
|
justifyContent: wide ? "flex-start" : "center",
|
|
6050
6163
|
gap: wide ? 8 : 0,
|
|
6051
|
-
width: "100%",
|
|
6052
|
-
|
|
6164
|
+
width: wide ? "100%" : 36,
|
|
6165
|
+
minWidth: wide ? 0 : 36,
|
|
6166
|
+
height: wide ? 42 : 36,
|
|
6167
|
+
flex: wide ? "1 1 auto" : "none",
|
|
6053
6168
|
border: "none",
|
|
6054
6169
|
background: "transparent",
|
|
6055
6170
|
cursor: "pointer",
|
|
6056
|
-
color:
|
|
6057
|
-
borderRadius:
|
|
6058
|
-
padding: wide ? "0 10px" : 0,
|
|
6059
|
-
|
|
6171
|
+
color: label,
|
|
6172
|
+
borderRadius: wide ? 12 : "50%",
|
|
6173
|
+
padding: wide ? "0 10px 0 8px" : 0,
|
|
6174
|
+
// The native settings row bleeds 2px past its seat on both sides so its
|
|
6175
|
+
// hover chrome spans the full row; matching keeps both icon columns on
|
|
6176
|
+
// the same x.
|
|
6177
|
+
margin: wide ? "0 -2px" : "0 auto 2px",
|
|
6178
|
+
fontSize: wide ? 14 : void 0,
|
|
6179
|
+
lineHeight: wide ? "22px" : void 0,
|
|
6180
|
+
boxSizing: "border-box",
|
|
6181
|
+
transition: "background 120ms ease"
|
|
6060
6182
|
}
|
|
6061
6183
|
},
|
|
6062
6184
|
(0, import_react8.createElement)(import_dsh_client_ui_primitives8.IconSettingsOutline16, { size: wide ? 16 : 18 }),
|
|
6063
|
-
wide ? (0, import_react8.createElement)("span", { style: { fontSize:
|
|
6185
|
+
wide ? (0, import_react8.createElement)("span", { style: { fontSize: 14, lineHeight: "22px" } }, "\u5DE5\u7A0B\u6D41\u7A0B") : null
|
|
6064
6186
|
);
|
|
6065
6187
|
}
|
|
6066
6188
|
|