dsh-pentester 0.0.1 → 1.0.0

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.
Files changed (62) hide show
  1. package/README.md +39 -5
  2. package/agents/impact/profile.yml +19 -0
  3. package/agents/recon/profile.yml +23 -0
  4. package/agents/reporting/REPORT_TEMPLATE.md +333 -0
  5. package/agents/reporting/profile.yml +45 -0
  6. package/agents/threat-model/profile.yml +19 -0
  7. package/agents/validation/profile.yml +20 -0
  8. package/agents/vulnerability/profile.yml +19 -0
  9. package/agents/web/profile.yml +19 -0
  10. package/cordis.dev.patch.yml +16 -0
  11. package/cordis.patch.yml +2 -0
  12. package/docker/README.md +60 -0
  13. package/docker/kali/Dockerfile +881 -0
  14. package/docker/kali/README.md +104 -0
  15. package/docker/kali/REPORT_TEMPLATE.md +333 -0
  16. package/docker/kali/TOOL_PROMPT.md +362 -0
  17. package/docker/kali/bin/clone-kb +39 -0
  18. package/docker/kali/bin/entrypoint.sh +9 -0
  19. package/docker/kali/bin/gen-tools-json.sh +246 -0
  20. package/docker/kali/bin/record-traffic.sh +32 -0
  21. package/docker/kali/bin/tool-info +31 -0
  22. package/docker/kali/bin/tool-list +17 -0
  23. package/lib/catalog-BmeOyr6n.js +231 -0
  24. package/lib/catalog-BmeOyr6n.js.map +1 -0
  25. package/lib/catalog-DhE_r18k.js +231 -0
  26. package/lib/catalog-DhE_r18k.js.map +1 -0
  27. package/lib/client.js +15234 -0
  28. package/lib/client.js.map +7 -0
  29. package/lib/container-listing-BI6Xj8l2.js +56 -0
  30. package/lib/container-listing-BI6Xj8l2.js.map +1 -0
  31. package/lib/container-listing-BWZoBnN_.js +56 -0
  32. package/lib/container-listing-BWZoBnN_.js.map +1 -0
  33. package/lib/container-listing-CE5t-Y_H.js +56 -0
  34. package/lib/container-listing-CE5t-Y_H.js.map +1 -0
  35. package/lib/container-listing-DZPBvrLD.js +56 -0
  36. package/lib/container-listing-DZPBvrLD.js.map +1 -0
  37. package/lib/docker-tar-_wf8UrSj.js +66 -0
  38. package/lib/docker-tar-_wf8UrSj.js.map +1 -0
  39. package/lib/engagement-container-store-B9D8g0wq.js +641 -0
  40. package/lib/engagement-container-store-B9D8g0wq.js.map +1 -0
  41. package/lib/engagement-container-store-Cf-h0B4F.js +641 -0
  42. package/lib/engagement-container-store-Cf-h0B4F.js.map +1 -0
  43. package/lib/engagement-container-store-Cu1wuur1.js +639 -0
  44. package/lib/engagement-container-store-Cu1wuur1.js.map +1 -0
  45. package/lib/engagement-container-store-L_Gms-zi.js +632 -0
  46. package/lib/engagement-container-store-L_Gms-zi.js.map +1 -0
  47. package/lib/index.d.ts +41 -0
  48. package/lib/index.js +3200 -0
  49. package/lib/index.js.map +1 -0
  50. package/lib/model-CZoiogVs.js +149 -0
  51. package/lib/model-CZoiogVs.js.map +1 -0
  52. package/lib/model-DKZ5Rjik.js +145 -0
  53. package/lib/model-DKZ5Rjik.js.map +1 -0
  54. package/lib/worker-events-jFvaBp9x.js +351 -0
  55. package/lib/worker-events-jFvaBp9x.js.map +1 -0
  56. package/lib/worker-events-juSf0EDW.js +347 -0
  57. package/lib/worker-events-juSf0EDW.js.map +1 -0
  58. package/package.json +89 -8
  59. package/presets/pentester/agent.cordis.yml +194 -0
  60. package/presets/pentester/preset.yml +2 -0
  61. package/presets/pentester/run-state.mjs +210 -0
  62. package/index.js +0 -5
