@oyasmi/pipiclaw 0.5.8 → 0.6.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.
- package/README.md +39 -3
- package/dist/agent/channel-runner.d.ts +5 -0
- package/dist/agent/channel-runner.js +59 -15
- package/dist/agent/prompt-builder.js +6 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/memory/consolidation.js +11 -2
- package/dist/memory/session.js +2 -2
- package/dist/memory/sidecar-worker.d.ts +1 -0
- package/dist/memory/sidecar-worker.js +56 -1
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +2 -0
- package/dist/runtime/bootstrap.d.ts +2 -1
- package/dist/runtime/bootstrap.js +74 -23
- package/dist/runtime/delivery.js +56 -5
- package/dist/runtime/dingtalk.d.ts +2 -0
- package/dist/runtime/dingtalk.js +14 -7
- package/dist/runtime/events.d.ts +3 -0
- package/dist/runtime/events.js +30 -5
- package/dist/security/command-guard.js +4 -0
- package/dist/security/config.d.ts +6 -0
- package/dist/security/config.js +57 -6
- package/dist/security/network.d.ts +28 -0
- package/dist/security/network.js +246 -0
- package/dist/security/path-guard.js +4 -0
- package/dist/security/platform.d.ts +1 -0
- package/dist/security/platform.js +3 -0
- package/dist/security/types.d.ts +16 -1
- package/dist/settings.d.ts +4 -1
- package/dist/settings.js +31 -6
- package/dist/shared/config-diagnostics.d.ts +7 -0
- package/dist/shared/config-diagnostics.js +3 -0
- package/dist/subagents/discovery.d.ts +1 -1
- package/dist/subagents/discovery.js +1 -1
- package/dist/subagents/tool.d.ts +2 -0
- package/dist/subagents/tool.js +24 -2
- package/dist/tools/config.d.ts +37 -0
- package/dist/tools/config.js +170 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +23 -1
- package/dist/tools/web-fetch.d.ts +17 -0
- package/dist/tools/web-fetch.js +29 -0
- package/dist/tools/web-search.d.ts +16 -0
- package/dist/tools/web-search.js +29 -0
- package/dist/web/client.d.ts +41 -0
- package/dist/web/client.js +193 -0
- package/dist/web/config.d.ts +19 -0
- package/dist/web/config.js +35 -0
- package/dist/web/extract.d.ts +7 -0
- package/dist/web/extract.js +122 -0
- package/dist/web/fetch.d.ts +23 -0
- package/dist/web/fetch.js +150 -0
- package/dist/web/format.d.ts +21 -0
- package/dist/web/format.js +38 -0
- package/dist/web/search-providers.d.ts +15 -0
- package/dist/web/search-providers.js +199 -0
- package/dist/web/search.d.ts +19 -0
- package/dist/web/search.js +52 -0
- package/package.json +9 -2
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { lookup } from "node:dns/promises";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
export class NetworkGuardError extends Error {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super(options.message);
|
|
6
|
+
this.name = "NetworkGuardError";
|
|
7
|
+
this.url = options.url;
|
|
8
|
+
this.stage = options.stage;
|
|
9
|
+
this.category = options.category;
|
|
10
|
+
this.resolvedHost = options.resolvedHost;
|
|
11
|
+
this.resolvedAddress = options.resolvedAddress;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const BLOCKED_HOSTS = new Set(["localhost", "metadata.google.internal", "metadata", "169.254.169.254"]);
|
|
15
|
+
const PRIVATE_IPV4_CIDRS = [
|
|
16
|
+
"0.0.0.0/8",
|
|
17
|
+
"10.0.0.0/8",
|
|
18
|
+
"100.64.0.0/10",
|
|
19
|
+
"127.0.0.0/8",
|
|
20
|
+
"169.254.0.0/16",
|
|
21
|
+
"172.16.0.0/12",
|
|
22
|
+
"192.168.0.0/16",
|
|
23
|
+
"198.18.0.0/15",
|
|
24
|
+
];
|
|
25
|
+
const PRIVATE_IPV6_CIDRS = ["::1/128", "::/128", "fc00::/7", "fe80::/10"];
|
|
26
|
+
function normalizeHost(host) {
|
|
27
|
+
return host.trim().replace(/\.$/, "").toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
function parseIpv4(ip) {
|
|
30
|
+
const parts = ip.split(".");
|
|
31
|
+
if (parts.length !== 4) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
let value = 0;
|
|
35
|
+
for (const part of parts) {
|
|
36
|
+
if (!/^\d+$/.test(part)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const octet = Number.parseInt(part, 10);
|
|
40
|
+
if (octet < 0 || octet > 255) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
value = (value << 8) | octet;
|
|
44
|
+
}
|
|
45
|
+
return value >>> 0;
|
|
46
|
+
}
|
|
47
|
+
function expandIpv6(ip) {
|
|
48
|
+
const normalized = ip.toLowerCase();
|
|
49
|
+
const hasEmbeddedIpv4 = normalized.includes(".");
|
|
50
|
+
let working = normalized;
|
|
51
|
+
if (hasEmbeddedIpv4) {
|
|
52
|
+
const lastColon = working.lastIndexOf(":");
|
|
53
|
+
if (lastColon === -1) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const ipv4 = parseIpv4(working.slice(lastColon + 1));
|
|
57
|
+
if (ipv4 === null) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const high = ((ipv4 >>> 16) & 0xffff).toString(16);
|
|
61
|
+
const low = (ipv4 & 0xffff).toString(16);
|
|
62
|
+
working = `${working.slice(0, lastColon)}:${high}:${low}`;
|
|
63
|
+
}
|
|
64
|
+
const pieces = working.split("::");
|
|
65
|
+
if (pieces.length > 2) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const left = pieces[0] ? pieces[0].split(":").filter(Boolean) : [];
|
|
69
|
+
const right = pieces[1] ? pieces[1].split(":").filter(Boolean) : [];
|
|
70
|
+
if (left.length + right.length > 8) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const fill = new Array(8 - left.length - right.length).fill("0");
|
|
74
|
+
const groups = pieces.length === 2 ? [...left, ...fill, ...right] : left;
|
|
75
|
+
return groups.length === 8 ? groups : null;
|
|
76
|
+
}
|
|
77
|
+
function parseIpv6(ip) {
|
|
78
|
+
const groups = expandIpv6(ip);
|
|
79
|
+
if (!groups) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
let value = 0n;
|
|
83
|
+
for (const group of groups) {
|
|
84
|
+
if (!/^[0-9a-f]{1,4}$/i.test(group)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
value = (value << 16n) | BigInt(Number.parseInt(group, 16));
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
function ipInCidr(ip, cidr) {
|
|
92
|
+
const [network, prefixText] = cidr.split("/");
|
|
93
|
+
const prefix = Number.parseInt(prefixText ?? "", 10);
|
|
94
|
+
if (!Number.isFinite(prefix)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const version = isIP(ip);
|
|
98
|
+
if (version === 4) {
|
|
99
|
+
const ipValue = parseIpv4(ip);
|
|
100
|
+
const networkValue = parseIpv4(network);
|
|
101
|
+
if (ipValue === null || networkValue === null || prefix < 0 || prefix > 32) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
|
|
105
|
+
return (ipValue & mask) === (networkValue & mask);
|
|
106
|
+
}
|
|
107
|
+
if (version === 6) {
|
|
108
|
+
const ipValue = parseIpv6(ip);
|
|
109
|
+
const networkValue = parseIpv6(network);
|
|
110
|
+
if (ipValue === null || networkValue === null || prefix < 0 || prefix > 128) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const shift = 128 - prefix;
|
|
114
|
+
if (shift === 128) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
return ipValue >> BigInt(shift) === networkValue >> BigInt(shift);
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
function matchesAllowedHost(hostname, allowedHosts) {
|
|
122
|
+
const normalized = normalizeHost(hostname);
|
|
123
|
+
return allowedHosts.some((candidate) => normalizeHost(candidate) === normalized);
|
|
124
|
+
}
|
|
125
|
+
function matchesAllowedCidr(address, allowedCidrs) {
|
|
126
|
+
return allowedCidrs.some((cidr) => ipInCidr(address, cidr.trim()));
|
|
127
|
+
}
|
|
128
|
+
function isBlockedHost(hostname) {
|
|
129
|
+
const normalized = normalizeHost(hostname);
|
|
130
|
+
return normalized.endsWith(".localhost") || BLOCKED_HOSTS.has(normalized);
|
|
131
|
+
}
|
|
132
|
+
function isPrivateAddress(address) {
|
|
133
|
+
const version = isIP(address);
|
|
134
|
+
if (version === 4) {
|
|
135
|
+
return PRIVATE_IPV4_CIDRS.some((cidr) => ipInCidr(address, cidr));
|
|
136
|
+
}
|
|
137
|
+
if (version === 6) {
|
|
138
|
+
return PRIVATE_IPV6_CIDRS.some((cidr) => ipInCidr(address, cidr));
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
async function validateUrlTarget(rawUrl, context, stage) {
|
|
143
|
+
const url = (() => {
|
|
144
|
+
try {
|
|
145
|
+
return new URL(rawUrl);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
throw new NetworkGuardError({
|
|
149
|
+
url: rawUrl,
|
|
150
|
+
stage,
|
|
151
|
+
category: "invalid-url",
|
|
152
|
+
message: `Invalid URL: ${rawUrl}`,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
})();
|
|
156
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
157
|
+
throw new NetworkGuardError({
|
|
158
|
+
url: rawUrl,
|
|
159
|
+
stage,
|
|
160
|
+
category: "unsupported-scheme",
|
|
161
|
+
message: `Only http/https URLs are allowed, got ${url.protocol || "unknown"}`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const hostname = normalizeHost(url.hostname);
|
|
165
|
+
if (!hostname) {
|
|
166
|
+
throw new NetworkGuardError({
|
|
167
|
+
url: rawUrl,
|
|
168
|
+
stage,
|
|
169
|
+
category: "missing-host",
|
|
170
|
+
message: `URL is missing a hostname: ${rawUrl}`,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
const { networkGuard } = context.config;
|
|
174
|
+
if (!networkGuard.enabled) {
|
|
175
|
+
return { url: url.toString(), hostname };
|
|
176
|
+
}
|
|
177
|
+
if (matchesAllowedHost(hostname, networkGuard.allowedHosts)) {
|
|
178
|
+
return { url: url.toString(), hostname };
|
|
179
|
+
}
|
|
180
|
+
if (isBlockedHost(hostname)) {
|
|
181
|
+
throw new NetworkGuardError({
|
|
182
|
+
url: rawUrl,
|
|
183
|
+
stage,
|
|
184
|
+
category: "blocked-host",
|
|
185
|
+
message: `Blocked host: ${hostname}`,
|
|
186
|
+
resolvedHost: hostname,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (isIP(hostname)) {
|
|
190
|
+
if (!matchesAllowedCidr(hostname, networkGuard.allowedCidrs) && isPrivateAddress(hostname)) {
|
|
191
|
+
throw new NetworkGuardError({
|
|
192
|
+
url: rawUrl,
|
|
193
|
+
stage,
|
|
194
|
+
category: "private-address",
|
|
195
|
+
message: `Blocked private network address: ${hostname}`,
|
|
196
|
+
resolvedHost: hostname,
|
|
197
|
+
resolvedAddress: hostname,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return { url: url.toString(), hostname, resolvedAddress: hostname };
|
|
201
|
+
}
|
|
202
|
+
let records;
|
|
203
|
+
try {
|
|
204
|
+
records = (await lookup(hostname, { all: true, verbatim: true }));
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
throw new NetworkGuardError({
|
|
208
|
+
url: rawUrl,
|
|
209
|
+
stage,
|
|
210
|
+
category: "dns-failure",
|
|
211
|
+
message: `Failed to resolve host ${hostname}: ${error instanceof Error ? error.message : String(error)}`,
|
|
212
|
+
resolvedHost: hostname,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (records.length === 0) {
|
|
216
|
+
throw new NetworkGuardError({
|
|
217
|
+
url: rawUrl,
|
|
218
|
+
stage,
|
|
219
|
+
category: "dns-failure",
|
|
220
|
+
message: `Failed to resolve host ${hostname}`,
|
|
221
|
+
resolvedHost: hostname,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
for (const record of records) {
|
|
225
|
+
if (matchesAllowedCidr(record.address, networkGuard.allowedCidrs)) {
|
|
226
|
+
return { url: url.toString(), hostname, resolvedAddress: record.address };
|
|
227
|
+
}
|
|
228
|
+
if (isPrivateAddress(record.address)) {
|
|
229
|
+
throw new NetworkGuardError({
|
|
230
|
+
url: rawUrl,
|
|
231
|
+
stage,
|
|
232
|
+
category: "private-address",
|
|
233
|
+
message: `Blocked private network address resolved from ${hostname}: ${record.address}`,
|
|
234
|
+
resolvedHost: hostname,
|
|
235
|
+
resolvedAddress: record.address,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return { url: url.toString(), hostname, resolvedAddress: records[0]?.address };
|
|
240
|
+
}
|
|
241
|
+
export async function validateNetworkTarget(url, context) {
|
|
242
|
+
return validateUrlTarget(url, context, "request");
|
|
243
|
+
}
|
|
244
|
+
export async function validateRedirectTarget(url, context) {
|
|
245
|
+
return validateUrlTarget(url, context, "redirect");
|
|
246
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, lstatSync, realpathSync } from "node:fs";
|
|
2
2
|
import { homedir, tmpdir } from "node:os";
|
|
3
3
|
import { basename, dirname, isAbsolute, normalize, resolve } from "node:path";
|
|
4
|
+
import { isWindowsPlatform } from "./platform.js";
|
|
4
5
|
const PRIVATE_KEY_EXTENSIONS = new Set([".pem", ".key", ".p12", ".pfx"]);
|
|
5
6
|
const PRIVATE_KEY_NAME_HINTS = /(id_rsa|id_ed25519|private|secret|credentials)/i;
|
|
6
7
|
const PROC_MEM_PATH = /^\/proc\/\d+\/mem(?:\/|$)/;
|
|
@@ -197,6 +198,9 @@ export function guardPath(rawPath, operation, ctx) {
|
|
|
197
198
|
if (!ctx.config.enabled) {
|
|
198
199
|
return { allowed: true, operation, rawPath };
|
|
199
200
|
}
|
|
201
|
+
if (isWindowsPlatform()) {
|
|
202
|
+
return { allowed: true, operation, rawPath };
|
|
203
|
+
}
|
|
200
204
|
const homeDir = ctx.homeDir ?? homedir();
|
|
201
205
|
const effectiveCtx = {
|
|
202
206
|
...ctx,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isWindowsPlatform(): boolean;
|
package/dist/security/types.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface SecurityConfig {
|
|
|
14
14
|
writeDeny: string[];
|
|
15
15
|
resolveSymlinks: boolean;
|
|
16
16
|
};
|
|
17
|
+
networkGuard: {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
allowedCidrs: string[];
|
|
20
|
+
allowedHosts: string[];
|
|
21
|
+
maxRedirects: number;
|
|
22
|
+
};
|
|
17
23
|
audit: {
|
|
18
24
|
logBlocked: boolean;
|
|
19
25
|
logFile?: string;
|
|
@@ -63,4 +69,13 @@ export interface BlockedCommandLogEvent extends SecurityLogEventBase {
|
|
|
63
69
|
reason?: string;
|
|
64
70
|
matchedText?: string;
|
|
65
71
|
}
|
|
66
|
-
export
|
|
72
|
+
export interface BlockedNetworkLogEvent extends SecurityLogEventBase {
|
|
73
|
+
type: "network";
|
|
74
|
+
url: string;
|
|
75
|
+
stage: "request" | "redirect";
|
|
76
|
+
resolvedHost?: string;
|
|
77
|
+
resolvedAddress?: string;
|
|
78
|
+
category?: string;
|
|
79
|
+
reason?: string;
|
|
80
|
+
}
|
|
81
|
+
export type SecurityLogEvent = BlockedPathLogEvent | BlockedCommandLogEvent | BlockedNetworkLogEvent;
|
package/dist/settings.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* This module currently provides only PipiclawSettingsManager.
|
|
8
8
|
*/
|
|
9
9
|
import type { Transport } from "@mariozechner/pi-ai";
|
|
10
|
+
import type { ConfigDiagnostic } from "./shared/config-diagnostics.js";
|
|
10
11
|
type PackageSource = string | {
|
|
11
12
|
source: string;
|
|
12
13
|
extensions?: string[];
|
|
@@ -83,10 +84,13 @@ export interface PipiclawSettings {
|
|
|
83
84
|
export declare class PipiclawSettingsManager {
|
|
84
85
|
private settingsPath;
|
|
85
86
|
private settings;
|
|
87
|
+
private loadErrors;
|
|
86
88
|
constructor(baseDir: string);
|
|
87
89
|
private load;
|
|
88
90
|
private save;
|
|
89
91
|
reload(): void;
|
|
92
|
+
drainErrors(): SettingsError[];
|
|
93
|
+
getDiagnostics(): ConfigDiagnostic[];
|
|
90
94
|
getCompactionSettings(): PipiclawCompactionSettings;
|
|
91
95
|
getCompactionEnabled(): boolean;
|
|
92
96
|
setCompactionEnabled(enabled: boolean): void;
|
|
@@ -176,6 +180,5 @@ export declare class PipiclawSettingsManager {
|
|
|
176
180
|
getProjectSettings(): Settings;
|
|
177
181
|
applyOverrides(overrides: Partial<Settings>): void;
|
|
178
182
|
flush(): Promise<void>;
|
|
179
|
-
drainErrors(): SettingsError[];
|
|
180
183
|
}
|
|
181
184
|
export {};
|
package/dist/settings.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
10
10
|
import { dirname, join } from "path";
|
|
11
|
+
import * as log from "./log.js";
|
|
11
12
|
const DEFAULT_COMPACTION = {
|
|
12
13
|
enabled: true,
|
|
13
14
|
reserveTokens: 16384,
|
|
@@ -40,18 +41,32 @@ const DEFAULT_SESSION_MEMORY = {
|
|
|
40
41
|
*/
|
|
41
42
|
export class PipiclawSettingsManager {
|
|
42
43
|
constructor(baseDir) {
|
|
44
|
+
this.loadErrors = [];
|
|
43
45
|
this.settingsPath = join(baseDir, "settings.json");
|
|
44
46
|
this.settings = this.load();
|
|
45
47
|
}
|
|
46
48
|
load() {
|
|
49
|
+
this.loadErrors = [];
|
|
47
50
|
if (!existsSync(this.settingsPath)) {
|
|
48
51
|
return {};
|
|
49
52
|
}
|
|
50
53
|
try {
|
|
51
54
|
const content = readFileSync(this.settingsPath, "utf-8");
|
|
52
|
-
|
|
55
|
+
const parsed = JSON.parse(content);
|
|
56
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
57
|
+
this.loadErrors.push({
|
|
58
|
+
scope: "global",
|
|
59
|
+
error: new Error(`Expected a JSON object in ${this.settingsPath}`),
|
|
60
|
+
});
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
return parsed;
|
|
53
64
|
}
|
|
54
|
-
catch {
|
|
65
|
+
catch (error) {
|
|
66
|
+
this.loadErrors.push({
|
|
67
|
+
scope: "global",
|
|
68
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
69
|
+
});
|
|
55
70
|
return {};
|
|
56
71
|
}
|
|
57
72
|
}
|
|
@@ -64,12 +79,25 @@ export class PipiclawSettingsManager {
|
|
|
64
79
|
writeFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2), "utf-8");
|
|
65
80
|
}
|
|
66
81
|
catch (error) {
|
|
67
|
-
|
|
82
|
+
log.logWarning(`Could not save settings file`, `${this.settingsPath}\n${String(error)}`);
|
|
68
83
|
}
|
|
69
84
|
}
|
|
70
85
|
reload() {
|
|
71
86
|
this.settings = this.load();
|
|
72
87
|
}
|
|
88
|
+
drainErrors() {
|
|
89
|
+
const errors = this.loadErrors;
|
|
90
|
+
this.loadErrors = [];
|
|
91
|
+
return errors;
|
|
92
|
+
}
|
|
93
|
+
getDiagnostics() {
|
|
94
|
+
return this.loadErrors.map(({ error }) => ({
|
|
95
|
+
source: "settings",
|
|
96
|
+
path: this.settingsPath,
|
|
97
|
+
severity: "error",
|
|
98
|
+
message: error.message,
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
73
101
|
getCompactionSettings() {
|
|
74
102
|
return {
|
|
75
103
|
...DEFAULT_COMPACTION,
|
|
@@ -377,7 +405,4 @@ export class PipiclawSettingsManager {
|
|
|
377
405
|
flush() {
|
|
378
406
|
return Promise.resolve();
|
|
379
407
|
}
|
|
380
|
-
drainErrors() {
|
|
381
|
-
return [];
|
|
382
|
-
}
|
|
383
408
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Api, Model } from "@mariozechner/pi-ai";
|
|
2
|
-
declare const ALLOWED_SUB_AGENT_TOOLS: readonly ["read", "bash", "edit", "write"];
|
|
2
|
+
declare const ALLOWED_SUB_AGENT_TOOLS: readonly ["read", "bash", "edit", "write", "web_search", "web_fetch"];
|
|
3
3
|
declare const ALLOWED_CONTEXT_MODES: readonly ["isolated", "contextual"];
|
|
4
4
|
declare const ALLOWED_MEMORY_MODES: readonly ["none", "session", "relevant"];
|
|
5
5
|
export type SubAgentToolName = (typeof ALLOWED_SUB_AGENT_TOOLS)[number];
|
|
@@ -3,7 +3,7 @@ import { existsSync, readdirSync, readFileSync } from "fs";
|
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { findExactModelReferenceMatch, formatModelReference } from "../models/utils.js";
|
|
5
5
|
import { SUB_AGENTS_DIR_NAME } from "../paths.js";
|
|
6
|
-
const ALLOWED_SUB_AGENT_TOOLS = ["read", "bash", "edit", "write"];
|
|
6
|
+
const ALLOWED_SUB_AGENT_TOOLS = ["read", "bash", "edit", "write", "web_search", "web_fetch"];
|
|
7
7
|
const DEFAULT_SUB_AGENT_TOOLS = ["read", "bash"];
|
|
8
8
|
const DEFAULT_MAX_TURNS = 24;
|
|
9
9
|
const DEFAULT_MAX_TOOL_CALLS = 48;
|
package/dist/subagents/tool.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { Executor } from "../sandbox.js";
|
|
|
4
4
|
import type { SecurityConfig } from "../security/types.js";
|
|
5
5
|
import type { PipiclawMemoryRecallSettings } from "../settings.js";
|
|
6
6
|
import type { UsageTotals } from "../shared/types.js";
|
|
7
|
+
import type { PipiclawWebToolsConfig } from "../tools/config.js";
|
|
7
8
|
import { type ResolvedSubAgentConfig, type SubAgentDiscoveryResult } from "./discovery.js";
|
|
8
9
|
declare const subagentSchema: import("@sinclair/typebox").TObject<{
|
|
9
10
|
label: import("@sinclair/typebox").TString;
|
|
@@ -44,6 +45,7 @@ export interface SubAgentToolOptions {
|
|
|
44
45
|
getSubAgentDiscovery?: () => SubAgentDiscoveryResult;
|
|
45
46
|
getMemoryRecallSettings?: () => PipiclawMemoryRecallSettings;
|
|
46
47
|
securityConfig?: SecurityConfig;
|
|
48
|
+
webConfig?: PipiclawWebToolsConfig;
|
|
47
49
|
runtimeContext: {
|
|
48
50
|
workspacePath: string;
|
|
49
51
|
channelId: string;
|
package/dist/subagents/tool.js
CHANGED
|
@@ -11,6 +11,8 @@ import { clipText, extractAssistantText, extractLabelFromArgs, HAN_REGEX } from
|
|
|
11
11
|
import { createBashTool } from "../tools/bash.js";
|
|
12
12
|
import { createEditTool } from "../tools/edit.js";
|
|
13
13
|
import { createReadTool } from "../tools/read.js";
|
|
14
|
+
import { createWebFetchTool } from "../tools/web-fetch.js";
|
|
15
|
+
import { createWebSearchTool } from "../tools/web-search.js";
|
|
14
16
|
import { createWriteTool } from "../tools/write.js";
|
|
15
17
|
import { formatSubAgentList, resolveSubAgentConfig, validateSubAgentTask, } from "./discovery.js";
|
|
16
18
|
const subagentSchema = Type.Object({
|
|
@@ -118,6 +120,25 @@ function createToolSet(executor, bashTimeoutSec, options) {
|
|
|
118
120
|
}),
|
|
119
121
|
];
|
|
120
122
|
}
|
|
123
|
+
function createNamedToolSet(executor, bashTimeoutSec, options) {
|
|
124
|
+
const tools = createToolSet(executor, bashTimeoutSec, options);
|
|
125
|
+
const byName = Object.fromEntries(tools.map((tool) => [tool.name, tool]));
|
|
126
|
+
if (options.webConfig && options.webConfig.enable !== false) {
|
|
127
|
+
byName.web_search = createWebSearchTool({
|
|
128
|
+
webConfig: options.webConfig,
|
|
129
|
+
securityConfig: options.securityConfig ?? DEFAULT_SECURITY_CONFIG,
|
|
130
|
+
workspaceDir: options.workspaceDir,
|
|
131
|
+
channelId: options.runtimeContext.channelId,
|
|
132
|
+
});
|
|
133
|
+
byName.web_fetch = createWebFetchTool({
|
|
134
|
+
webConfig: options.webConfig,
|
|
135
|
+
securityConfig: options.securityConfig ?? DEFAULT_SECURITY_CONFIG,
|
|
136
|
+
workspaceDir: options.workspaceDir,
|
|
137
|
+
channelId: options.runtimeContext.channelId,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return byName;
|
|
141
|
+
}
|
|
121
142
|
function buildSubAgentTask(task, config, runtimeContext, contextBlocks) {
|
|
122
143
|
const taskText = task.trim();
|
|
123
144
|
const lines = [
|
|
@@ -288,17 +309,18 @@ export function createSubAgentTool(options) {
|
|
|
288
309
|
details: createDetails(config, usage, assistantTurns, toolCalls, Date.now() - startedAt, Boolean(failureReason), failureReason),
|
|
289
310
|
});
|
|
290
311
|
};
|
|
312
|
+
const availableTools = Object.values(createNamedToolSet(options.executor, config.bashTimeoutSec, options));
|
|
291
313
|
const worker = options.createWorker?.({
|
|
292
314
|
subAgent: config,
|
|
293
315
|
apiKey,
|
|
294
|
-
tools: filterToolsByName(
|
|
316
|
+
tools: filterToolsByName(availableTools, config.tools),
|
|
295
317
|
}) ??
|
|
296
318
|
new Agent({
|
|
297
319
|
initialState: {
|
|
298
320
|
systemPrompt: config.systemPrompt,
|
|
299
321
|
model: config.model,
|
|
300
322
|
thinkingLevel: "off",
|
|
301
|
-
tools: filterToolsByName(
|
|
323
|
+
tools: filterToolsByName(availableTools, config.tools),
|
|
302
324
|
},
|
|
303
325
|
convertToLlm,
|
|
304
326
|
getApiKey: async () => apiKey,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ConfigDiagnostic } from "../shared/config-diagnostics.js";
|
|
2
|
+
export type WebSearchProvider = "brave" | "tavily" | "jina" | "searxng" | "duckduckgo";
|
|
3
|
+
export interface PipiclawWebSearchConfig {
|
|
4
|
+
provider: WebSearchProvider;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
maxResults: number;
|
|
8
|
+
timeoutMs: number;
|
|
9
|
+
}
|
|
10
|
+
export interface PipiclawWebFetchConfig {
|
|
11
|
+
maxChars: number;
|
|
12
|
+
timeoutMs: number;
|
|
13
|
+
maxImageBytes: number;
|
|
14
|
+
maxResponseBytes: number;
|
|
15
|
+
preferJina: boolean;
|
|
16
|
+
enableJinaFallback: boolean;
|
|
17
|
+
defaultExtractMode: "markdown" | "text";
|
|
18
|
+
}
|
|
19
|
+
export interface PipiclawWebToolsConfig {
|
|
20
|
+
enable: boolean;
|
|
21
|
+
proxy: string | null;
|
|
22
|
+
search: PipiclawWebSearchConfig;
|
|
23
|
+
fetch: PipiclawWebFetchConfig;
|
|
24
|
+
}
|
|
25
|
+
export interface PipiclawToolsConfig {
|
|
26
|
+
tools: {
|
|
27
|
+
web: PipiclawWebToolsConfig;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface LoadedToolsConfig {
|
|
31
|
+
config: PipiclawToolsConfig;
|
|
32
|
+
diagnostics: ConfigDiagnostic[];
|
|
33
|
+
}
|
|
34
|
+
export declare const DEFAULT_TOOLS_CONFIG: PipiclawToolsConfig;
|
|
35
|
+
export declare function getToolsConfigPath(appHomeDir?: string): string;
|
|
36
|
+
export declare function loadToolsConfigWithDiagnostics(appHomeDir?: string): LoadedToolsConfig;
|
|
37
|
+
export declare function loadToolsConfig(appHomeDir?: string): PipiclawToolsConfig;
|