@poncho-ai/harness 0.7.0 → 0.7.2
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/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +6 -0
- package/dist/index.d.ts +25 -20
- package/dist/index.js +330 -246
- package/package.json +3 -3
- package/src/agent-parser.ts +70 -23
- package/src/config.ts +0 -2
- package/src/harness.ts +127 -75
- package/src/mcp.ts +5 -58
- package/src/skill-context.ts +86 -27
- package/src/skill-tools.ts +103 -60
- package/src/state.ts +6 -0
- package/src/tool-policy.ts +39 -0
- package/test/agent-parser.test.ts +26 -0
- package/test/harness.test.ts +113 -26
- package/test/mcp.test.ts +9 -24
package/dist/index.js
CHANGED
|
@@ -7,8 +7,6 @@ import YAML from "yaml";
|
|
|
7
7
|
|
|
8
8
|
// src/tool-policy.ts
|
|
9
9
|
var MCP_PATTERN = /^[^/*\s]+\/(\*|[^/*\s]+)$/;
|
|
10
|
-
var MCP_TOOL_PATTERN = /^(\*|[^/*\s]+)$/;
|
|
11
|
-
var SCRIPT_PATTERN = /^[^/*\s]+\/(\*|[^*\s]+)$/;
|
|
12
10
|
var validateMcpPattern = (pattern, path) => {
|
|
13
11
|
if (!MCP_PATTERN.test(pattern)) {
|
|
14
12
|
throw new Error(
|
|
@@ -16,19 +14,35 @@ var validateMcpPattern = (pattern, path) => {
|
|
|
16
14
|
);
|
|
17
15
|
}
|
|
18
16
|
};
|
|
19
|
-
var
|
|
20
|
-
|
|
17
|
+
var normalizeRelativeScriptPattern = (value, path) => {
|
|
18
|
+
const trimmed = value.trim();
|
|
19
|
+
if (trimmed.length === 0) {
|
|
20
|
+
throw new Error(`Invalid script pattern at ${path}: value cannot be empty.`);
|
|
21
|
+
}
|
|
22
|
+
const withoutDotPrefix = trimmed.startsWith("./") ? trimmed.slice(2) : trimmed;
|
|
23
|
+
const normalized = withoutDotPrefix.replaceAll("\\", "/");
|
|
24
|
+
if (normalized.startsWith("/") || normalized === ".." || normalized.startsWith("../")) {
|
|
21
25
|
throw new Error(
|
|
22
|
-
`Invalid
|
|
26
|
+
`Invalid script pattern at ${path}: "${value}". Expected a relative path.`
|
|
23
27
|
);
|
|
24
28
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (!SCRIPT_PATTERN.test(pattern)) {
|
|
29
|
+
const segments = normalized.split("/");
|
|
30
|
+
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
28
31
|
throw new Error(
|
|
29
|
-
`Invalid script pattern at ${path}: "${
|
|
32
|
+
`Invalid script pattern at ${path}: "${value}". Expected a normalized relative path.`
|
|
30
33
|
);
|
|
31
34
|
}
|
|
35
|
+
return `./${segments.join("/")}`;
|
|
36
|
+
};
|
|
37
|
+
var isSiblingScriptsPattern = (pattern) => pattern === "./scripts" || pattern.startsWith("./scripts/");
|
|
38
|
+
var escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
39
|
+
var matchesRelativeScriptPattern = (value, pattern) => {
|
|
40
|
+
const normalizedValue = normalizeRelativeScriptPattern(value, "value");
|
|
41
|
+
const normalizedPattern = normalizeRelativeScriptPattern(pattern, "pattern");
|
|
42
|
+
const regex = new RegExp(
|
|
43
|
+
`^${escapeRegex(normalizedPattern).replaceAll("\\*", ".*")}$`
|
|
44
|
+
);
|
|
45
|
+
return regex.test(normalizedValue);
|
|
32
46
|
};
|
|
33
47
|
var splitPattern = (pattern) => {
|
|
34
48
|
const slash = pattern.indexOf("/");
|
|
@@ -48,48 +62,6 @@ var matchesSlashPattern = (value, pattern) => {
|
|
|
48
62
|
}
|
|
49
63
|
return targetName === patternName;
|
|
50
64
|
};
|
|
51
|
-
var mergePolicyForEnvironment = (policy, environment) => {
|
|
52
|
-
const base = {
|
|
53
|
-
mode: policy?.mode,
|
|
54
|
-
include: [...policy?.include ?? []],
|
|
55
|
-
exclude: [...policy?.exclude ?? []]
|
|
56
|
-
};
|
|
57
|
-
const env = policy?.byEnvironment?.[environment];
|
|
58
|
-
if (!env) {
|
|
59
|
-
return base;
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
mode: env.mode ?? base.mode,
|
|
63
|
-
include: env.include ? [...env.include] : base.include,
|
|
64
|
-
exclude: env.exclude ? [...env.exclude] : base.exclude
|
|
65
|
-
};
|
|
66
|
-
};
|
|
67
|
-
var applyToolPolicy = (values, policy) => {
|
|
68
|
-
const mode = policy?.mode ?? "all";
|
|
69
|
-
const include = policy?.include ?? [];
|
|
70
|
-
const exclude = policy?.exclude ?? [];
|
|
71
|
-
const allowed = [];
|
|
72
|
-
const filteredOut = [];
|
|
73
|
-
for (const value of values) {
|
|
74
|
-
const inInclude = include.some((pattern) => matchesSlashPattern(value, pattern));
|
|
75
|
-
const inExclude = exclude.some((pattern) => matchesSlashPattern(value, pattern));
|
|
76
|
-
let keep = true;
|
|
77
|
-
if (mode === "allowlist") {
|
|
78
|
-
keep = inInclude;
|
|
79
|
-
} else if (mode === "denylist") {
|
|
80
|
-
keep = !inExclude;
|
|
81
|
-
}
|
|
82
|
-
if (mode === "all" && exclude.length > 0) {
|
|
83
|
-
keep = !inExclude;
|
|
84
|
-
}
|
|
85
|
-
if (keep) {
|
|
86
|
-
allowed.push(value);
|
|
87
|
-
} else {
|
|
88
|
-
filteredOut.push(value);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return { allowed, filteredOut };
|
|
92
|
-
};
|
|
93
65
|
|
|
94
66
|
// src/agent-parser.ts
|
|
95
67
|
var FRONTMATTER_PATTERN = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
|
|
@@ -109,17 +81,46 @@ var parseAgentMarkdown = (content) => {
|
|
|
109
81
|
}
|
|
110
82
|
const modelValue = asRecord(parsed.model);
|
|
111
83
|
const limitsValue = asRecord(parsed.limits);
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
84
|
+
const parseTools = (key) => {
|
|
85
|
+
const entries = Array.isArray(parsed[key]) ? parsed[key].filter((item) => typeof item === "string") : [];
|
|
86
|
+
const mcp = [];
|
|
87
|
+
const scripts = [];
|
|
88
|
+
for (const [index, entry] of entries.entries()) {
|
|
89
|
+
if (entry.startsWith("mcp:")) {
|
|
90
|
+
const withoutPrefix = entry.slice(4);
|
|
91
|
+
validateMcpPattern(withoutPrefix, `AGENT.md frontmatter ${key}[${index}]`);
|
|
92
|
+
mcp.push(withoutPrefix);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
scripts.push(
|
|
96
|
+
normalizeRelativeScriptPattern(entry, `AGENT.md frontmatter ${key}[${index}]`)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return { mcp, scripts };
|
|
100
|
+
};
|
|
101
|
+
const allowedTools = parseTools("allowed-tools");
|
|
102
|
+
const approvalRequired = parseTools("approval-required");
|
|
103
|
+
for (const pattern of approvalRequired.mcp) {
|
|
104
|
+
const matchesAllowed = allowedTools.mcp.some(
|
|
105
|
+
(allowedPattern) => matchesSlashPattern(pattern, allowedPattern)
|
|
106
|
+
);
|
|
107
|
+
if (!matchesAllowed) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Invalid AGENT.md frontmatter approval-required: MCP pattern "${pattern}" must be included in allowed-tools.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const pattern of approvalRequired.scripts) {
|
|
114
|
+
if (pattern.startsWith("./scripts/")) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const matchesAllowed = allowedTools.scripts.some(
|
|
118
|
+
(allowedPattern) => matchesRelativeScriptPattern(pattern, allowedPattern)
|
|
119
|
+
);
|
|
120
|
+
if (!matchesAllowed) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Invalid AGENT.md frontmatter approval-required: script pattern "${pattern}" must be included in allowed-tools when outside ./scripts/.`
|
|
123
|
+
);
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
const frontmatter = {
|
|
@@ -135,9 +136,13 @@ var parseAgentMarkdown = (content) => {
|
|
|
135
136
|
maxSteps: asNumberOrUndefined(limitsValue.maxSteps),
|
|
136
137
|
timeout: asNumberOrUndefined(limitsValue.timeout)
|
|
137
138
|
} : void 0,
|
|
138
|
-
allowedTools:
|
|
139
|
-
mcp:
|
|
140
|
-
scripts:
|
|
139
|
+
allowedTools: allowedTools.mcp.length > 0 || allowedTools.scripts.length > 0 ? {
|
|
140
|
+
mcp: allowedTools.mcp.length > 0 ? allowedTools.mcp : void 0,
|
|
141
|
+
scripts: allowedTools.scripts.length > 0 ? allowedTools.scripts : void 0
|
|
142
|
+
} : void 0,
|
|
143
|
+
approvalRequired: approvalRequired.mcp.length > 0 || approvalRequired.scripts.length > 0 ? {
|
|
144
|
+
mcp: approvalRequired.mcp.length > 0 ? approvalRequired.mcp : void 0,
|
|
145
|
+
scripts: approvalRequired.scripts.length > 0 ? approvalRequired.scripts : void 0
|
|
141
146
|
} : void 0
|
|
142
147
|
};
|
|
143
148
|
return {
|
|
@@ -1058,43 +1063,8 @@ var LocalMcpBridge = class {
|
|
|
1058
1063
|
`Invalid MCP auth config for "${name}": auth.type "bearer" requires auth.tokenEnv.`
|
|
1059
1064
|
);
|
|
1060
1065
|
}
|
|
1061
|
-
this.validatePolicy(server, name);
|
|
1062
1066
|
}
|
|
1063
1067
|
}
|
|
1064
|
-
validatePolicy(server, serverName) {
|
|
1065
|
-
const policy = server.tools;
|
|
1066
|
-
const validateList = (values, path) => {
|
|
1067
|
-
for (const [index, value] of (values ?? []).entries()) {
|
|
1068
|
-
validateMcpToolPattern(value, `${path}[${index}]`);
|
|
1069
|
-
}
|
|
1070
|
-
};
|
|
1071
|
-
validateList(policy?.include, `mcp.${serverName}.tools.include`);
|
|
1072
|
-
validateList(policy?.exclude, `mcp.${serverName}.tools.exclude`);
|
|
1073
|
-
validateList(
|
|
1074
|
-
policy?.byEnvironment?.development?.include,
|
|
1075
|
-
`mcp.${serverName}.tools.byEnvironment.development.include`
|
|
1076
|
-
);
|
|
1077
|
-
validateList(
|
|
1078
|
-
policy?.byEnvironment?.development?.exclude,
|
|
1079
|
-
`mcp.${serverName}.tools.byEnvironment.development.exclude`
|
|
1080
|
-
);
|
|
1081
|
-
validateList(
|
|
1082
|
-
policy?.byEnvironment?.staging?.include,
|
|
1083
|
-
`mcp.${serverName}.tools.byEnvironment.staging.include`
|
|
1084
|
-
);
|
|
1085
|
-
validateList(
|
|
1086
|
-
policy?.byEnvironment?.staging?.exclude,
|
|
1087
|
-
`mcp.${serverName}.tools.byEnvironment.staging.exclude`
|
|
1088
|
-
);
|
|
1089
|
-
validateList(
|
|
1090
|
-
policy?.byEnvironment?.production?.include,
|
|
1091
|
-
`mcp.${serverName}.tools.byEnvironment.production.include`
|
|
1092
|
-
);
|
|
1093
|
-
validateList(
|
|
1094
|
-
policy?.byEnvironment?.production?.exclude,
|
|
1095
|
-
`mcp.${serverName}.tools.byEnvironment.production.exclude`
|
|
1096
|
-
);
|
|
1097
|
-
}
|
|
1098
1068
|
getServerName(server) {
|
|
1099
1069
|
return server.name ?? server.url;
|
|
1100
1070
|
}
|
|
@@ -1219,7 +1189,7 @@ var LocalMcpBridge = class {
|
|
|
1219
1189
|
}
|
|
1220
1190
|
return output.sort();
|
|
1221
1191
|
}
|
|
1222
|
-
async loadTools(requestedPatterns
|
|
1192
|
+
async loadTools(requestedPatterns) {
|
|
1223
1193
|
for (const [index, pattern] of requestedPatterns.entries()) {
|
|
1224
1194
|
validateMcpPattern(pattern, `requestedPatterns[${index}]`);
|
|
1225
1195
|
}
|
|
@@ -1227,7 +1197,6 @@ var LocalMcpBridge = class {
|
|
|
1227
1197
|
if (requestedPatterns.length === 0) {
|
|
1228
1198
|
return tools;
|
|
1229
1199
|
}
|
|
1230
|
-
const filteredByPolicy = [];
|
|
1231
1200
|
const filteredByIntent = [];
|
|
1232
1201
|
for (const server of this.remoteServers) {
|
|
1233
1202
|
const serverName = this.getServerName(server);
|
|
@@ -1237,20 +1206,12 @@ var LocalMcpBridge = class {
|
|
|
1237
1206
|
}
|
|
1238
1207
|
const discovered = this.toolCatalog.get(serverName) ?? [];
|
|
1239
1208
|
const fullNames = discovered.map((tool) => `${serverName}/${tool.name}`);
|
|
1240
|
-
const
|
|
1241
|
-
const fullPatternPolicy = effectivePolicy ? {
|
|
1242
|
-
...effectivePolicy,
|
|
1243
|
-
include: effectivePolicy.include?.map((p) => `${serverName}/${p}`),
|
|
1244
|
-
exclude: effectivePolicy.exclude?.map((p) => `${serverName}/${p}`)
|
|
1245
|
-
} : effectivePolicy;
|
|
1246
|
-
const policyDecision = applyToolPolicy(fullNames, fullPatternPolicy);
|
|
1247
|
-
filteredByPolicy.push(...policyDecision.filteredOut);
|
|
1248
|
-
const selectedFullNames = policyDecision.allowed.filter(
|
|
1209
|
+
const selectedFullNames = fullNames.filter(
|
|
1249
1210
|
(toolName) => requestedPatterns.some((pattern) => matchesSlashPattern(toolName, pattern))
|
|
1250
1211
|
);
|
|
1251
|
-
for (const
|
|
1252
|
-
if (!selectedFullNames.includes(
|
|
1253
|
-
filteredByIntent.push(
|
|
1212
|
+
for (const discoveredTool of fullNames) {
|
|
1213
|
+
if (!selectedFullNames.includes(discoveredTool)) {
|
|
1214
|
+
filteredByIntent.push(discoveredTool);
|
|
1254
1215
|
}
|
|
1255
1216
|
}
|
|
1256
1217
|
const selectedRawNames = new Set(
|
|
@@ -1264,7 +1225,7 @@ var LocalMcpBridge = class {
|
|
|
1264
1225
|
this.log("info", "tools.selected", {
|
|
1265
1226
|
requestedPatternCount: requestedPatterns.length,
|
|
1266
1227
|
registeredCount: tools.length,
|
|
1267
|
-
filteredByPolicyCount:
|
|
1228
|
+
filteredByPolicyCount: 0,
|
|
1268
1229
|
filteredByIntentCount: filteredByIntent.length
|
|
1269
1230
|
});
|
|
1270
1231
|
return tools;
|
|
@@ -1322,7 +1283,7 @@ var createModelProvider = (provider) => {
|
|
|
1322
1283
|
};
|
|
1323
1284
|
|
|
1324
1285
|
// src/skill-context.ts
|
|
1325
|
-
import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
|
|
1286
|
+
import { readFile as readFile4, readdir as readdir2, stat } from "fs/promises";
|
|
1326
1287
|
import { dirname as dirname3, resolve as resolve5, normalize } from "path";
|
|
1327
1288
|
import YAML2 from "yaml";
|
|
1328
1289
|
var DEFAULT_SKILL_DIRS = ["skills"];
|
|
@@ -1351,26 +1312,53 @@ var parseSkillFrontmatter = (content) => {
|
|
|
1351
1312
|
return void 0;
|
|
1352
1313
|
}
|
|
1353
1314
|
const description = typeof parsed.description === "string" ? parsed.description.trim() : "";
|
|
1354
|
-
const
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1315
|
+
const parseTools = (key) => {
|
|
1316
|
+
const entries = Array.isArray(parsed[key]) ? parsed[key].filter((item) => typeof item === "string") : [];
|
|
1317
|
+
const mcp = [];
|
|
1318
|
+
const scripts = [];
|
|
1319
|
+
for (const [index, entry] of entries.entries()) {
|
|
1320
|
+
if (entry.startsWith("mcp:")) {
|
|
1321
|
+
const withoutPrefix = entry.slice(4);
|
|
1322
|
+
validateMcpPattern(withoutPrefix, `SKILL.md frontmatter ${key}[${index}]`);
|
|
1323
|
+
mcp.push(withoutPrefix);
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
scripts.push(
|
|
1327
|
+
normalizeRelativeScriptPattern(entry, `SKILL.md frontmatter ${key}[${index}]`)
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
return { mcp, scripts };
|
|
1331
|
+
};
|
|
1332
|
+
const allowedTools = parseTools("allowed-tools");
|
|
1333
|
+
const approvalRequired = parseTools("approval-required");
|
|
1334
|
+
for (const pattern of approvalRequired.mcp) {
|
|
1335
|
+
const matchesAllowed = allowedTools.mcp.some(
|
|
1336
|
+
(allowedPattern) => matchesSlashPattern(pattern, allowedPattern)
|
|
1337
|
+
);
|
|
1338
|
+
if (!matchesAllowed) {
|
|
1339
|
+
throw new Error(
|
|
1340
|
+
`Invalid SKILL.md frontmatter approval-required: MCP pattern "${pattern}" must be included in allowed-tools.`
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
for (const pattern of approvalRequired.scripts) {
|
|
1345
|
+
if (isSiblingScriptsPattern(pattern)) {
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
const matchesAllowed = allowedTools.scripts.some(
|
|
1349
|
+
(allowedPattern) => matchesRelativeScriptPattern(pattern, allowedPattern)
|
|
1350
|
+
);
|
|
1351
|
+
if (!matchesAllowed) {
|
|
1352
|
+
throw new Error(
|
|
1353
|
+
`Invalid SKILL.md frontmatter approval-required: script pattern "${pattern}" must be included in allowed-tools when outside ./scripts/.`
|
|
1354
|
+
);
|
|
1365
1355
|
}
|
|
1366
1356
|
}
|
|
1367
1357
|
return {
|
|
1368
1358
|
name,
|
|
1369
1359
|
description,
|
|
1370
|
-
allowedTools
|
|
1371
|
-
|
|
1372
|
-
scripts: scriptTools
|
|
1373
|
-
}
|
|
1360
|
+
allowedTools,
|
|
1361
|
+
approvalRequired
|
|
1374
1362
|
};
|
|
1375
1363
|
};
|
|
1376
1364
|
var collectSkillManifests = async (directory) => {
|
|
@@ -1378,11 +1366,22 @@ var collectSkillManifests = async (directory) => {
|
|
|
1378
1366
|
const files = [];
|
|
1379
1367
|
for (const entry of entries) {
|
|
1380
1368
|
const fullPath = resolve5(directory, entry.name);
|
|
1381
|
-
|
|
1369
|
+
let isDir = entry.isDirectory();
|
|
1370
|
+
let isFile = entry.isFile();
|
|
1371
|
+
if (entry.isSymbolicLink()) {
|
|
1372
|
+
try {
|
|
1373
|
+
const s = await stat(fullPath);
|
|
1374
|
+
isDir = s.isDirectory();
|
|
1375
|
+
isFile = s.isFile();
|
|
1376
|
+
} catch {
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
if (isDir) {
|
|
1382
1381
|
files.push(...await collectSkillManifests(fullPath));
|
|
1383
1382
|
continue;
|
|
1384
1383
|
}
|
|
1385
|
-
if (
|
|
1384
|
+
if (isFile && entry.name.toLowerCase() === "skill.md") {
|
|
1386
1385
|
files.push(fullPath);
|
|
1387
1386
|
}
|
|
1388
1387
|
}
|
|
@@ -1416,6 +1415,9 @@ var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
|
|
|
1416
1415
|
if (message.startsWith("Invalid MCP tool pattern") || message.startsWith("Invalid script pattern")) {
|
|
1417
1416
|
throw new Error(`Invalid SKILL.md frontmatter at ${manifest}: ${message}`);
|
|
1418
1417
|
}
|
|
1418
|
+
if (message.startsWith("Invalid SKILL.md frontmatter approval-required")) {
|
|
1419
|
+
throw new Error(`Invalid SKILL.md frontmatter at ${manifest}: ${message}`);
|
|
1420
|
+
}
|
|
1419
1421
|
}
|
|
1420
1422
|
}
|
|
1421
1423
|
return skills;
|
|
@@ -1546,16 +1548,13 @@ function convertSchema(schema) {
|
|
|
1546
1548
|
|
|
1547
1549
|
// src/skill-tools.ts
|
|
1548
1550
|
import { defineTool as defineTool3 } from "@poncho-ai/sdk";
|
|
1549
|
-
import { access as access2, readdir as readdir3 } from "fs/promises";
|
|
1551
|
+
import { access as access2, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
1550
1552
|
import { extname, normalize as normalize2, resolve as resolve6, sep as sep2 } from "path";
|
|
1551
1553
|
import { pathToFileURL } from "url";
|
|
1552
1554
|
import { createJiti } from "jiti";
|
|
1553
1555
|
var createSkillTools = (skills, options) => {
|
|
1554
|
-
if (skills.length === 0) {
|
|
1555
|
-
return [];
|
|
1556
|
-
}
|
|
1557
1556
|
const skillsByName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
1558
|
-
const knownNames = skills.map((skill) => skill.name).join(", ");
|
|
1557
|
+
const knownNames = skills.length > 0 ? skills.map((skill) => skill.name).join(", ") : "(none)";
|
|
1559
1558
|
return [
|
|
1560
1559
|
defineTool3({
|
|
1561
1560
|
name: "activate_skill",
|
|
@@ -1677,7 +1676,7 @@ var createSkillTools = (skills, options) => {
|
|
|
1677
1676
|
}),
|
|
1678
1677
|
defineTool3({
|
|
1679
1678
|
name: "list_skill_scripts",
|
|
1680
|
-
description: `List JavaScript/TypeScript script files available under a skill
|
|
1679
|
+
description: `List JavaScript/TypeScript script files available under a skill directory (recursive). Available skills: ${knownNames}`,
|
|
1681
1680
|
inputSchema: {
|
|
1682
1681
|
type: "object",
|
|
1683
1682
|
properties: {
|
|
@@ -1712,62 +1711,81 @@ var createSkillTools = (skills, options) => {
|
|
|
1712
1711
|
}),
|
|
1713
1712
|
defineTool3({
|
|
1714
1713
|
name: "run_skill_script",
|
|
1715
|
-
description: `Run a JavaScript/TypeScript module in a skill
|
|
1714
|
+
description: `Run a JavaScript/TypeScript module in a skill or project directory. Uses default export function or named run/main/handler function. Available skills: ${knownNames}`,
|
|
1716
1715
|
inputSchema: {
|
|
1717
1716
|
type: "object",
|
|
1718
1717
|
properties: {
|
|
1719
1718
|
skill: {
|
|
1720
1719
|
type: "string",
|
|
1721
|
-
description: "
|
|
1720
|
+
description: "Optional skill name. Omit to run a project-level script relative to AGENT.md directory."
|
|
1722
1721
|
},
|
|
1723
1722
|
script: {
|
|
1724
1723
|
type: "string",
|
|
1725
|
-
description: "Relative path
|
|
1724
|
+
description: "Relative script path from the skill/project root (e.g. ./fetch-page.ts, scripts/summarize.ts, tools/multiply.ts)"
|
|
1726
1725
|
},
|
|
1727
1726
|
input: {
|
|
1728
1727
|
type: "object",
|
|
1729
1728
|
description: "Optional JSON input payload passed to the script function"
|
|
1730
1729
|
}
|
|
1731
1730
|
},
|
|
1732
|
-
required: ["
|
|
1731
|
+
required: ["script"],
|
|
1733
1732
|
additionalProperties: false
|
|
1734
1733
|
},
|
|
1735
1734
|
handler: async (input) => {
|
|
1736
1735
|
const name = typeof input.skill === "string" ? input.skill.trim() : "";
|
|
1737
1736
|
const script = typeof input.script === "string" ? input.script.trim() : "";
|
|
1738
1737
|
const payload = typeof input.input === "object" && input.input !== null ? input.input : {};
|
|
1739
|
-
const skill = skillsByName.get(name);
|
|
1740
|
-
if (!skill) {
|
|
1741
|
-
return {
|
|
1742
|
-
error: `Unknown skill: "${name}". Available skills: ${knownNames}`
|
|
1743
|
-
};
|
|
1744
|
-
}
|
|
1745
1738
|
if (!script) {
|
|
1746
1739
|
return { error: "Script path is required" };
|
|
1747
1740
|
}
|
|
1748
1741
|
try {
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1742
|
+
if (name) {
|
|
1743
|
+
const skill = skillsByName.get(name);
|
|
1744
|
+
if (!skill) {
|
|
1745
|
+
return {
|
|
1746
|
+
error: `Unknown skill: "${name}". Available skills: ${knownNames}`
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
const resolved2 = resolveScriptPath(skill.skillDir, script);
|
|
1750
|
+
if (options?.isScriptAllowed && !options.isScriptAllowed(name, resolved2.relativePath)) {
|
|
1751
|
+
return {
|
|
1752
|
+
error: `Script "${resolved2.relativePath}" for skill "${name}" is not allowed by policy.`
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
await access2(resolved2.fullPath);
|
|
1756
|
+
const fn2 = await loadRunnableScriptFunction(resolved2.fullPath);
|
|
1757
|
+
const output2 = await fn2(payload, {
|
|
1758
|
+
scope: "skill",
|
|
1759
|
+
skill: name,
|
|
1760
|
+
scriptPath: resolved2.fullPath
|
|
1761
|
+
});
|
|
1752
1762
|
return {
|
|
1753
|
-
|
|
1763
|
+
skill: name,
|
|
1764
|
+
script: resolved2.relativePath,
|
|
1765
|
+
output: output2
|
|
1754
1766
|
};
|
|
1755
1767
|
}
|
|
1756
|
-
|
|
1757
|
-
const
|
|
1768
|
+
const baseDir = options?.workingDir ?? process.cwd();
|
|
1769
|
+
const resolved = resolveScriptPath(baseDir, script);
|
|
1770
|
+
if (options?.isRootScriptAllowed && !options.isRootScriptAllowed(resolved.relativePath)) {
|
|
1771
|
+
return {
|
|
1772
|
+
error: `Script "${resolved.relativePath}" is not allowed by policy.`
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
await access2(resolved.fullPath);
|
|
1776
|
+
const fn = await loadRunnableScriptFunction(resolved.fullPath);
|
|
1758
1777
|
const output = await fn(payload, {
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
scriptPath
|
|
1778
|
+
scope: "agent",
|
|
1779
|
+
scriptPath: resolved.fullPath
|
|
1762
1780
|
});
|
|
1763
1781
|
return {
|
|
1764
|
-
skill:
|
|
1765
|
-
script,
|
|
1782
|
+
skill: null,
|
|
1783
|
+
script: resolved.relativePath,
|
|
1766
1784
|
output
|
|
1767
1785
|
};
|
|
1768
1786
|
} catch (err) {
|
|
1769
1787
|
return {
|
|
1770
|
-
error: `Failed to run script "${script}" in skill "${name}": ${err instanceof Error ? err.message : String(err)}`
|
|
1788
|
+
error: name ? `Failed to run script "${script}" in skill "${name}": ${err instanceof Error ? err.message : String(err)}` : `Failed to run script "${script}" from AGENT scope: ${err instanceof Error ? err.message : String(err)}`
|
|
1771
1789
|
};
|
|
1772
1790
|
}
|
|
1773
1791
|
}
|
|
@@ -1776,25 +1794,35 @@ var createSkillTools = (skills, options) => {
|
|
|
1776
1794
|
};
|
|
1777
1795
|
var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"]);
|
|
1778
1796
|
var listSkillScripts = async (skill, isScriptAllowed) => {
|
|
1779
|
-
const
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
return [];
|
|
1784
|
-
}
|
|
1785
|
-
const scripts = await collectScriptFiles(scriptsRoot);
|
|
1786
|
-
return scripts.map((fullPath) => `scripts/${fullPath.slice(scriptsRoot.length + 1).split(sep2).join("/")}`).filter((path) => isScriptAllowed ? isScriptAllowed(skill.name, path) : true).sort();
|
|
1797
|
+
const scripts = await collectScriptFiles(skill.skillDir);
|
|
1798
|
+
return scripts.map((fullPath) => fullPath.slice(skill.skillDir.length + 1).split(sep2).join("/")).filter((relativePath) => relativePath.toLowerCase() !== "skill.md").map(
|
|
1799
|
+
(relativePath) => relativePath.includes("/") ? relativePath : `./${relativePath}`
|
|
1800
|
+
).filter((path) => isScriptAllowed ? isScriptAllowed(skill.name, path) : true).sort();
|
|
1787
1801
|
};
|
|
1788
1802
|
var collectScriptFiles = async (directory) => {
|
|
1789
1803
|
const entries = await readdir3(directory, { withFileTypes: true });
|
|
1790
1804
|
const files = [];
|
|
1791
1805
|
for (const entry of entries) {
|
|
1806
|
+
if (entry.name === "node_modules") {
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1792
1809
|
const fullPath = resolve6(directory, entry.name);
|
|
1793
|
-
|
|
1810
|
+
let isDir = entry.isDirectory();
|
|
1811
|
+
let isFile = entry.isFile();
|
|
1812
|
+
if (entry.isSymbolicLink()) {
|
|
1813
|
+
try {
|
|
1814
|
+
const s = await stat2(fullPath);
|
|
1815
|
+
isDir = s.isDirectory();
|
|
1816
|
+
isFile = s.isFile();
|
|
1817
|
+
} catch {
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
if (isDir) {
|
|
1794
1822
|
files.push(...await collectScriptFiles(fullPath));
|
|
1795
1823
|
continue;
|
|
1796
1824
|
}
|
|
1797
|
-
if (
|
|
1825
|
+
if (isFile) {
|
|
1798
1826
|
const extension = extname(fullPath).toLowerCase();
|
|
1799
1827
|
if (SCRIPT_EXTENSIONS.has(extension)) {
|
|
1800
1828
|
files.push(fullPath);
|
|
@@ -1803,16 +1831,23 @@ var collectScriptFiles = async (directory) => {
|
|
|
1803
1831
|
}
|
|
1804
1832
|
return files;
|
|
1805
1833
|
};
|
|
1806
|
-
var
|
|
1807
|
-
const
|
|
1834
|
+
var normalizeScriptPolicyPath = (relativePath) => {
|
|
1835
|
+
const trimmed = relativePath.trim();
|
|
1836
|
+
const normalized = normalize2(trimmed).split(sep2).join("/");
|
|
1808
1837
|
if (normalized.startsWith("..") || normalized.startsWith("/")) {
|
|
1809
|
-
throw new Error("Script path must be relative and within the
|
|
1838
|
+
throw new Error("Script path must be relative and within the allowed directory");
|
|
1839
|
+
}
|
|
1840
|
+
const withoutDotPrefix = normalized.startsWith("./") ? normalized.slice(2) : normalized;
|
|
1841
|
+
if (withoutDotPrefix.length === 0 || withoutDotPrefix === ".") {
|
|
1842
|
+
throw new Error("Script path must point to a file");
|
|
1810
1843
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1844
|
+
return withoutDotPrefix;
|
|
1845
|
+
};
|
|
1846
|
+
var resolveScriptPath = (baseDir, relativePath) => {
|
|
1847
|
+
const normalized = normalizeScriptPolicyPath(relativePath);
|
|
1848
|
+
const fullPath = resolve6(baseDir, normalized);
|
|
1849
|
+
if (!fullPath.startsWith(`${resolve6(baseDir)}${sep2}`) && fullPath !== resolve6(baseDir)) {
|
|
1850
|
+
throw new Error("Script path must stay inside the allowed directory");
|
|
1816
1851
|
}
|
|
1817
1852
|
const extension = extname(fullPath).toLowerCase();
|
|
1818
1853
|
if (!SCRIPT_EXTENSIONS.has(extension)) {
|
|
@@ -1820,7 +1855,10 @@ var resolveSkillScriptPath = (skill, relativePath) => {
|
|
|
1820
1855
|
`Unsupported script extension "${extension || "(none)"}". Allowed: ${[...SCRIPT_EXTENSIONS].join(", ")}`
|
|
1821
1856
|
);
|
|
1822
1857
|
}
|
|
1823
|
-
return
|
|
1858
|
+
return {
|
|
1859
|
+
fullPath,
|
|
1860
|
+
relativePath: `./${normalized}`
|
|
1861
|
+
};
|
|
1824
1862
|
};
|
|
1825
1863
|
var loadRunnableScriptFunction = async (scriptPath) => {
|
|
1826
1864
|
const loaded = await loadScriptModule(scriptPath);
|
|
@@ -1979,26 +2017,38 @@ You are running locally in development mode. Treat this as an editable agent wor
|
|
|
1979
2017
|
|
|
1980
2018
|
You can extend your own capabilities by creating custom JavaScript/TypeScript scripts:
|
|
1981
2019
|
|
|
1982
|
-
- Create scripts under \`skills/<skill-name
|
|
2020
|
+
- Create scripts under \`skills/<skill-name>/\` (recursive) to add new functionality
|
|
1983
2021
|
- Scripts can perform any Node.js operations: API calls, file processing, data transformations, web scraping, etc.
|
|
1984
2022
|
- Use the \`run_skill_script\` tool to execute these scripts and integrate results into your workflow
|
|
1985
2023
|
- This allows you to dynamically add custom tools and capabilities as users need them, without requiring external dependencies or MCP servers
|
|
1986
2024
|
|
|
2025
|
+
## Skill Authoring Guardrails
|
|
2026
|
+
|
|
2027
|
+
- Every \`SKILL.md\` must include YAML frontmatter between \`---\` markers.
|
|
2028
|
+
- Required frontmatter fields for discovery: \`name\` (non-empty string). Add \`description\` whenever possible.
|
|
2029
|
+
- \`allowed-tools\` and \`approval-required\` belong in SKILL frontmatter (not in script files).
|
|
2030
|
+
- MCP entries in frontmatter must use \`mcp:server/tool\` or \`mcp:server/*\`.
|
|
2031
|
+
- Script entries in frontmatter must be relative paths (for example \`./scripts/fetch.ts\`, \`./tools/audit.ts\`, \`./fetch-page.ts\`).
|
|
2032
|
+
- \`approval-required\` should be a stricter subset of allowed access:
|
|
2033
|
+
- MCP entries must also appear in \`allowed-tools\`.
|
|
2034
|
+
- Script entries outside \`./scripts/\` must also appear in \`allowed-tools\`.
|
|
2035
|
+
- Keep MCP server connection details (\`url\`, auth env vars) in \`poncho.config.js\` only.
|
|
2036
|
+
|
|
1987
2037
|
## When users ask about customization:
|
|
1988
2038
|
|
|
1989
2039
|
- Explain and edit \`poncho.config.js\` for model/provider, storage+memory, auth, telemetry, and MCP settings.
|
|
1990
2040
|
- Help create or update local skills under \`skills/<skill-name>/SKILL.md\`.
|
|
1991
|
-
- For executable
|
|
1992
|
-
-
|
|
1993
|
-
-
|
|
1994
|
-
-
|
|
2041
|
+
- For executable scripts, use a sibling \`scripts/\` directory next to \`AGENT.md\` or \`SKILL.md\`; run via \`run_skill_script\`.
|
|
2042
|
+
- To use a custom script folder (for example \`tools/\`), declare it in \`allowed-tools\` and gate sensitive paths with \`approval-required\` in frontmatter.
|
|
2043
|
+
- For MCP setup, default to direct \`poncho.config.js\` edits (\`mcp\` entries with URL and bearer token env).
|
|
2044
|
+
- Keep MCP server connection details in \`poncho.config.js\` only (name/url/auth). Do not move server definitions into \`SKILL.md\`.
|
|
2045
|
+
- In \`AGENT.md\`/\`SKILL.md\` frontmatter, declare MCP tools in \`allowed-tools\` array as \`mcp:server/pattern\` (for example \`mcp:linear/*\` or \`mcp:linear/list_issues\`), and use \`approval-required\` for human-gated calls.
|
|
1995
2046
|
- Never use nested MCP objects in skill frontmatter (for example \`mcp: [{ name, url, auth }]\`).
|
|
1996
|
-
- To scope tools to a skill: keep server config in \`poncho.config.js\`, add desired \`allowed-tools\` patterns in that skill's \`SKILL.md\`, and remove global \`AGENT.md\` patterns if you do not want global availability.
|
|
2047
|
+
- To scope tools to a skill: keep server config in \`poncho.config.js\`, add desired \`allowed-tools\`/ \`approval-required\` patterns in that skill's \`SKILL.md\`, and remove global \`AGENT.md\` patterns if you do not want global availability.
|
|
1997
2048
|
- Do not invent unsupported top-level config keys (for example \`model\` in \`poncho.config.js\`). Keep existing config structure unless README/spec explicitly says otherwise.
|
|
1998
|
-
- In \`poncho.config.js\`, MCP tool patterns are scoped within each server object, so use just the tool name (for example \`include: ["*"]\` or \`include: ["list_issues"]\`), not the full \`server/tool\` format.
|
|
1999
2049
|
- Keep \`poncho.config.js\` valid JavaScript and preserve existing imports/types/comments. If there is a JSDoc type import, do not rewrite it to a different package name.
|
|
2000
2050
|
- Preferred MCP config shape in \`poncho.config.js\`:
|
|
2001
|
-
\`mcp: [{ name: "linear", url: "https://mcp.linear.app/mcp", auth: { type: "bearer", tokenEnv: "LINEAR_TOKEN" }
|
|
2051
|
+
\`mcp: [{ name: "linear", url: "https://mcp.linear.app/mcp", auth: { type: "bearer", tokenEnv: "LINEAR_TOKEN" } }]\`
|
|
2002
2052
|
- If shell/CLI access exists, you can use \`poncho mcp add --url ... --name ... --auth-bearer-env ...\`, then \`poncho mcp tools list <server>\` and \`poncho mcp tools select <server>\`.
|
|
2003
2053
|
- If shell/CLI access is unavailable, ask the user to run needed commands and provide exact copy-paste commands.
|
|
2004
2054
|
- For setup, skills, MCP, auth, storage, telemetry, or "how do I..." questions, proactively read \`README.md\` with \`read_file\` before answering.
|
|
@@ -2076,9 +2126,6 @@ var AgentHarness = class {
|
|
|
2076
2126
|
this.dispatcher.registerMany(options.toolDefinitions);
|
|
2077
2127
|
}
|
|
2078
2128
|
}
|
|
2079
|
-
runtimeEnvironment() {
|
|
2080
|
-
return this.environment ?? "development";
|
|
2081
|
-
}
|
|
2082
2129
|
listActiveSkills() {
|
|
2083
2130
|
return [...this.activeSkillNames].sort();
|
|
2084
2131
|
}
|
|
@@ -2088,6 +2135,12 @@ var AgentHarness = class {
|
|
|
2088
2135
|
getAgentScriptIntent() {
|
|
2089
2136
|
return this.parsedAgent?.frontmatter.allowedTools?.scripts ?? [];
|
|
2090
2137
|
}
|
|
2138
|
+
getAgentMcpApprovalPatterns() {
|
|
2139
|
+
return this.parsedAgent?.frontmatter.approvalRequired?.mcp ?? [];
|
|
2140
|
+
}
|
|
2141
|
+
getAgentScriptApprovalPatterns() {
|
|
2142
|
+
return this.parsedAgent?.frontmatter.approvalRequired?.scripts ?? [];
|
|
2143
|
+
}
|
|
2091
2144
|
getRequestedMcpPatterns() {
|
|
2092
2145
|
const skillPatterns = /* @__PURE__ */ new Set();
|
|
2093
2146
|
for (const skillName of this.activeSkillNames) {
|
|
@@ -2105,34 +2158,96 @@ var AgentHarness = class {
|
|
|
2105
2158
|
return this.getAgentMcpIntent();
|
|
2106
2159
|
}
|
|
2107
2160
|
getRequestedScriptPatterns() {
|
|
2108
|
-
const
|
|
2161
|
+
const patterns = new Set(this.getAgentScriptIntent());
|
|
2109
2162
|
for (const skillName of this.activeSkillNames) {
|
|
2110
2163
|
const skill = this.loadedSkills.find((entry) => entry.name === skillName);
|
|
2111
2164
|
if (!skill) {
|
|
2112
2165
|
continue;
|
|
2113
2166
|
}
|
|
2114
2167
|
for (const pattern of skill.allowedTools.scripts) {
|
|
2168
|
+
patterns.add(pattern);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
return [...patterns];
|
|
2172
|
+
}
|
|
2173
|
+
getRequestedMcpApprovalPatterns() {
|
|
2174
|
+
const skillPatterns = /* @__PURE__ */ new Set();
|
|
2175
|
+
for (const skillName of this.activeSkillNames) {
|
|
2176
|
+
const skill = this.loadedSkills.find((entry) => entry.name === skillName);
|
|
2177
|
+
if (!skill) {
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
for (const pattern of skill.approvalRequired.mcp) {
|
|
2115
2181
|
skillPatterns.add(pattern);
|
|
2116
2182
|
}
|
|
2117
2183
|
}
|
|
2118
2184
|
if (skillPatterns.size > 0) {
|
|
2119
2185
|
return [...skillPatterns];
|
|
2120
2186
|
}
|
|
2121
|
-
return this.
|
|
2187
|
+
return this.getAgentMcpApprovalPatterns();
|
|
2188
|
+
}
|
|
2189
|
+
getRequestedScriptApprovalPatterns() {
|
|
2190
|
+
const patterns = new Set(this.getAgentScriptApprovalPatterns());
|
|
2191
|
+
for (const skillName of this.activeSkillNames) {
|
|
2192
|
+
const skill = this.loadedSkills.find((entry) => entry.name === skillName);
|
|
2193
|
+
if (!skill) {
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
for (const pattern of skill.approvalRequired.scripts) {
|
|
2197
|
+
patterns.add(pattern);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
return [...patterns];
|
|
2122
2201
|
}
|
|
2123
2202
|
isScriptAllowedByPolicy(skill, scriptPath) {
|
|
2124
|
-
const
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2203
|
+
const normalizedScriptPath = normalizeRelativeScriptPattern(
|
|
2204
|
+
scriptPath,
|
|
2205
|
+
"run_skill_script input.script"
|
|
2206
|
+
);
|
|
2207
|
+
const isSkillRootScript = normalizedScriptPath.startsWith("./") && !normalizedScriptPath.slice(2).includes("/");
|
|
2208
|
+
if (isSiblingScriptsPattern(normalizedScriptPath) || isSkillRootScript) {
|
|
2209
|
+
return true;
|
|
2210
|
+
}
|
|
2211
|
+
const skillPatterns = this.loadedSkills.find((entry) => entry.name === skill)?.allowedTools.scripts ?? [];
|
|
2212
|
+
const intentPatterns = /* @__PURE__ */ new Set([
|
|
2213
|
+
...this.getAgentScriptIntent(),
|
|
2214
|
+
...skillPatterns,
|
|
2215
|
+
...this.getRequestedScriptPatterns()
|
|
2216
|
+
]);
|
|
2217
|
+
return [...intentPatterns].some(
|
|
2218
|
+
(pattern) => matchesRelativeScriptPattern(normalizedScriptPath, pattern)
|
|
2219
|
+
);
|
|
2220
|
+
}
|
|
2221
|
+
isRootScriptAllowedByPolicy(scriptPath) {
|
|
2222
|
+
const normalizedScriptPath = normalizeRelativeScriptPattern(
|
|
2223
|
+
scriptPath,
|
|
2224
|
+
"run_skill_script input.script"
|
|
2225
|
+
);
|
|
2226
|
+
if (isSiblingScriptsPattern(normalizedScriptPath)) {
|
|
2227
|
+
return true;
|
|
2129
2228
|
}
|
|
2130
|
-
const
|
|
2131
|
-
|
|
2132
|
-
|
|
2229
|
+
const patterns = this.getAgentScriptIntent();
|
|
2230
|
+
return patterns.some(
|
|
2231
|
+
(pattern) => matchesRelativeScriptPattern(normalizedScriptPath, pattern)
|
|
2133
2232
|
);
|
|
2134
|
-
|
|
2135
|
-
|
|
2233
|
+
}
|
|
2234
|
+
requiresApprovalForToolCall(toolName, input) {
|
|
2235
|
+
if (toolName === "run_skill_script") {
|
|
2236
|
+
const rawScript = typeof input.script === "string" ? input.script.trim() : "";
|
|
2237
|
+
if (!rawScript) {
|
|
2238
|
+
return false;
|
|
2239
|
+
}
|
|
2240
|
+
const canonicalPath = normalizeRelativeScriptPattern(
|
|
2241
|
+
`./${normalizeScriptPolicyPath(rawScript)}`,
|
|
2242
|
+
"run_skill_script input.script"
|
|
2243
|
+
);
|
|
2244
|
+
const scriptPatterns = this.getRequestedScriptApprovalPatterns();
|
|
2245
|
+
return scriptPatterns.some(
|
|
2246
|
+
(pattern) => matchesRelativeScriptPattern(canonicalPath, pattern)
|
|
2247
|
+
);
|
|
2248
|
+
}
|
|
2249
|
+
const mcpPatterns = this.getRequestedMcpApprovalPatterns();
|
|
2250
|
+
return mcpPatterns.some((pattern) => matchesSlashPattern(toolName, pattern));
|
|
2136
2251
|
}
|
|
2137
2252
|
async refreshMcpTools(reason) {
|
|
2138
2253
|
if (!this.mcpBridge) {
|
|
@@ -2147,10 +2262,7 @@ var AgentHarness = class {
|
|
|
2147
2262
|
);
|
|
2148
2263
|
return;
|
|
2149
2264
|
}
|
|
2150
|
-
const tools = await this.mcpBridge.loadTools(
|
|
2151
|
-
requestedPatterns,
|
|
2152
|
-
this.runtimeEnvironment()
|
|
2153
|
-
);
|
|
2265
|
+
const tools = await this.mcpBridge.loadTools(requestedPatterns);
|
|
2154
2266
|
this.dispatcher.registerMany(tools);
|
|
2155
2267
|
for (const tool of tools) {
|
|
2156
2268
|
this.registeredMcpToolNames.add(tool.name);
|
|
@@ -2165,43 +2277,9 @@ var AgentHarness = class {
|
|
|
2165
2277
|
})}`
|
|
2166
2278
|
);
|
|
2167
2279
|
}
|
|
2168
|
-
validateScriptPolicyConfig(config) {
|
|
2169
|
-
const check = (values, path) => {
|
|
2170
|
-
for (const [index, value] of (values ?? []).entries()) {
|
|
2171
|
-
validateScriptPattern(value, `${path}[${index}]`);
|
|
2172
|
-
}
|
|
2173
|
-
};
|
|
2174
|
-
check(config?.scripts?.include, "poncho.config.js scripts.include");
|
|
2175
|
-
check(config?.scripts?.exclude, "poncho.config.js scripts.exclude");
|
|
2176
|
-
check(
|
|
2177
|
-
config?.scripts?.byEnvironment?.development?.include,
|
|
2178
|
-
"poncho.config.js scripts.byEnvironment.development.include"
|
|
2179
|
-
);
|
|
2180
|
-
check(
|
|
2181
|
-
config?.scripts?.byEnvironment?.development?.exclude,
|
|
2182
|
-
"poncho.config.js scripts.byEnvironment.development.exclude"
|
|
2183
|
-
);
|
|
2184
|
-
check(
|
|
2185
|
-
config?.scripts?.byEnvironment?.staging?.include,
|
|
2186
|
-
"poncho.config.js scripts.byEnvironment.staging.include"
|
|
2187
|
-
);
|
|
2188
|
-
check(
|
|
2189
|
-
config?.scripts?.byEnvironment?.staging?.exclude,
|
|
2190
|
-
"poncho.config.js scripts.byEnvironment.staging.exclude"
|
|
2191
|
-
);
|
|
2192
|
-
check(
|
|
2193
|
-
config?.scripts?.byEnvironment?.production?.include,
|
|
2194
|
-
"poncho.config.js scripts.byEnvironment.production.include"
|
|
2195
|
-
);
|
|
2196
|
-
check(
|
|
2197
|
-
config?.scripts?.byEnvironment?.production?.exclude,
|
|
2198
|
-
"poncho.config.js scripts.byEnvironment.production.exclude"
|
|
2199
|
-
);
|
|
2200
|
-
}
|
|
2201
2280
|
async initialize() {
|
|
2202
2281
|
this.parsedAgent = await parseAgentFile(this.workingDir);
|
|
2203
2282
|
const config = await loadPonchoConfig(this.workingDir);
|
|
2204
|
-
this.validateScriptPolicyConfig(config);
|
|
2205
2283
|
this.loadedConfig = config;
|
|
2206
2284
|
this.registerConfiguredBuiltInTools(config);
|
|
2207
2285
|
const provider = this.parsedAgent.frontmatter.model?.provider ?? "anthropic";
|
|
@@ -2228,7 +2306,9 @@ var AgentHarness = class {
|
|
|
2228
2306
|
return this.listActiveSkills();
|
|
2229
2307
|
},
|
|
2230
2308
|
onListActiveSkills: () => this.listActiveSkills(),
|
|
2231
|
-
isScriptAllowed: (skill, scriptPath) => this.isScriptAllowedByPolicy(skill, scriptPath)
|
|
2309
|
+
isScriptAllowed: (skill, scriptPath) => this.isScriptAllowedByPolicy(skill, scriptPath),
|
|
2310
|
+
isRootScriptAllowed: (scriptPath) => this.isRootScriptAllowedByPolicy(scriptPath),
|
|
2311
|
+
workingDir: this.workingDir
|
|
2232
2312
|
})
|
|
2233
2313
|
);
|
|
2234
2314
|
if (memoryConfig?.enabled) {
|
|
@@ -2454,7 +2534,7 @@ ${boundedMainMemory.trim()}` : "";
|
|
|
2454
2534
|
});
|
|
2455
2535
|
const modelName = agent.frontmatter.model?.name ?? "claude-opus-4-5";
|
|
2456
2536
|
const temperature = agent.frontmatter.model?.temperature ?? 0.2;
|
|
2457
|
-
const maxTokens = agent.frontmatter.model?.maxTokens
|
|
2537
|
+
const maxTokens = agent.frontmatter.model?.maxTokens;
|
|
2458
2538
|
const telemetryEnabled = this.loadedConfig?.telemetry?.enabled !== false;
|
|
2459
2539
|
const latitudeApiKey = this.loadedConfig?.telemetry?.latitude?.apiKey;
|
|
2460
2540
|
const result = await streamText({
|
|
@@ -2463,7 +2543,7 @@ ${boundedMainMemory.trim()}` : "";
|
|
|
2463
2543
|
messages: coreMessages,
|
|
2464
2544
|
tools,
|
|
2465
2545
|
temperature,
|
|
2466
|
-
maxTokens,
|
|
2546
|
+
...typeof maxTokens === "number" ? { maxTokens } : {},
|
|
2467
2547
|
experimental_telemetry: {
|
|
2468
2548
|
isEnabled: telemetryEnabled && !!latitudeApiKey
|
|
2469
2549
|
}
|
|
@@ -2526,8 +2606,11 @@ ${boundedMainMemory.trim()}` : "";
|
|
|
2526
2606
|
for (const call of toolCalls) {
|
|
2527
2607
|
const runtimeToolName = exposedToolNames.get(call.name) ?? call.name;
|
|
2528
2608
|
yield pushEvent({ type: "tool:started", tool: runtimeToolName, input: call.input });
|
|
2529
|
-
const
|
|
2530
|
-
|
|
2609
|
+
const requiresApproval = this.requiresApprovalForToolCall(
|
|
2610
|
+
runtimeToolName,
|
|
2611
|
+
call.input
|
|
2612
|
+
);
|
|
2613
|
+
if (requiresApproval) {
|
|
2531
2614
|
const approvalId = `approval_${randomUUID2()}`;
|
|
2532
2615
|
yield pushEvent({
|
|
2533
2616
|
type: "tool:approval:required",
|
|
@@ -3597,6 +3680,7 @@ export {
|
|
|
3597
3680
|
loadSkillContext,
|
|
3598
3681
|
loadSkillInstructions,
|
|
3599
3682
|
loadSkillMetadata,
|
|
3683
|
+
normalizeScriptPolicyPath,
|
|
3600
3684
|
parseAgentFile,
|
|
3601
3685
|
parseAgentMarkdown,
|
|
3602
3686
|
readSkillResource,
|