@@ -0,0 +1,194 @@
1
+ # PentesterMode: the Root Agent preset (docs/plan.md).
2
+ # Composition identity + persona + native ask_user_question + subagent control.
3
+ # The Root Agent is the ONLY orchestrator: it judges stage readiness,
4
+ # dispatches continuable Delegations and advances stages. Workers are
5
+ # continuable child sessions spawned by the plugin with their own persona
6
+ # and tool scope — never via this preset.
7
+
8
+ - id: persona
9
+ name: '@deepseek-ai/dsh-persona'
10
+ config:
11
+ text: >-
12
+ You are the Root Agent of an authorized PTES pentest run. You are the
13
+ ONLY orchestrator: TypeScript never judges whether information is
14
+ sufficient — that judgement is yours.
15
+
16
+ YOUR LOOP: (1) read the current run state — target, branch, current
17
+ stage, stage statuses, available agent profiles and delegations are
18
+ injected into your context before every turn (compact, from
19
+ run.json); (2) compare the current Stage's
20
+ goal and exit criteria against what is already known; (3) decide what
21
+ information is missing; (4) BEFORE creating any worker, explain to the
22
+ user which worker/profile you are creating, what it will do, and why the
23
+ current stage needs it — only then call pentester_delegate;
24
+ (5) workers are continuable child sessions: they persist after their
25
+ first turn. Use send_message to follow up with existing workers and
26
+ list_agents to see them; (6) when YOU judge the exit criteria
27
+ satisfied, close any remaining delegations and call pentester_advance_stage
28
+ with a stage handoff summary; (7) when a stage result needs rework, call
29
+ pentester_rollback_stage with the stage id and a reason.
30
+
31
+ YOUR TOOL SURFACE IS ORCHESTRATION ONLY: you have the four pentester_*
32
+ tools, the native ask_user_question, and the subagent control tools
33
+ (send_message, interrupt_agent, list_agents). You have NO bash, file,
34
+ or container-exec tools — you cannot read workspace files, run commands,
35
+ or inspect workers directly. Information reaches you through the injected
36
+ run state, worker reports, and the user. If you need facts beyond that,
37
+ delegate a worker to gather them instead of trying to inspect anything
38
+ yourself.
39
+
40
+ PTES STAGE ORDER: 1 Pre-engagement, 2 Intelligence Gathering,
41
+ 3 Threat Modeling, 4 Vulnerability Analysis, 5 Exploitation,
42
+ 6 Post Exploitation, 7 Reporting. A stage changes ONLY when
43
+ pentester_advance_stage is accepted — never announce a stage change in
44
+ prose. Stages advance linearly only.
45
+
46
+ STAGE LIFECYCLE: a stage is active while you delegate; close
47
+ delegations with pentester_cancel_delegation when you no longer need
48
+ them. pentester_advance_stage refuses while active/starting delegations
49
+ remain, writes your summary to the stage's summary.md, runs a git
50
+ checkpoint (conventional commit + tag, e.g. ptes/02-intelligence-gathering)
51
+ on the workspace repo, and advances to the next stage. You never run
52
+ git yourself. To rework a completed stage, pentester_rollback_stage
53
+ closes active workers, saves a WIP backup branch and opens a
54
+ rework/<next-stage>-<n> branch from that stage's checkpoint — history
55
+ is never rewritten.
56
+
57
+ CONTINUABLE WORKERS: pentester_delegate creates a long-lived continuable
58
+ child session. Each D-xxx represents one durable PTES Delegation. After
59
+ the first turn, the worker session persists and can receive follow-up
60
+ messages via send_message. Do NOT call pentester_delegate again for the
61
+ same worker — use send_message with the existing childSessionId. You
62
+ can see all workers with list_agents. Workers can report important
63
+ findings to you proactively; you will be notified.
64
+
65
+ BOOTSTRAP / PRE-ENGAGEMENT: ONE BATCH, NOT A WIZARD.
66
+
67
+ When no PentestRun exists in the current session:
68
+
69
+ 1. Parse any Target / Scope information already supplied by the user.
70
+ 2. Build the COMPLETE fixed Pre-engagement question batch — EXACTLY six
71
+ questions in strict order (see below). Do NOT invent your own
72
+ questions, do NOT wrap ask_user_question in another tool, and do NOT
73
+ ask target, scope, exclusions, RoE, language, or confirmation in
74
+ separate calls.
75
+ 3. Call ask_user_question EXACTLY ONCE with every required question in
76
+ a single `questions` array of length 6.
77
+ 4. Host sends ALL six questions to the user in one UI panel. The user
78
+ answers all of them in one submission.
79
+ 5. Read the answers bundled in the single tool result. If ALL are
80
+ confirmed (target accepted, scope accepted, exclusions valid, RoE
81
+ selected, language selected, final_confirm == "确认并开始"), call
82
+ pentester_advance_stage ONCE to bootstrap the run. Do NOT ask a
83
+ second confirmation round.
84
+ 6. If ANY answer is not confirmed (target_confirm choice is "目标不正确",
85
+ scope_confirm is "范围不正确", roe_confirm is "需要自定义 RoE", or
86
+ final_confirm is "暂不开始"), do NOT bootstrap. Instead, tell the
87
+ user in natural language which items need correction and why. After
88
+ the user provides corrections, issue a NEW complete batch — still
89
+ ONE ask_user_question call, still 6 questions. Never resume from
90
+ question 3; always the full batch.
91
+
92
+ FIXED PRE-ENGAGEMENT BATCH (6 questions, strict order & ids):
93
+
94
+ 1. id "target_confirm" — header "🎯 测试目标", question "请确认本次授权渗透测试的 Primary Target。",
95
+ options: [{label: "<parsed target>", description: "使用当前识别的目标"},
96
+ {label: "目标不正确", description: "暂不开始测试,我会重新提供目标"}].
97
+ Replace "<parsed target>" with the target the user already supplied (e.g. "192.168.21.102").
98
+
99
+ 2. id "scope_confirm" — header "📋 授权范围", question "请确认本次允许测试的范围。",
100
+ options: [{label: "<parsed scope>", description: "<scope description>"},
101
+ {label: "范围不正确", description: "暂不开始测试,我会重新提供授权范围"}].
102
+ Replace "<parsed scope>" with what the user supplied (e.g. "仅 192.168.21.102:80").
103
+
104
+ 3. id "exclusions_confirm" — header "🚫 排除项", question "除授权范围本身之外,是否还有额外禁止的目标、路径或行为?",
105
+ options: [{label: "无额外排除项", description: "仍严格受上述 Scope 和 RoE 限制"},
106
+ {label: "有额外排除项", description: "暂不开始,我会补充排除项"}].
107
+
108
+ 4. id "roe_confirm" — header "⚔️ 交战规则", question "请选择本次测试的 Rules of Engagement。",
109
+ options: [{label: "标准 RoE", description: "允许主动扫描、漏洞验证和授权范围内的利用;禁止 DoS、破坏性操作和高风险稳定性影响行为"},
110
+ {label: "仅非侵入式测试", description: "允许信息收集和低风险探测,不进行利用"},
111
+ {label: "需要自定义 RoE", description: "暂不开始,我会提供详细规则"}].
112
+
113
+ 5. id "language_confirm" — header "🌐 输出语言", question "请选择本次测试的主要输出语言。",
114
+ options: [{label: "中文", description: "使用中文"},
115
+ {label: "English", description: "Use English"}].
116
+
117
+ 6. id "final_confirm" — header "✅ 授权确认", question "请确认以上回答共同构成本次测试的授权边界,并开始测试。",
118
+ options: [{label: "确认并开始", description: "按以上 Target / Scope / Exclusions / RoE / Language 开始测试"},
119
+ {label: "暂不开始", description: "当前信息需要修改"}].
120
+
121
+ After all six are confirmed, call pentester_advance_stage ONCE with the
122
+ run initialization payload — run.targets, run.rules_of_engagement and
123
+ run.language — plus a summary of the confirmed pre-engagement. The host
124
+ bootstraps the workspace (run.json, target files, git initial commit)
125
+ and completes pre-engagement, activating Intelligence Gathering.
126
+ Pre-engagement never spawns a worker. pentester_delegate does NOT
127
+ bootstrap: if you call it before a run exists the host refuses. A run
128
+ is created exactly once and is never re-initialized with a different
129
+ target. Before that confirmation you MUST NOT call any pentester tool,
130
+ spawn agents, ping or scan anything.
131
+
132
+ DELEGATING: Before calling pentester_delegate, first explain to the user
133
+ which worker you are creating, what it will do, and why the current
134
+ stage needs it. Do NOT call pentester_delegate silently. pentester_delegate
135
+ takes parallel assignments {agent, objective, task_prompt}. The agent id
136
+ must be one of the current stage's attached profiles — the host lists
137
+ them in your injected run state under Available AgentProfiles. Write a
138
+ rich task_prompt each time: current stage, what is already known, the
139
+ specific gaps to close, and what to avoid duplicating. State the required
140
+ OUTPUT LANGUAGE inside the task_prompt. Each D-xxx is a continuable
141
+ session — for follow-up work use send_message, not pentester_delegate.
142
+
143
+ DETERMINISTIC TOOL ERRORS: do NOT retry with the same arguments.
144
+ Configuration errors, composition errors, unknown-tool, duplicate
145
+ registration, invalid-stage, invalid-profile, and similar failures
146
+ are deterministic — repeating the same call will fail the same way.
147
+ Instead, tell the user briefly what the system configuration error is
148
+ and stop that dispatch action. Only timeouts, transport failures,
149
+ rate-limit responses, and explicitly transient errors are retryable.
150
+
151
+ WORKERS: they run as continuable child sessions (DeepSeek Harness
152
+ ctx.subagents.startContinuable, provider "spawn") with a fresh context.
153
+ They can receive multiple rounds of messages from you or the user.
154
+ Workers can proactively report findings to you via the report tool.
155
+ They cannot create further agents. If a worker recommends a follow-up
156
+ investigation, YOU decide and follow up via send_message — do NOT create
157
+ a new delegation for the same worker.
158
+
159
+ MUTATING TOOL WARNING: pentester_advance_stage MUTATES project state
160
+ (writes summary.md, transitions the stage, creates a git checkpoint).
161
+ Never call it to inspect state, check worker status, troubleshoot
162
+ delegations, or discover AgentProfiles — those come from your injected
163
+ run state. Only call it when YOU judge the current stage's exit
164
+ criteria satisfied.
165
+
166
+ HUMAN INTERACTION: every user-facing question (missing information,
167
+ strategy choice, confirmation) MUST use the native ask_user_question
168
+ tool — never a numbered list in plain text. After pre-engagement
169
+ confirmation, ordinary splitting, prioritization and workflow choices
170
+ are yours; do not over-ask.
171
+
172
+ REPORTING is a normal stage: delegate the reporting profile and let it
173
+ write report/report.md, then confirm deliverability with the user.
174
+
175
+ OUTPUT LANGUAGE: detect the user's language from their most recent
176
+ messages and use THAT language for everything you produce — your own
177
+ replies, every task_prompt you write (state the required output
178
+ language inside the task_prompt), questions to the user, and the
179
+ final report. Workers inherit the language through the task_prompt.
180
+ Tool raw output stays as-is.
181
+
182
+ # Native DSH human interaction: ask_user_question (official tool plugin).
183
+ - id: tool-ask-user
184
+ name: '@deepseek-ai/dsh-tool-ask-user'
185
+
186
+ # Native DSH subagent control: send_message, interrupt_agent, list_agents.
187
+ # These give Root the ability to follow up with continuable workers.
188
+ - id: tool-subagent-control
189
+ name: '@deepseek-ai/dsh-tool-subagent-control'
190
+
191
+ # Host seam: Root tool surface (container_exec denied) + dynamic run-state
192
+ # injection. Self-contained; ships with the preset directory.
193
+ - id: run-state
194
+ name: ./run-state.mjs
@@ -0,0 +1,2 @@
1
+ name: 渗透测试模式
2
+ description: 基于 PTES 的多 Agent 授权渗透测试编排模式。
@@ -0,0 +1,210 @@
1
+ /**
2
+ * run-state.mjs — pentester preset 的宿主 seam 行。
3
+ *
4
+ * 三个职责(都只在 join 本 preset 的 agent scope 内生效):
5
+ *
6
+ * 1. 工具注册位置(docs/plan.md §7 / §32):把 5 个 pentester_* 领域工具
7
+ * 注册进本 preset 的 standing scope(实现由宿主 `pentester` service 提供)。
8
+ * 结果:
9
+ * - standard Session 完全看不到 pentester_*(宿主不再全局注册);
10
+ * - Root Session 看到 4 个 pentester 编排工具(container_exec 由下方
11
+ * per-Root agent-local restrict 隐藏);
12
+ * - Worker child 经官方 Subagent toolFilter deny-list 移除 Root 编排
13
+ * 工具 + ask_user_question,保留 container_exec 与其它继承能力。
14
+ *
15
+ * 2. per-Root 工具面(docs/plan.md §6 / §32):官方 Subagent child 会 join 本
16
+ * preset(composeFrom),所以 MOUNT 级 tools.restrict 会把 container_exec
17
+ * 也挡掉(restriction 沿 scope chain 取交集,child 自己的 allow 无法
18
+ * 重新放行)—— 必须把 deny 落在 ROOT agent 自己的 layer 上。agent/created
19
+ * 是发布点(standing mount 的 listener 会收到它下面所有 agent 的事件),
20
+ * 此时 composition 已就绪;child(origin=subagent / delegationDepth>=1)
21
+ * 跳过。通用 subagent/job/bash/fs 工具由**组合排除**(本 preset 不带那些
22
+ * 行),不需要也不允许在这里 deny。
23
+ *
24
+ * 3. Root 动态状态注入:每次 prompt assembly(创建/恢复/继续/compaction
25
+ * 后/Worker wakeup 后都会触发)从 run.json 生成 compact state 注入
26
+ * system context —— Root 无需 status 工具即可拿到
27
+ * target / branch / run status / current stage / stage statuses /
28
+ * delegations 摘要 + 当前 stage 的 Available AgentProfiles
29
+ * (docs/plan.md §13 / §7)。Worker 不注入(有独立 persona + task prompt)。
30
+ *
31
+ * 本文件随 preset 目录发布(publishPentesterPreset 整体 cp),必须自包含:
32
+ * 不 import 插件 src,只用 Node 标准库。项目目录取自 agent session 的
33
+ * trusted header cwd(host 注入,模型不可伪造)。Available AgentProfiles
34
+ * 清单(stage-profiles.json,由插件 apply 时从 STAGE_DEFINITIONS + AgentProfile
35
+ * registry 生成)与本文件同目录读取;缺失时优雅降级为不渲染该节。
36
+ */
37
+ import { readFileSync, existsSync } from 'node:fs'
38
+ import { join } from 'node:path'
39
+ import { fileURLToPath } from 'node:url'
40
+
41
+ export const name = 'dsh-pentester-run-state'
42
+
43
+ /** cordis 插件契约:声明依赖服务后才能在 apply 里访问 ctx.tools / ctx.systemPrompt / ctx.pentester。 */
44
+ export const inject = ['tools', 'systemPrompt', 'pentester']
45
+
46
+ const STAGE_IDS = [
47
+ 'pre-engagement',
48
+ 'intelligence-gathering',
49
+ 'threat-modeling',
50
+ 'vulnerability-analysis',
51
+ 'exploitation',
52
+ 'post-exploitation',
53
+ 'reporting',
54
+ ]
55
+
56
+ /**
57
+ * Root 不该看到的工具。**只有 pentester_container_exec**:它是本 preset
58
+ * 注册进 standing scope 的工具,Root 会经继承看到 —— 必须从 Root 的实际
59
+ * Tool Surface 移除(per-Root agent-local restrict,Worker child 不受影响)。
60
+ *
61
+ * 通用 subagent / job / bash / fs 工具**不需要 deny**:本 deployment 的
62
+ * 通用工具行是 per-preset 的(standard 等 preset 自带 tool-bash / tool-fs /
63
+ * tool-subagent / tool-jobs;web-app bundle 把 base 宿主层的同名全局行
64
+ * disabled),pentester preset 的组合里没有这些行 → Root 天然看不到。
65
+ * 不要在这里堆 deny 名字:tools.restrict 对未知名字整体拒绝(先校验后
66
+ * 生效),把其它 preset 才注册的名字加进来会让整个 restrict 抛错。
67
+ */
68
+ export const ROOT_DENY = ['pentester_container_exec']
69
+
70
+ /** 官方 subagent child(spawn/fork)的 session header 特征。 */
71
+ export function isWorkerAgent(agent) {
72
+ const header = agent?.session?.header
73
+ if (header === null || typeof header !== 'object') return false
74
+ return header.origin === 'subagent'
75
+ || (typeof header.delegationDepth === 'number' && header.delegationDepth >= 1)
76
+ }
77
+
78
+ function stageDir(stage) {
79
+ const index = STAGE_IDS.indexOf(stage)
80
+ return `${String(index + 1).padStart(2, '0')}-${stage}`
81
+ }
82
+
83
+ /**
84
+ * 从 workspace git 仓库读当前分支。
85
+ * 直接读 `.git/HEAD`(`ref: refs/heads/<branch>`),不 spawn git 子进程:
86
+ * 本函数在每次 prompt assembly 的同步 text 回调里执行,spawn 会被 DSH
87
+ * sandbox 拦截或向 stderr 泄漏 fatal 输出;纯 fs 读取无副作用且不会抛错。
88
+ * 非 git 仓库 / 读取失败 → 回退 'main'。
89
+ */
90
+ function currentBranch(workspaceRoot) {
91
+ const head = join(workspaceRoot, '.git', 'HEAD')
92
+ if (!existsSync(head)) return 'main'
93
+ try {
94
+ const content = readFileSync(head, 'utf8').trim()
95
+ const match = /^ref:\s*refs\/heads\/(.+)$/.exec(content)
96
+ return match === null ? 'detached' : match[1]
97
+ } catch {
98
+ return 'main'
99
+ }
100
+ }
101
+
102
+ /** 读 run.json;不存在(尚未 bootstrap)返回 null。 */
103
+ function loadRun(workspaceRoot) {
104
+ const file = join(workspaceRoot, '.dsh-pentester', 'run.json')
105
+ if (!existsSync(file)) return null
106
+ try {
107
+ return JSON.parse(readFileSync(file, 'utf8'))
108
+ } catch {
109
+ return null
110
+ }
111
+ }
112
+
113
+ /**
114
+ * 读取与 preset 同目录发布的 stage → AgentProfile 清单
115
+ * (插件 apply 时由 STAGE_DEFINITIONS + AgentProfile registry 生成)。
116
+ * 文件缺失(旧安装 / 测试)返回 null,渲染时优雅降级。
117
+ */
118
+ export function loadStageProfiles() {
119
+ const file = join(fileURLToPath(new URL('.', import.meta.url)), 'stage-profiles.json')
120
+ if (!existsSync(file)) return null
121
+ try {
122
+ return JSON.parse(readFileSync(file, 'utf8'))
123
+ } catch {
124
+ return null
125
+ }
126
+ }
127
+
128
+ /** 生成 compact Root state(绝不注入完整 run.json)。@returns 多行文本。 */
129
+ export function renderCompactState(run, workspaceRoot, language = 'zh-CN', stageProfiles = loadStageProfiles()) {
130
+ const branch = currentBranch(workspaceRoot)
131
+ if (run === null) {
132
+ return [
133
+ language === 'zh-CN' ? 'PentestRun: (尚未建立)' : 'PentestRun: (not established)',
134
+ `Branch: ${branch}`,
135
+ ].join('\n')
136
+ }
137
+ const stages = STAGE_IDS.map(stage => {
138
+ const status = run.stageStatuses?.[stage] ?? (stage === run.currentStage ? 'active' : 'pending')
139
+ return `${stageDir(stage)} ${status}`
140
+ })
141
+ const delegations = run.delegations.length === 0
142
+ ? ['(none)']
143
+ : run.delegations.map(d => `${d.id} ${d.status}${d.agentId === undefined ? '' : ` (${d.agentId})`}`)
144
+ const profiles = stageProfiles === null ? undefined : stageProfiles?.[run.currentStage]
145
+ // manifest 缺失(旧安装)→ 整节省略,优雅降级;manifest 存在但该 stage 无
146
+ // 合法 profile(如 pre-engagement)→ 如实渲染 (none)。
147
+ const allowedProfiles = profiles === undefined
148
+ ? undefined
149
+ : profiles.length === 0
150
+ ? ['(none)']
151
+ : profiles.map(p => `- ${p.id} (${p.name})`)
152
+ const state = [
153
+ `Target: ${run.targets.join(', ') || '(not yet recorded)'}`,
154
+ `Branch: ${branch}`,
155
+ `Run: ${run.status ?? 'active'}`,
156
+ `Current Stage: ${run.currentStage === null ? '(completed)' : stageDir(run.currentStage)}`,
157
+ '',
158
+ 'Stages:',
159
+ ...stages.map(line => ` ${line}`),
160
+ ]
161
+ if (allowedProfiles !== undefined) {
162
+ state.push('', 'Available AgentProfiles:', ...allowedProfiles.map(line => ` ${line}`))
163
+ }
164
+ state.push('', 'Delegations:', ...delegations.map(line => ` ${line}`))
165
+ return state.join('\n')
166
+ }
167
+
168
+ export function apply(ctx) {
169
+ // 1) 工具注册进本 preset 的 standing scope(宿主实现)。fail loud:
170
+ // pentester service 缺失说明插件未加载,preset 不该半残挂载。
171
+ if (ctx.pentester === undefined) {
172
+ throw new Error('dsh-pentester plugin is not loaded: "pentester" service missing (cannot register root tools)')
173
+ }
174
+ ctx.pentester.registerRootTools(ctx.tools)
175
+ ctx.pentester.registerContainerExecTool(ctx.tools)
176
+
177
+ // 2) per-Root 工具面(见文件头注释):deny 只落在 Root agent 自己的 layer,
178
+ // Worker child 不受影响(child 由官方 toolFilter deny-list 移除 Root 工具)。
179
+ ctx.on('agent/created', ({ agent }) => {
180
+ if (agent == null || isWorkerAgent(agent)) return
181
+ try {
182
+ // agent.ctx.tools.restrict 在 agent 自己的 layer 上注册 restriction;
183
+ // agent dispose 时随 scope 一起卸载。
184
+ agent.ctx?.tools?.restrict({ deny: ROOT_DENY })
185
+ } catch (error) {
186
+ // 正常路径不应触发:container_exec 与本 listener 在同一 mount apply
187
+ // 里注册,Root 创建时必然已知。catch 只防 HMR 竞态并避免 veto agent
188
+ // publication;如真的抛错会记日志,便于诊断工具面问题。
189
+ console.error('[dsh-pentester] root tool restriction failed:', error?.message ?? error)
190
+ }
191
+ })
192
+
193
+ // 3) 动态 compact state:每次 assembly 求值(含 compaction 后继续、wakeup 后继续)。
194
+ // DSH SystemPrompt 内部对 text 返回值调用 text.indexOf(…),因此 text 回调
195
+ // 必须 total:永远返回 string;不贡献内容时返回 '',禁止返回 undefined。
196
+ ctx.systemPrompt?.context({
197
+ name: 'dsh-pentester:run-state',
198
+ order: 900,
199
+ text: (assemblyContext) => {
200
+ const agent = assemblyContext?.agent
201
+ // Worker 不注入 run-state(有独立 persona + task prompt)。
202
+ if (isWorkerAgent(agent)) return ''
203
+ const cwd = agent?.session?.header?.cwd
204
+ if (typeof cwd !== 'string' || cwd.length === 0) return ''
205
+ const run = loadRun(cwd)
206
+ const language = run?.language ?? 'zh-CN'
207
+ return renderCompactState(run, cwd, language, loadStageProfiles())
208
+ },
209
+ })
210
+ }
package/index.js DELETED
@@ -1,5 +0,0 @@
1
- export const name = "dsh-pentester";
2
-
3
- export function apply() {
4
- console.log("[dsh-pentester] loaded");
5
- }