agent-bypass 2.20.0 → 2.20.1

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.
@@ -153,367 +153,5 @@ class JsripSameSiteTests(unittest.TestCase):
153
153
  self.assertFalse(self.js.same_site("https://example.com", "https://wwwexample.com/a.js"))
154
154
 
155
155
 
156
- class DshInstallerTests(unittest.TestCase):
157
- """DeepSeek Harness:技能安装 + Ck 模式预设注入(临时 HOME,离线)。"""
158
-
159
- DSH_ENV = ("DSH_HOME", "DSH_PROFILE", "DSH_AGENTS_HOME", "DSH_APP_PATH", "DSH_ASAR_PATH")
160
-
161
- def _node(self, *args, home):
162
- env = dict(os.environ)
163
- env["HOME"] = home
164
- for k in self.DSH_ENV:
165
- env.pop(k, None)
166
- return _tmp_run(["node", str(ROOT / "bin" / "install.js"), *args], env=env)
167
-
168
- def _seed(self, home, patch_text="[]\n"):
169
- prof = Path(home) / ".dsh" / "profiles" / "desktop"
170
- prof.mkdir(parents=True, exist_ok=True)
171
- f = prof / "cordis.patch.yml"
172
- f.write_text(patch_text, encoding="utf-8")
173
- return f
174
-
175
- def test_install_idempotent_and_persona_dollar_preserved(self):
176
- with tempfile.TemporaryDirectory() as home:
177
- patch = self._seed(home)
178
- r1 = self._node("--dsh", home=home)
179
- self.assertEqual(r1.returncode, 0, r1.stdout + r1.stderr)
180
- skill = Path(home) / ".agents" / "skills" / "agent-bypass" / "SKILL.md"
181
- self.assertTrue(skill.exists(), "DSH 技能应安装到 ~/.agents/skills")
182
- text = patch.read_text(encoding="utf-8")
183
- self.assertEqual(text.count("agent-bypass:dsh-preset-begin"), 1)
184
- self.assertEqual(text.count("id: preset-ck"), 1)
185
- self.assertIn("Ck 万能执行员", text)
186
- # $$ 不能被 String.replace 的替换串语义吃掉(PHP 变量变量)
187
- self.assertIn("parse_str / $$", text)
188
- # 幂等:重复安装不重复追加
189
- r2 = self._node("--dsh", home=home)
190
- self.assertEqual(r2.returncode, 0, r2.stdout + r2.stderr)
191
- text2 = patch.read_text(encoding="utf-8")
192
- self.assertEqual(text2.count("agent-bypass:dsh-preset-begin"), 1)
193
- self.assertEqual(text2.count("id: preset-ck"), 1)
194
-
195
- def test_crlf_preserved_and_uninstall_restores(self):
196
- with tempfile.TemporaryDirectory() as home:
197
- patch = self._seed(home, '# note\r\n- id: web\r\n name: "@deepseek-ai/dsh-web"\r\n')
198
- self.assertEqual(self._node("--dsh", home=home).returncode, 0)
199
- data = patch.read_bytes()
200
- self.assertIn(b"agent-bypass:dsh-preset-begin", data)
201
- self.assertGreater(data.count(b"\r\n"), 100, "CRLF 文件应保持 CRLF")
202
- r = self._node("--dsh", "--uninstall", home=home)
203
- self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
204
- data2 = patch.read_bytes()
205
- self.assertNotIn(b"agent-bypass:dsh-preset-begin", data2)
206
- self.assertIn(b"# note", data2)
207
- self.assertIn(b"id: web", data2)
208
- self.assertFalse((Path(home) / ".agents" / "skills" / "agent-bypass").exists())
209
-
210
- def test_comment_only_patch_stays_valid_yaml(self):
211
- with tempfile.TemporaryDirectory() as home:
212
- patch = self._seed(home, "# user comment\n[]\n")
213
- self.assertEqual(self._node("--dsh", home=home).returncode, 0)
214
- self.assertEqual(self._node("--dsh", "--uninstall", home=home).returncode, 0)
215
- text = patch.read_text(encoding="utf-8")
216
- self.assertIn("# user comment", text)
217
- self.assertIn("[]", text, "只留注释时应补回空数组,保证 loader 可解析")
218
-
219
- def test_default_targets_include_pi_opencode_dsh(self):
220
- with tempfile.TemporaryDirectory() as home:
221
- (Path(home) / ".pi").mkdir()
222
- (Path(home) / ".config" / "opencode").mkdir(parents=True)
223
- self._seed(home)
224
- r = self._node(home=home)
225
- self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
226
- self.assertTrue((Path(home) / ".pi" / "agent" / "skills" / "agent-bypass" / "SKILL.md").exists())
227
- self.assertTrue((Path(home) / ".config" / "opencode" / "skills" / "agent-bypass" / "SKILL.md").exists())
228
- self.assertTrue((Path(home) / ".agents" / "skills" / "agent-bypass" / "SKILL.md").exists())
229
- patch_text = (Path(home) / ".dsh" / "profiles" / "desktop" / "cordis.patch.yml").read_text(encoding="utf-8")
230
- self.assertIn("id: preset-ck", patch_text)
231
- self.assertIn("dsh", r.stdout + r.stderr)
232
-
233
-
234
- class DoctorTests(unittest.TestCase):
235
- """--doctor 环境自检:只读、不写任何文件。"""
236
-
237
- def test_doctor_readonly(self):
238
- with tempfile.TemporaryDirectory() as home:
239
- env = dict(os.environ)
240
- env["HOME"] = home
241
- for k in ("DSH_HOME", "DSH_PROFILE", "DSH_AGENTS_HOME", "DSH_APP_PATH", "DSH_ASAR_PATH"):
242
- env.pop(k, None)
243
- r = _tmp_run(["node", str(ROOT / "bin" / "install.js"), "--doctor"], env=env)
244
- self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
245
- out = r.stdout + r.stderr
246
- self.assertIn("环境自检", out)
247
- self.assertIn("平台", out)
248
- self.assertIn("包管理器", out)
249
- # 只读:临时 HOME 不应产生任何文件
250
- self.assertEqual(list(Path(home).rglob("*")), [], "doctor 不应写文件")
251
-
252
-
253
- class NetDnsScanOriginTests(unittest.TestCase):
254
- """fake-IP/TUN:netdns 判定 + portscan 解析守卫 + origin_trace 解析(全部离线)。"""
255
-
256
- @classmethod
257
- def setUpClass(cls):
258
- sys.path.insert(0, str(SCRIPTS))
259
- import netdns
260
- import portscan
261
- import origin_trace
262
- cls.nd = netdns
263
- cls.ps = portscan
264
- cls.ot = origin_trace
265
-
266
- def test_fake_ip_ranges(self):
267
- for ip in ("198.18.0.5", "198.19.255.1", "240.1.2.3", "198.51.100.7", "203.0.113.9"):
268
- self.assertTrue(self.nd.is_fake_ip(ip), ip)
269
- for ip in ("1.1.1.1", "8.8.8.8", "192.168.1.1", "10.0.0.1"):
270
- self.assertFalse(self.nd.is_fake_ip(ip), ip)
271
-
272
- def test_parse_dns_json(self):
273
- sample = ('{"Answer":[{"type":1,"data":"93.184.216.34"},'
274
- '{"type":28,"data":"2606:2800::1"},{"type":1,"data":"198.18.0.1"}]}')
275
- self.assertEqual(self.nd._parse_dns_json(sample, "A"), ["93.184.216.34", "198.18.0.1"])
276
- self.assertEqual(self.nd._parse_dns_json(sample, "AAAA"), ["2606:2800::1"])
277
- self.assertEqual(self.nd._parse_dns_json("not-json", "A"), [])
278
-
279
- def test_portscan_split_host_fake_guard(self):
280
- self.assertEqual(self.ps.split_host("https://example.com:8443/x"), ("example.com", 8443))
281
- self.assertEqual(self.ps.split_host("1.2.3.4"), ("1.2.3.4", None))
282
- self.assertIsNone(self.ps.split_host("bad host")[0])
283
- self.assertTrue(self.ps.is_fake_ip("198.18.1.2"))
284
- ip, src, fake = self.ps.resolve_target("198.18.1.2", mode="auto")
285
- self.assertEqual((ip, src), ("198.18.1.2", "ip"))
286
- self.assertEqual(fake, [])
287
-
288
- def test_origin_trace_parsers(self):
289
- ct = '[{"name_value":"a.example.com\\n*.example.com\\nother.test"}]'
290
- self.assertEqual(self.ot.parse_crtsh(ct, "example.com"), ["a.example.com", "example.com"])
291
- self.assertEqual(self.ot.extract_title("<title> Hi There </title>"), "Hi There")
292
- self.assertEqual(self.ot.cdn_markers({"cf-ray": "x", "server": "nginx"}), ["cf-ray"])
293
- base = {"title": "X", "server": "nginx", "status": "HTTP/1.1 200 OK", "cdn": ["cf-ray"]}
294
- cand = {"title": "X", "server": "nginx", "status": "HTTP/1.1 200 OK", "cdn": [], "cert_domain_ok": True}
295
- score, reasons = self.ot.score_candidate(base, cand)
296
- self.assertGreaterEqual(score, 5)
297
- self.assertIn("title", reasons)
298
- self.assertLess(self.ot.score_candidate(base, {"cdn": ["cf-ray"]})[0], score)
299
-
300
- def test_doh_shell_syntax(self):
301
- sh = shutil.which("sh")
302
- if not sh:
303
- self.skipTest("no sh")
304
- r = _tmp_run([sh, "-n", str(SCRIPTS / "doh_resolve.sh")])
305
- self.assertEqual(r.returncode, 0, r.stderr)
306
-
307
-
308
- class SkillCompactTests(unittest.TestCase):
309
- """SKILL.md 体积/去重回归:防止再次膨胀。"""
310
-
311
- def test_skill_structure_and_size(self):
312
- skill = (ROOT / "SKILL.md").read_text(encoding="utf-8")
313
- self.assertEqual(skill.count("<!-- module:"), 35)
314
- self.assertNotIn("## 修正对照", skill, "重复的修正对照表已移除,勿再加回")
315
- self.assertLess(len(skill.encode("utf-8")), 132000, "SKILL.md 体积超限(保持精简,长内容放 references/)")
316
-
317
-
318
- class HangProtectionTests(unittest.TestCase):
319
- """防卡死:DNS 硬超时、扫描总时限、溯源预算/同名 CDN 扣分(全部离线)。"""
320
-
321
- @classmethod
322
- def setUpClass(cls):
323
- sys.path.insert(0, str(SCRIPTS))
324
- import netdns, portscan, origin_trace
325
- cls.nd = netdns
326
- cls.ps = portscan
327
- cls.ot = origin_trace
328
-
329
- def test_system_resolve_hard_timeout(self):
330
- import time as _t
331
- orig = self.nd._system_resolve_impl
332
- self.nd._system_resolve_impl = lambda name: (_t.sleep(3), ["1.2.3.4"])[1]
333
- t0 = _t.monotonic()
334
- try:
335
- ips = self.nd.system_resolve("example.com", timeout=0.2)
336
- finally:
337
- self.nd._system_resolve_impl = orig
338
- self.assertEqual(ips, [])
339
- self.assertLess(_t.monotonic() - t0, 1.5, "system_resolve 必须有硬超时,不能挂住")
340
-
341
- def test_portscan_total_deadline(self):
342
- import asyncio, time as _t
343
- found, timed_out = asyncio.run(
344
- self.ps.scan("127.0.0.1", [9, 10], conc=1, timeout=0.05,
345
- deadline=_t.monotonic() - 1))
346
- self.assertEqual(found, [])
347
- self.assertTrue(timed_out)
348
-
349
- def test_origin_same_cdn_penalty(self):
350
- base = {"title": "X", "server": "nginx", "status": "HTTP/1.1 200 OK", "cdn": ["cf-ray"]}
351
- cand_cdn = {"title": "X", "server": "nginx", "status": "HTTP/1.1 200 OK", "cdn": ["cf-ray"]}
352
- cand_origin = {"title": "X", "server": "nginx", "status": "HTTP/1.1 200 OK", "cdn": []}
353
- s_cdn, r_cdn = self.ot.score_candidate(base, cand_cdn)
354
- s_org, r_org = self.ot.score_candidate(base, cand_origin)
355
- self.assertIn("same-cdn", r_cdn)
356
- self.assertLess(s_cdn, s_org)
357
-
358
- def test_passive_ct_parsers(self):
359
- cs = '[{"dns_names":["www.example.com","*.example.com","x.test"]}]'
360
- self.assertEqual(self.ot.parse_certspotter(cs, "example.com"), ["www.example.com", "example.com"])
361
- otx = ('{"passive_dns":[{"hostname":"mail.example.com","address":"1.2.3.4"},'
362
- '{"hostname":"x.test","address":"9.9.9.9"},'
363
- '{"hostname":"cdn.example.com","address":"198.18.0.9"}]}')
364
- hosts, ips = self.ot.parse_otx(otx, "example.com")
365
- self.assertIn("mail.example.com", hosts)
366
- self.assertEqual(ips, ["1.2.3.4"], "被动 DNS 的 fake-IP 应被过滤")
367
- hosts, ips = self.ot.parse_hackertarget("vpn.example.com,5.6.7.8\nAPI count exceeded", "example.com")
368
- self.assertEqual(hosts, ["vpn.example.com"])
369
- self.assertEqual(ips, ["5.6.7.8"])
370
- self.assertEqual(self.ot.parse_anubis('["a.example.com","b.test"]', "example.com"), ["a.example.com"])
371
- self.assertEqual(self.ot.parse_rapiddns("<td>oa.example.com</td><td>x.test</td>", "example.com"),
372
- ["oa.example.com"])
373
- for src in ("crt.sh", "certspotter", "otx", "hackertarget", "anubis", "rapiddns"):
374
- self.assertIn(src, self.ot.CT_SOURCES)
375
-
376
- def test_ct_multi_source_fallback(self):
377
- def fake(name, domain, timeout):
378
- if name == "crt.sh":
379
- raise RuntimeError("502 Bad Gateway")
380
- return (["s1.example.com", "s2.example.com"], ["1.2.3.4"])
381
- orig = self.ot._ct_fetch
382
- self.ot._ct_fetch = fake
383
- try:
384
- hosts, ips, status = self.ot.fetch_passive_names(
385
- "example.com", sources=["crt.sh", "otx"], budget=5)
386
- finally:
387
- self.ot._ct_fetch = orig
388
- self.assertEqual(hosts, ["s1.example.com", "s2.example.com"])
389
- self.assertEqual(ips, ["1.2.3.4"])
390
- self.assertIn("失败", status["crt.sh"], "单源 502 应被记录并跳过")
391
-
392
- def test_origin_bad_flag_rejected(self):
393
- r = _tmp_run([sys.executable, str(SCRIPTS / "origin_trace.py"), "--bogus"])
394
- self.assertEqual(r.returncode, 2)
395
-
396
-
397
- class PivotQueryTests(unittest.TestCase):
398
- """搜索引警扩面:murmur3/favicon hash、7 引擎查询生成、CLI JSON(离线)。"""
399
-
400
- @classmethod
401
- def setUpClass(cls):
402
- sys.path.insert(0, str(SCRIPTS))
403
- import pivot_queries
404
- cls.pq = pivot_queries
405
-
406
- def test_murmur3_known_vectors(self):
407
- self.assertEqual(self.pq.murmur3_32(b""), 0)
408
- self.assertEqual(self.pq.murmur3_32(b"foo"), -156908512)
409
- self.assertEqual(self.pq.murmur3_32(b"hello"), 613153351)
410
-
411
- def test_favicon_hash_wiring(self):
412
- import base64
413
- data = b"\x89PNG\r\n\x1a\nfake-favicon-bytes"
414
- hb, hr = self.pq.favicon_hashes(data)
415
- self.assertEqual(hb, self.pq.murmur3_32(base64.b64encode(data)))
416
- self.assertEqual(hr, self.pq.murmur3_32(data))
417
-
418
- def test_build_queries_across_engines(self):
419
- info = {"domain": "t.com", "ip": "1.2.3.4", "cidr": "1.2.3.0/24", "cert": "t.com",
420
- "title": "Admin", "body": "uniq", "asn": "AS13335", "port": 8443, "icon_hash": -123}
421
- q = self.pq.build_queries(info)
422
- for eng in ("fofa", "shodan", "quake", "zoomeye", "hunter", "censys", "netlas"):
423
- self.assertIn(eng, q)
424
- self.assertIn('ip="1.2.3.4"', q["fofa"])
425
- self.assertIn('icon_hash="-123"', q["fofa"])
426
- self.assertIn("http.favicon.hash:-123", q["shodan"])
427
- self.assertIn("net:1.2.3.0/24", q["shodan"])
428
- self.assertIn('cert:"t.com"', q["quake"])
429
- self.assertIn("cidr:1.2.3.0/24", q["zoomeye"])
430
- self.assertIn('web.title="Admin"', q["hunter"])
431
-
432
- def test_cli_json_and_favicon_file(self):
433
- with tempfile.TemporaryDirectory() as d:
434
- fav = Path(d) / "favicon.ico"
435
- data = b"\x00\x01\x02fake"
436
- fav.write_bytes(data)
437
- r = _tmp_run([sys.executable, str(SCRIPTS / "pivot_queries.py"),
438
- "--domain", "t.com", "--ip", "1.2.3.4",
439
- "--favicon", str(fav), "--json"])
440
- self.assertEqual(r.returncode, 0, r.stderr)
441
- payload = json.loads(r.stdout)
442
- self.assertEqual(payload["input"]["icon_hash"], self.pq.favicon_hashes(data)[0])
443
- self.assertTrue(payload["queries"]["fofa"])
444
- self.assertTrue(payload["scan_templates"])
445
-
446
- def test_cli_rejects_empty_input(self):
447
- r = _tmp_run([sys.executable, str(SCRIPTS / "pivot_queries.py")])
448
- self.assertEqual(r.returncode, 2)
449
-
450
-
451
- class EnvChainTests(unittest.TestCase):
452
- """多路径攻击:.env 解析/分类/脱敏/链生成(离线)。"""
453
-
454
- SAMPLE = "\n".join([
455
- "APP_ENV=production",
456
- "DB_HOST=10.0.0.5",
457
- "DB_PORT=3306",
458
- "DB_USER=appuser",
459
- "DB_PASSWORD=SuperSecret1",
460
- "REDIS_HOST=10.0.0.6",
461
- "REDIS_PASSWORD=redispass",
462
- "AWS_ACCESS_KEY_ID=AKIAFAKE123",
463
- "AWS_SECRET_ACCESS_KEY=fakesecret",
464
- "JWT_SECRET=jwtsecret123",
465
- "SMTP_HOST=smtp.example.com",
466
- "SSH_USER=deploy",
467
- ]) + "\n"
468
-
469
- @classmethod
470
- def setUpClass(cls):
471
- sys.path.insert(0, str(SCRIPTS))
472
- import env_chain
473
- cls.ec = env_chain
474
-
475
- def test_parse_env_and_classify(self):
476
- env = self.ec.parse_env('export DB_HOST=1.2.3.4\nDB_USER="u"\n# comment\nBADLINE\n')
477
- self.assertEqual(env["DB_HOST"], "1.2.3.4")
478
- self.assertEqual(env["DB_USER"], "u")
479
- self.assertEqual(self.ec.classify("DB_PASSWORD"), "mysql")
480
- self.assertEqual(self.ec.classify("REDIS_URL"), "db_url")
481
- self.assertEqual(self.ec.classify("AWS_SECRET_ACCESS_KEY"), "aws")
482
- self.assertEqual(self.ec.classify("JWT_SECRET"), "secret")
483
- self.assertEqual(self.ec.classify("SMTP_PASSWORD"), "smtp")
484
- self.assertEqual(self.ec.classify("SSH_PRIVATE_KEY"), "ssh")
485
-
486
- def test_extract_and_chains_masking(self):
487
- env = self.ec.parse_env(self.SAMPLE)
488
- data = self.ec.extract(env)
489
- services = {s["service"] for s in data["services"]}
490
- self.assertIn("mysql", services)
491
- self.assertIn("redis", services)
492
- chains = self.ec.build_chains(data)
493
- text = json.dumps(chains, ensure_ascii=False)
494
- self.assertNotIn("SuperSecret1", text, "默认必须脱敏")
495
- self.assertNotIn("redispass", text)
496
- revealed = json.dumps(self.ec.build_chains(data, reveal=True), ensure_ascii=False)
497
- self.assertIn("SuperSecret1", revealed)
498
- self.assertIn(".env → MySQL", revealed)
499
-
500
- def test_cli_json_roundtrip(self):
501
- with tempfile.TemporaryDirectory() as d:
502
- f = Path(d) / ".env"
503
- f.write_text(self.SAMPLE, encoding="utf-8")
504
- r = _tmp_run([sys.executable, str(SCRIPTS / "env_chain.py"), str(f), "--json"])
505
- self.assertEqual(r.returncode, 0, r.stderr)
506
- payload = json.loads(r.stdout)
507
- self.assertIn("chains", payload)
508
- self.assertNotIn("SuperSecret1", r.stdout)
509
- r2 = _tmp_run([sys.executable, str(SCRIPTS / "env_chain.py"), str(f), "--json", "--reveal"])
510
- self.assertEqual(r2.returncode, 0, r2.stderr)
511
- self.assertIn("SuperSecret1", r2.stdout)
512
- empty = Path(d) / "empty.txt"
513
- empty.write_text("# nothing\n", encoding="utf-8")
514
- r3 = _tmp_run([sys.executable, str(SCRIPTS / "env_chain.py"), str(empty)])
515
- self.assertEqual(r3.returncode, 2)
516
-
517
-
518
156
  if __name__ == "__main__":
