Skip to content
Open
2 changes: 1 addition & 1 deletion agent/toolcatalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func builtinToolsByAgent() map[string][]actool.CoreTool {
return map[string][]actool.CoreTool{
"mainagent": ts.MainAgentTools(),
"planner": ts.PlannerTools(),
"worker": ts.WorkerTools(),
"worker": ts.WorkerTools(), // 含 list_proxies(代理池只读,随开关生效)
// goals(目标拆解器)默认绑 set_goals:它靠这个工具把拆出的目标写进图。
// 与 mainagent 共用同一受管工具,web 端可改描述/schema、按 agent 勾选。
"goals": {ts.setGoals()},
Expand Down
1 change: 1 addition & 0 deletions agent/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func compactIntents(ns []*db.Node, parentsOf, yieldsOf map[int64][]int64) []map[
type ToolSet struct {
as *db.AssetStore // asset store (optional; nil = asset tools not available)
cs *db.CompanyStore // company store (optional)
ps *db.ProxyStore // proxy pool store (optional; nil = list_proxies unavailable)
ts *db.ExplorationStore
worker string
taskID int64 // PG tasks.id; 0 when unknown (tests / orchestrator cross-task reads)
Expand Down
67 changes: 66 additions & 1 deletion agent/tools_insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ func (t *ToolSet) SetAssetStore(as *db.AssetStore, cs *db.CompanyStore) {
t.cs = cs
}

// SetProxyStore wires the proxy pool store so the list_proxies tool is active.
func (t *ToolSet) SetProxyStore(ps *db.ProxyStore) { t.ps = ps }

// assetInputItem is one element of the insert_assets "assets" array.
type assetInputItem struct {
Type string `json:"type"` // root_domain|ip|subdomain|app|service|endpoint
Expand Down Expand Up @@ -160,7 +163,7 @@ func (t *ToolSet) insertAssets() actool.CoreTool {
// 覆盖度相关:该资产是否与当前测试任务有关。
"related": map[string]any{
"type": "boolean",
"description": "该资产是否与【当前测试任务】相关:true(默认)才自动纳入资产覆盖度(测试范围分母);false 则只入库、不计入本任务覆盖度(如顺带发现的旁站/无关资产)。仅在任务开启资产覆盖度功能时生效。",
"description": "该资产是否与【当前测试任务】相关:true(默认)才自动纳入资产覆盖度(测试范围分母),该资产会进入待测资产中,如果是和任务无关的,例如CDN仅存储静态资源类,必须设置为false或不进行资产插入;false 则只入库、不计入本任务覆盖度(如顺带发现的旁站/无关资产)。仅在任务开启资产覆盖度功能时生效。",
},
}, "type"),
},
Expand Down Expand Up @@ -582,6 +585,66 @@ func (t *ToolSet) listCompanies() actool.CoreTool {
)
}

// listProxies lets an agent enumerate healthy outbound proxies it can route through
// on its own (e.g. curl -x, proxychains) for a specific target. Read-only: it only
// lists nodes, it does not change any global egress setting.
func (t *ToolSet) listProxies() actool.CoreTool {
return readTool(
"list_proxies",
"列出代理池中【当前健康】的出口代理节点,供你在命令里自行使用(如 curl -x <proxy>、"+
"proxychains、nmap --proxies)访问目标、轮换出口 IP。返回 http/https/socks5 代理的 "+
"address(scheme://host:port)、地区、匿名度、延迟。可选 protocol/region/tag 过滤。"+
"只读:不改变全局出口设置。代理池未开启时返回空。",
obj(map[string]any{
"protocol": str("按协议过滤(可选):http/https/socks5"),
"region": str("按地区码过滤(可选),如 CN/US"),
"tag": str("按标签过滤(可选)"),
}),
func(_ context.Context, in json.RawMessage) (actool.Result, error) {
if t.ps == nil || !t.ps.PoolEnabled() {
return actool.Errorf("list_proxies 未启用: 代理池功能已关闭(在系统设置开启后可用)"), nil
}
var a struct {
Protocol string `json:"protocol"`
Region string `json:"region"`
Tag string `json:"tag"`
}
_ = json.Unmarshal(in, &a)
f := db.ProxyFilter{
Protocol: strings.TrimSpace(a.Protocol),
Region: strings.TrimSpace(a.Region),
OnlyEnabled: true,
OnlyHealthy: true,
}
if tag := strings.TrimSpace(a.Tag); tag != "" {
f.Tags = []string{tag}
}
proxies, err := t.ps.ListProxies(f)
if err != nil {
return actool.Errorf("查询代理失败: " + err.Error()), nil
}
type proxyOut struct {
Address string `json:"address"` // scheme://host:port (含认证,供命令直接使用)
Protocol string `json:"protocol"`
Region string `json:"region,omitempty"`
Anonymity string `json:"anonymity,omitempty"`
LatencyMs int `json:"latency_ms"`
}
out := make([]proxyOut, 0, len(proxies))
for _, p := range proxies {
out = append(out, proxyOut{
Address: p.URL().String(),
Protocol: p.Protocol,
Region: p.Region,
Anonymity: p.Anonymity,
LatencyMs: p.LatencyMs,
})
}
return jsonResult(map[string]any{"count": len(out), "proxies": out})
},
)
}

// splitLines splits a multi-line string into non-empty trimmed lines.
func splitLines(s string) []string {
var out []string
Expand All @@ -603,6 +666,8 @@ func (t *ToolSet) WorkerTools() []actool.CoreTool {
t.searchAllWorkerTraces(), t.listWorkerTraces(), t.getWorkerTrace(),
// asset management (handlers guard nil store internally)
t.insertAssets(), t.addCompanyScope(), t.listAssets(), t.listCompanies(),
// outbound proxy pool (read-only; guarded when pool store/switch off)
t.listProxies(),
}
}

Expand Down
1 change: 1 addition & 0 deletions agent/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ func (w *Worker) Execute(ctx context.Context, name string, taskID int64, as *db.
tsx.SetCoverageEnabled(coverageEnabled)
if as != nil {
tsx.SetAssetStore(as, as.Companies())
tsx.SetProxyStore(as.Proxies()) // list_proxies(只读,随代理池开关生效)
}
tsx.SetOwnerNode(intent.ID) // assets this worker discovers anchor to its intent → visible to the task
tsx.SetEnrich(enr) // async DNS/HTTP auto-completion for assets this worker writes
Expand Down
3 changes: 2 additions & 1 deletion cmd/artex/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func main() {
addr = flag.String("addr", ":8787", "HTTP listen address")
dataDir = flag.String("data", filepath.Join(config.BaseDir(), "data"), "data directory for SQLite stores (default: data/ next to the executable)")
proxy = flag.String("proxy", ":8788", "traffic recording proxy address (empty to disable)")
proxyGW = flag.String("proxy-pool-gateway", "127.0.0.1:8789", "proxy-pool forwarding gateway address (empty to disable 入口C)")
)
flag.Parse()

Expand Down Expand Up @@ -76,7 +77,7 @@ func main() {
ctx, shutdown := shutdownContext(sigCtx)
defer shutdown(agent.AbortShutdown)

mgr, err := server.NewManager(*dataDir, *proxy)
mgr, err := server.NewManager(*dataDir, *proxy, *proxyGW)
if err != nil {
log.Fatalf("open stores: %v", err)
}
Expand Down
4 changes: 4 additions & 0 deletions db/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ func (d *DB) Assets() *AssetStore {
// Companies returns the company store associated with this asset store.
func (s *AssetStore) Companies() *CompanyStore { return s.company }

// Proxies returns the proxy pool store on the same DB, so agent tool wiring that
// already holds an AssetStore can reach the pool without a separate handle.
func (s *AssetStore) Proxies() *ProxyStore { return s.db.Proxies() }

// withCompanyScopeMutation serializes scope resolution and every asset write
// that consumes its result in one transaction. Nested asset side effects reuse
// the same transaction through the scoped store.
Expand Down
Loading