519
157
  unittest.main()
@@ -33,7 +33,7 @@ class InventoryTests(unittest.TestCase):
33
33
  skill = (ROOT / 'SKILL.md').read_text(encoding='utf-8')
34
34
  for item in ['agent-routing.md','tool-selection.md','verification-gates.md','framework-comparison-2026.md','tool_inventory.py']:
35
35
  self.assertIn(item, skill)
36
- self.assertEqual(skill.count("<!-- module:"), 35)
36
+ self.assertEqual(skill.count('<!-- module:'), 35)
37
37
 
38
38
 
39
39
  if __name__ == '__main__':
package/dsh/README.md DELETED
@@ -1,74 +0,0 @@
1
- # dsh/ — DeepSeek Harness 集成(Ck 模式)
2
-
3
- 把 agent-bypass 接进 [DeepSeek Harness](https://www.deepseek.com)(DSH)桌面端,两件事一起做:
4
-
5
- 1. **技能可读**:技能文件装到 DSH 的用户技能根 `~/.agents/skills/agent-bypass/`,DSH 的 `skill-filesystem` 会在启动时自动发现并加载(catalog 里出现 `agent-bypass`,命中触发词即可用)。
6
- 2. **Ck 模式**:把一个 `@deepseek-ai/dsh-agent-preset` 声明写进 DSH 的 profile patch,新会话里出现 **「Ck 万能执行员」** Agent 预设——人格来自 `persona.md`,工具/插件行与 DSH 官方 `standard` 预设**逐行一致**。
7
-
8
- ## 文件
9
-
10
- | 文件 | 作用 |
11
- |---|---|
12
- | `persona.md` | Ck 万能执行员人格正文(自包含,不引用外部技能/模块) |
13
- | `preset.template.yml` | 预设声明模板:官方 standard 预设的插件/工具行 + `__PERSONA__` 占位符 |
14
- | `build-template.js` | 从本机 DSH 的 `app.asar` 重新派生 `preset.template.yml`(DSH 升级后用) |
15
-
16
- ## 安装 / 卸载
17
-
18
- `npx agent-bypass`(默认目标已含 DSH)或显式;先自检 `npx agent-bypass --doctor` 可看 DSH 安装探测与 Ck 模式状态:
19
-
20
- ```bash
21
- npx agent-bypass --dsh # 只装 DeepSeek Harness
22
- npx agent-bypass --dsh --uninstall # 卸载(技能 + 预设标记块)
23
- ```
24
-
25
- 安装器做的事:
26
-
27
- - 技能 → `~/.agents/skills/agent-bypass/`(`DSH_AGENTS_HOME` 可改共享根;Windows 为 `%USERPROFILE%\.agents\skills`)。该目录是 Agent Skills 开放标准主副本,与 CC Switch / 其他 harness 共用;如果它是指向 Pi 真身的软链,安装器会穿透到真身更新、保持链接。
28
- - 预设 → `<DSH_HOME>/profiles/<profile>/cordis.patch.yml` 内写入标记块:
29
-
30
- ```yaml
31
- # >>> agent-bypass:dsh-preset-begin
32
- - insert:
33
- - id: preset-ck
34
- name: '@deepseek-ai/dsh-agent-preset'
35
- config: { id: ck, name: 'Ck 万能执行员', order: 0, plugins: [...] }
36
- # <<< agent-bypass:dsh-preset-end
37
- ```
38
-
39
- 幂等:重复安装只更新块内容,不动用户原有条目/注释;保留文件 CRLF 与 BOM;若检测到已有的 bundle 方式安装(`plugin_manager install_bundle`)或已存在 `preset-ck` 声明,会跳过注入并在输出里提示,避免行 id 冲突。
40
-
41
- ## 路径与自动识别(macOS / Windows / Linux)
42
-
43
- | 系统 | DSH 安装目录探测顺序 | app.asar |
44
- |---|---|---|
45
- | macOS | `/Applications/DeepSeek Harness.app`、`~/Applications/…`;找不到时 `mdfind com.deepseek.dsh` 兜底 | `<app>.app/Contents/Resources/app.asar` |
46
- | Windows | `%LOCALAPPDATA%\Programs\DeepSeek Harness`、`%LOCALAPPDATA%\DeepSeek Harness`、`%ProgramFiles%\…`、`%ProgramFiles(x86)%\…`;找不到时查注册表 Uninstall 项(InstallLocation / DisplayIcon) | `<安装目录>\resources\app.asar` |
47
- | Linux | `/opt/DeepSeek Harness`、`/opt/deepseek-harness`、`/usr/lib|share/deepseek-harness` | `<安装目录>/resources/app.asar` |
48
-
49
- 覆盖方式(任选):`DSH_APP_PATH=<安装目录>`、`DSH_ASAR_PATH=<app.asar>`、`node dsh/build-template.js --app <目录> / --asar <路径>`。
50
-
51
- DSH 自身数据目录:`DSH_HOME`(默认 `~/.dsh`)、profile 名 `DSH_PROFILE`(默认自动选 `desktop`,否则扫 `profiles/*/cordis.patch.yml`)。Windows 下同为 `%USERPROFILE%\.dsh`。
52
-
53
- `build-template.js` 用**纯 Node 读取 asar**(不依赖 Electron 二进制位置,跨平台一致;找不到安装目录时可显式指定)。
54
-
55
- ## 使用 Ck 模式
56
-
57
- 1. 新建会话(New Session)→ 「Agent 预设」控件 → **「Ck 万能执行员」**(`order: 0` 排最前)。
58
- 2. 想以后默认:设置 → 「Agent 预设」→ 自定义分组 → Ck 卡片 → **「设为新任务默认」**。
59
- 3. 已存在的会话保持其启动时预设,不能中途切换;预设列表没立即出现就重启一次 DSH。
60
-
61
- ## DSH 升级后
62
-
63
- 官方 `standard` 预设的插件行可能变化。在装了 DSH 的机器上重跑:
64
-
65
- ```bash
66
- node dsh/build-template.js # 自动定位 app.asar,重建 preset.template.yml
67
- npx agent-bypass --dsh # 重新注入(幂等)
68
- ```
69
-
70
- ## 已知边界
71
-
72
- - 预设不是安全沙箱(DSH 官方说明:YAML 与插件可执行 Host 代码);本目录只产出声明,不含第三方代码。
73
- - `persona.md` 会作为系统提示前缀随每次请求发送(约 1.4 万字符),嫌重可裁剪后重跑安装。
74
- - 不要把 `~/.dsh` 与 `~/.agents` 指向错误的用户目录;Windows 用 `%USERPROFILE%`,macOS/Linux 用 `$HOME`。